当前位置: 首页 > news >正文

常见算法与数据结构

数据结构/算法链表二维数组栈/队列字符串map
多指针反转链表 ,反转链表II,两数相加,盛水最多的容器
滑动窗口最小覆盖子串,无重复字符的最长子串
贪心算法去除重复字母
动态规划
递归合并两个有序列表相同的树
分治
堆栈队列结构辅助二叉树的层序遍历
数学公式模型法环形链表IZ 字形字符串变换

链表类

多指针

pre辅助指针法

反转链表

pre辅助指针法 + 链表拼接

反转链表II

简单运算

两数相加

删除链表的倒数第 N 个结点

快慢指针

环形链表I

快慢指针 + 环状链表特征深度剖析

环形链表II

递归

合并两个有序列表

引入额外数据结构

环形链表I
环形链表II

排序算法(由小到大排序)

参考

最大值交换排序:每次从n-m+1个元素里选出最大的一个,和倒数第m个元素交换位置,m的取值范围是[2,n-1],时间复杂度On2,最优情况是On1,最坏是On2,没有额外空间开销

插入排序:从左边第一个元素开始,默认左边是已经排好序的,由左向右便利,把右边的第m个元素按大小顺序插入左边,外层

冒泡排序,每次遍历,凡是右边比左边大就对两者swap,这样一趟下来,第m次遍历后,第n-m+1个位置是最大值

两端排序递归法:两个指针begin和end分别指向数组最左和最右,beign不能位于end右边,end也不能位于begin左边,选取一个位置作为key,key通常是等于beign,右指针end先走,遇到小于key的停下,左指针开始走,遇到大于key的停下,判断此时begin和end是否相遇,没相遇的话begin和end的位置swap,相遇的话则对begin和end的位置和key的位置swap,此时arr[beign]右边都是大于它的,右边都是小于他的

间隔排序:gap初始值是数组长度,每次间隔gap,对gap范围内的数做排序,gap=gap/2,直到gap=0才算排序完成

堆排序

大根堆:最大堆是父节点,反之是小根对,数据长度为n,叶子节点下标为i,那么左子树节点2i,右子树节点就是2i + 1,最后一个非叶子节点是n/2 - 1;

将数组调整为小根堆的方法是,从最后一个非叶子节点开始调整,即选取左右节点最小的值和根节点交换,如此调整直到根节点,

链表能力算法

链表反转

对链表head做反转,定义当前节点指针cur与上一节点指针pre,cur初始化值为head,pre初始化为null(对应的head的上一节点是null)

循环条件为cur不为null时,定义节点tmp保存cur的next节点,cur的next指针指向pre,pre指针挪动到cur,cur挪到下一个节点即tmp

当cur为null时,pre即为反转后的链表的头结点,返回pre作为最终结果

链表加法

两个链表l1和l2相加得到结果值链表result,定义结果值链表resultList的头结点resultHead并为其堆内存new开辟空间,定义指针cur指向resultHead,遍历l1和l2,创建cur的next节点,为其new分配空间,每次遍历对l1和l2当前节点的结果值相加,大于10则进位1,余数作为当前节点的value复制给cur.next节点的value,cur指针移动到下一节点即cur=cur.next,循环结束后,如果carry不为0则,则需要在创建一个节点放到最后的位置即cur.next位置,返回值则是通过返回这个结果值链表resultList的头结点resultHead

链表旋转(链表中的每个元素向右移动距离k)

环状链表法:使用临时指针tmp指向链表head,挪动tmp到链表最后一个接口,该过程中使用count计数链表长度,使用tmp.next = head将链表连接成环,计算需要挪动的位置为count - k -1,将tmp挪动count - k -1的距离,定义一个ret指针是tmp的下一节点,然后断开tmp的next节点,返回ret,即完成链表旋转

左右分段拼接
根据规律将链表分为左右两端,然后再拼起来

链表排序

因为链表没有下标可用,排序算法中的交换排序,gap排序,分段递归排序都设计到非相邻值交换,利用数组的特征排序时间复杂度可达到On2,但如果是链表的话时间复杂度会达到On3,适合链表的是通过相邻元素运算的方式,例如插入排序与冒泡排序,此处选择插入排序方案

遍历列表,从第一个开始视为排序

插入/删除链表第n个节点

判断是否环形链表/获取环形开始的节点

哈希表法
通过判重哈希表key值唯一性的方式判断是否环状链表,哈希表key存储节点,value作为计数器,可以判断出环形链表开始的节点即首次出现重复的节点

双指针法
快慢指针法,fast和slow指针从head除非,fast每次挪动两个节点,slow每次挪动一个节点,当fast和slow相遇即可判断这是第一个环状链表

设环的长度为a,非环的长度是b,fast和slow挪动次数为s,那么当快慢指针相遇时满足关系

(2s - b) % a = (s - b) % a

此时可以看做快慢指针在移动b个位置就可以回到起点了,即(s+b-b) % a = 0,但我们无法得知b的具体值,但我们可以利用一个特点,就是我们利用第三个指针thrid向右挪动b次,slow指针也同时挪动b次,third和slow指针会正好在起点相遇,通过此特征可以获取到环的起点位置

递归

运算递归

2的幂

Pow(x, n)

链表递归

二叉树

二叉树特征

例如二叉树的前中后序遍历、层序遍历、判断两个二叉树是否相等,判断二叉树是否对称,二叉树最大深度

二叉树结构转换与构造

数组转二叉搜索树,二叉树展开为链表

二叉搜素树

二叉搜索树是一种二叉树,需要在二叉树的基础上满足以下几个性质

  • 左子树性质:对于任意一个节点,其左子树中所有节点的键值都小于该节点的键值。
  • 右子树性质:对于同一个节点,其右子树中所有节点的键值都大于该节点的键值。
  • 子树递归性:树的每个子树也是一棵二叉搜索树。

相关题目
数组转二叉搜索树,数量为n的节点所能构造出二叉搜索树的个数

二叉搜索树转为排好序的双向链表

按中序遍历,遍历结果是将二叉搜索树由小到大排列,使用中序遍历,定义一个指针cur,cur初始化值是树的头结点root,定义一个前序节点pre作为当前cur的上一节点,初始化值为null,的挪动cur遍历树,中序遍历输出的第一个节点(此时pre和head均为为null),即可作为链表的头结点head但如果pre不为null的话就让pre的right指向cur,cur的left指向right,挪动pre到cur,对右节点做遍历,当前节点cur为null作为递归结束条件

滑动窗口算法

最小覆盖子串

无重复字符的最长子串

动态规划

自底(初始化构建底部)向上构建问题解,往往涉及到二维数组结构的应用,也就是我们为了得到(m,n)这个点的最优解,往往需要把这前(m*n)个点的坐标都给求出来,然后取(m,n)

1、定义二维数组arr[i][j],同时构建横纵坐标模型,i为纵坐标,j为衡坐标,需要思考i、j和arr[i][j]分别代表什么含义,即代表xxx长度为i、j时的最优解的结果填充二维数组中arr[i][j]的值;

案例1,最长回文子串

暴力破解,两个指针外层循环,取两个指针范围内的字符串分别对其判断是否是回文,然后取里面的最大值

  • 机器人棋盘路径数量问题,i为长度m的变量,j为宽度n的变量,arr[i][j]为长度为i,宽度为j时的路径数量
  • 寻找网格最短路径问题,i为长度m的
  • 变量,j为宽度n的变量,arr[i][j]为为长度为i,宽度为j时的最短路径和
  • 将 word1转换成 word2 所使用的最少操作数问题,i和j分别代表着两个字符串的长度m和n,arr[i][j]带两字符串长分别为i和j时最小的子串长度
  • 找word1和word2最长公共子序列问题,i和j分别代表着两个字符串的长度m和n,arr[i][j]带两字符串长分别为i和j时最长子序列长度;
  • 寻找word1和word2最长公共子串问题,i和j分别代表着两个字符串的长度m和n,arr[i][j]带两字符串长分别为i和j时当前最新的最长的共有子串的长度;如果中间有过中断,即非先相等的时候,就把当前点设置为0,代表重新最算最大联系的子串,当前最大值会被记录到变量max中,当后续联系子串长度超过max会更新max

2、初始化、

  • 机器人棋盘路径数量问, 因为只能向下或向右行走,所以最下面的格子arr[i][0]和最上面arr[j][0]格子均初始化为 0<i<m 和arr[j][0] 0<j<n 初始化为1,代表只有1条路可走
  • 寻找网格最短路径问题,因为只能向下或向右行走,所以最下面的格子arr[0][i] += arr[0][i-1] 0<i<m 和arr[j][0] += arr[j][0] + arr[j-1][0] 0<j<n 初始化为1,代表最上面和最左边的最大值就是横/纵的简单加和
  • 将 word1转换成 word2 所使用的最少操作数问题,arr[0][j] = j; arr[i][0] = i;即把一个不存在的字符word1/word2替换为长度为i或j的另一个需要i或j步;
  • 寻找word1和word2最长公共子序列问题,dp[i][0]和dp[0][j]均为0,即有一方为0,那么他们的最长子序列均为0
  • 寻找word1和word2最长公共子序列问题,dp[i][0]和dp[0][j]均为0,即有一方为0,那么他们的最长子序列均为0

3、找规律填充所以非特殊(非坐标轴)数组

  • 机器人棋盘路径数量问题符合规律,也就是到达坐标(i,j)的路径数等于到达坐标(i-1,j)的路径和+到达坐标(i,j-1)的坐标数的相加,也就是从坐标(i-1,j)向右一步或从(i,j-1)向下一步,即dp[i][j] = dp[i-1][j] + dp[i][j-1]

  • 寻找网格最短路径问题,也就是到达坐标(i,j)的位置等价于从坐标(i-1,j)向右一步或从(i,j-1)向下一步,也就从这两步里选择一个总和最小的,arr[i][j] = Math.max(dp[i-1][j] + dp[i][j-1]+arr[i][j]

  • 将 word1转换成 word2 所使用的最少操作数问题,将word1看做横坐标i,word2看做纵坐标j,当前位置的word1和字符和word2 字符相等(word1.charAt(i-1) == word2.charAt(j-1))时,那意味着不需要任何操作,即dp[i][j] = dp[i-1][j-1],不相等时则要考虑三种操作机制

    • 替换操作,即(i,j)前的坐标点都已经被操作为相同字符的前提下的dp[i][j] = dp[i-1][j-1] + 1
    • 删除或增加word1一个字符同步word2,即dp[i][j] = dp[i-1][j] + 1
    • 删除或增加word2一个字符同步word1,即dp[i][j] = dp[i][j-1] + 1
      由于我们目前是获取最小操作步骤,所以最终在当前i,j位置 word1和word2字符串不相等时,我们取值内容即为Math.max(Math.max( dp[i-1][j], dp[i][j-1]), dp[i-1][j-1])+1;
  • 寻找word1和word2最长公共子序列问题,首先考虑当前位置i和j(word1.charAt(i-1) == word2.charAt(j-1))相等时dp[i][j] = dp[i-1][j-1] + 1,不相等时,那么此时最长公共子序列即为,那么dp[i][j] = Math.max( dp[i-1][j], dp[i][j-1])

  • 寻找word1和word2最长公共子序列问题,首先考虑当前位置i和j(word1.charAt(i-1) == word2.charAt(j-1))相等时dp[i][j] = dp[i-1][j-1] +1,不相等时不做任何操作,即当前位置的dp[i][j]还是0,所以相当于重新开始计算最大子串;

例题

编辑距离
最短路径
最长公共子序列、最长公共子串问题、选数问题
最长回文子串
最小路径和
编辑距离

贪心算法

求解一个问题,不考虑全局最优解,而且考虑某种意义上的局部最优解,通过这一个个局部最优解来完成某种操作(选择排序)、得出最优解结论(最少纸币问题、最多无重叠区间、最高效机器调度问题、股票最高收益问题)、可行性分析(跳跃问题)

案例

  • 选择排序:每次从未排序的数据中选取最小值,并把最小值放在未排序数据的起始位置,直到未排序的数据个是为0结束排序,问题解决
  • 跳跃游戏:在可达范围内一顺序循环遍历,获取最大值max,当max >= arr.length-1即成功,否则均失败
  • 无重叠区间/最大安排活动数问题:(先排序),按活动结束时间遍历由早到晚进行遍历,判断前一个的结束时间是否大于后面的开始时间,如果大于等于则无冲突,小于则有冲突
  • 机器最高效调度问题:两个数组,一个代表任务对应工作量,一个代表机器当前已被分配的总工作量①先按任务量由大到小进行排序②每次筛选出最早完成的机器,然后把当前还未分配的工作里取出当前最大的工作分配给该机器人(初试时把前三个最大的任务依次分配给前三个机器人)③工作任务全部分配完毕,从三台机器中取出工作量总和最大的

去除重复字母,且保证返回字典序最小

思路,需要保证元素如果一个字符的开头在结果字符串中不是最小的,且后面存在和它相同的字符,那么移除这个字符,

构建这个返回结果的过程中,循环判断,如果后面插入的字符字典序比临近的上一个元素小,且上一个字符在后面的字符串中还会出现,那么移除这个字符,继续循环,直到遇到

分治算法

双指针法

最大蓄水量问题:right和left分别指向左边和右边的墙体,计算出当前的面积,如果左墙高于右墙,那么right左移,反之,left左移,再次计算面积,然后每次对比当前面积大小;

三数之和:

/*** 三指针法** first指向第一个元素,初始值为0,second在first后,初始值为first+1,third在second后且用于不等于小于second,(相等时则退出循环)* 初始值为num.length-1即指向数组最后一个** 当second和上一个距离大于first至少1个位置的元素相同的元素要一个个跳出理防止重复,first如果当前元素和上一个相同,那么就要不断跳出** 我们的目标值取target = -nums[first];** 当三个元素所指向的数组之和等于0时,即nums[second] + nums[third] = target时记录这三个元素 nums[second] + nums[third] < target时,* 继续second下次循环,使得second +1即可,如果nums[second] + nums[third] > target,那么我们就得不断移动third直到至少nums[second]和nums[third]* 家和不大于target;***/

数字反转

两数之和

哈希表的应用

两数字和,暴力算法的话,需要n的2次方时间复杂度,即两层for循环,第一层for循环是在第一次循环中找到分别找到和这个数组成target,并在第二次的循环中找到target所在的数据,而hash表则根据判断,第一次循环中如果hashmap中存在other,那么任务结束,返回结果,否则就将其存储在map中等待下一次的匹配

## 专辑,字符串处理
  • 最长回文串:寻找回文动态规划、自底向上构建,由里到外填充,双重循环
  • KMP算法(jdk核心api):维护一个str2匹配表,和例如str1在第12个位置首尾元素相同时,str2整体和str1第12个后面比较发生不相等的时候,不会再从第13个开始匹配,而是根据匹配表查找可跳跃位数,比如这里这里匹配表查出的值为3,那么此时直接跳跃3个位置,从第15个开始匹配,大大提高匹配效率
  • 最大公共子串/子序列(jdk核心api):动态规划算法
  • 无重复字符的最长子串 滑动窗口

字符串处理算法

KMP算法

主串长度n,子串长度m

暴力匹配算法,最坏情况时间复杂度为(n-m + 1)*n

栈相关

StringBuilder栈(单向)+ 数组字符哈希映射表
不同字符的最小子序列

链表栈(双向)

移掉 K 位数字使得剩下的数最小
建立一个双向链表栈,从左向右,第一个数先入栈,容纳后循环处理,如果后面的比前面小(通过栈顶peek来做比较)且k>0,i > 0,且栈中元素不为空,就通过deque出栈移除,存入右边的,k–,循环处理完毕,剩下的数就是递增的了,移除最后面的k位

自底向上处理deque,如果首位是0直接移除

动态规划
最长回文子串

双指针

贪心算法

整数转罗马数字
由大到小去处理问题,本题要注意的是不能用哈希表来存储数字于罗马字符对应关系,因为哈希表是无须的,可以用数组来解决

数据模型法

Z 字形变换

二维数组法,长n宽m,m=numRows, 一个z等于 z = 2*numRows - 2

  • 余数 length % z = leave; lenLeave = leave - numRows > 0 ? leave - numRows : 1
  • 个数 length / z = num;

最终数组的长和宽分别是

  • n = num * (numRows - 1) + lenLeave
  • m = numRows

结合实际规律对数组填充,第gap*n列由上到下依次填充,中间的每列只有一个点被填充,该点的坐标是[mIndex,j],mIndex标识这个点的行号,取值范围是[m-2, 0),j是当前列

填充过程中,一旦发现当前字符串指针超过了字符串s的长度就返回arr,终止处理流程,返回二维字符数组arr,防止数组下标越界异常,将arr转为字符串

性能优化
将二维数组模型优化为StringBuilder列表(rows)模型,时间复杂度由On2降到On
列表的长度即为numRows,通过巧立flag,初始值为-1,定义指针指向rows中的元素,遍历字符串字符,用指针i所指向的row中的StringBuilder拼接这个字符
当i指向第一个(i = 0)和最后一个元素(i = numRows - 1)flag = -flag,i挪动flag个距离 最终处理完毕返回结果值

滑动窗口

无重复字符的最长子串

通过用双指针和一个hashMap结构的集合来模拟一个滑动的窗口来解决该问题,将字符串拆分成字符数组循环,start和end初始值为0,分别指向窗口的头和尾的下标,同时窗口的end指针就等于当前循环的指针,start和end之间的内容代表的窗口,窗口是动态移动的,start取值需要注意,需要考虑判断滑动窗口是否包含end所指向的字符,方法是判断hashMap中所存储的该字符的下标 + 1是否大于start,大于start说明是滑动窗口中的字符,start被调整否则就不是,需要用一个变量max随着外层指针的移动记录到动态窗口最大值。

循环递归

至少k个字符重复子串

jdk场景数据结构

本体是一个数组
transient Object[] elementData

add即为顺序插入数组

内存开销方面

result.toString().contains(String.valueOf(s.charAt(1)));

内存消耗分析

逻辑优化,使用一个字符串数组映射表,用于判断当前字符是否存在于这个字符串中

if中有多个并集,需要把过滤性高的提前

http://www.xdnf.cn/news/958483.html

相关文章:

  • std::ratio 简单使用举例
  • 【生产就曲篇】让应用可观测:Actuator监控端点与日志最佳实践
  • 操作系统 | Linux:第一章 初识Linux
  • 使用Docker部署操作系统
  • .NET 2025年第 75 期工具库和资源汇总
  • 【PX4飞控】mavros gps相关话题分析,经纬度海拔获取方法,卫星数锁定状态获取方法
  • StarRocks 全面向量化执行引擎深度解析
  • Spring AI Chat Memory 实战指南:Local 与 JDBC 存储集成
  • YoloV8改进策略:Block改进|FCM,特征互补映射模块|AAAI 2025|即插即用
  • 【三方库研读】facebook/folly中File类原理与作用深度解析
  • PydanticAI快速入门示例
  • JS手写代码篇----使用Promise封装AJAX请求
  • 内网im,局域网环境下BeeWorks 如何保障数据安全?
  • MySQL 隔离级别:脏读、幻读及不可重复读的原理与示例
  • 基于ASP.NET+ SQL Server实现(Web)医院信息管理系统
  • GraphRAG优化新思路-开源的ROGRAG框架
  • python训练营打卡第49天
  • 三元组 题解
  • 日志的具体使用
  • deepseek+coze开发的智能体页面
  • 链表的实现与介绍
  • codeforces C. Cool Partition
  • X86架构离线环境安装Ollama
  • DPC密度峰值聚类
  • 【MPC-C++】qpOASES 源码编译与链接,编译器设置细节
  • bond配置与拆卸
  • 理解OpenFOAM案例中的blockMesh文件里的simpleGrading
  • 【AI论文】CASS:Nvidia到AMD的数据、模型和基准测试的转换
  • 应对无法定位程序输入点kernel32.dll错误的详尽指南:从问题分析到解决方案
  • 如何迁移Cordova应用到HarmonyOS 5 以及迁移时常见的问题?