1. 队列
电话号码的字母组合 # 思路:先入队列,再逐个出队列,出队列的元素和新的字符相加再进入队列,直到遍历结束
层次遍历
2. 栈
括号匹配
最长匹配括号 # 思路,栈中存储括号的下标,若为(,则入栈,否则出栈,判断栈是否为空,若为空,说明没有以其匹配的左括号,放入下标(最后一个没有被匹配右括号的下标);否则,和栈顶元素计算最大长度;
linux路径
单调栈
每日温度(减栈)# 栈中元素为下标和温度值,温度减少才入栈
去除重复字符(增栈,考虑剩余)# 先统计每个字符的个数;遍历,set或者数组判断是否已存在,若不存在,看是否更小且后面还有(通过统计的个数),是则入栈,且更新set为没有
求最大矩形(增栈)# 从左往右遍历,递增则入栈,否则栈顶元素逐渐出栈,高度(栈顶元素的下标对应的值)*宽度(i-stack.peek-1)= 面积,求面积最大值
3. 链表(一般两个节点,一个next指向头结点,一个遍历)
两两交换链表的节点 # 申请一个节点指向头节点,一个临时节点做交换
旋转链表 # 先形成环,再在指定位置断开
4. 数组
旋转图像(数组旋转90度);先对角线旋转,再水平旋转
合并区间(先升序排列,再合并)
# 5. 二维数组
矩阵置0
**描述**,O(1)空间复杂度,若某一行或某一列有0,则这一行及这一列都置为0
**思路**,用第一行和第一列作为这一行或者这一列是否有0的标志位,用两个变量记录第一行及第一列本身是否有0
5. 回溯(深度优先,递归)一般有两个方法
全排列 # dfs,参数有result,trace,depth,数组长度n,used数组
全排列后的第k个
方法二:全排列+剪枝
n皇后,8皇后(原理同上,只是判断重复时,斜着的和竖着的通过set判断)
括号生成
ip生成
5. 递归(树的深度优先)# 方法一般有两个方法
验证二插搜索树
相同的树
有序数组转为二叉搜索树 # (中序遍历,总是选择中间左边的数作为根节点)
二叉树最大路径和 # 单个dfs,先计算左右的贡献值,然后计算max,返回贡献值(<0时返回0)1:最大贡献值(root.val+max(0, dfs(root.left), dfs(root.right))) 2:求最大路径(dfs(root.left)+root.val+dfs(root.right))
从前序和中序构造二叉树 # 构造中序遍历的值与左边的map,两个方法, return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1); 子方法返回root;
5. 树(递归,队列)
中序遍历 # 递归
公共祖先 # 中序遍历,if(root == null || root == p || root == q) return root; 递归调用,左子树为空,返回右边,返回root
恢复二插搜索树 # 用中序遍历(递增)找到错误位置的数据的下标,然后交换
二叉树展开为链表 # 先中序遍历,再展开
3. 排序
逆序对个数 # 分两步,1:合并两个有序数组(归并排序) 分:public void sort(int[] nums,int l,int r) 和 sort(int[] nums,int l,int mid,int r)借用一个数组合并,3个while
2:改1,合并时,左边数组的指针移动时,计算逆序对的贡献(累加左边集合剩下的数量total += (mid-p1+1))
课程表 # 拓扑排序,用队列维持入度为0的点,然后相关的入度减1
3. 逆序
旋转数组(数组右移动k位)# 思路:3次逆序
旋转数组的最小值 #思路:二分法(二分法带等号), 区间先递增,后到最小值,再递增
搜索旋转数组(二分)
4. 动态规划(可以拆分为子问题)
模板
```c
单串问题
# 依赖前单个元素
dp[i] = dp[i-1] + ns[i]
# 依赖前部区域元素,最大最小问题
dp[i] = min(dp[i], f(dp[j])
```
0-1背包;动态规划 # dp方程 f[i][j] = Math.max(f[i - 1][j], f[i - 1][j - weight[i]] + value[i]);
最长回文 # 方程:dp[i+1][j-1] == 1 && (s.charAt(i) == s.charAt(j),遍历顺序:一个递减,一个递增
最大子序列和,如{5,4,-1,7,8}的最长子序列和是23 # dp[i] = Math.max(dp[i-1]+a[i],a[i]);
编辑距离,一个单词变为另一个单词(删除,添加,替换的方式)的最短距离 # if( ){ dp[i][j] = dp[i-1][j-1]; else dp[i][j] = 1+Math.min(Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1]);
数字字符串编码为字母 #第一种情况:最后一位使用一个字符,s【i】!=0,dp[i] = dp[i-1] ;第二种情况:最后两位拼成字母,s[i-1]!=0 && 10*s[i-1]+s[i] <26,dp[i] = dp[i-2]
不同二插搜索树的个数 # G(n)=G(0)∗G(n−1)+G(1)∗(n−2)+...+G(n−1)∗G(0) ,两层for循环, dp[i] += dp[j-1] * dp[i - j];
交错字符串 # 定义 dp(i,j) 表示 s1的前 i 个元素和 s2的前 j 个元素是否能交错组成 s3的前 i+j 个元素,s3.charAt(i+j-1) == s1.charAt(i-1) && dp[i-1][j]或者s3.charAt(i+j-1) == s2.charAt(j-1) && dp[i][j-1];dp[i][j] = true
不同的子序列 , s和t,s中包含t的个数 # dp[i][j]代表s前i个字符中包含t前j个字符的个数,if(s.charAt(i-1) == t.charAt(j-1)){ dp[i][j] = dp[i-1][j-1]+dp[i-1][j];else { dp[i][j] = dp[i-1][j];
三角形最短路径和 # dp[i][j]=min(dp[i−1][j−1],dp[i−1][j])+c[i][j]
买卖股票(可以交易多次)
```java
//i天,状态j(0,股票),手里的最大收益
dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]-prices[i]);
dp[i][1] = Math.max(dp[i-1][0]+prices[i],dp[i-1][1]); ```
零钱兑换,最少硬币数 # 两层遍历金钱和硬币数,dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
零钱兑换,数组总和 # 两层遍历硬币数和金钱, dp[i] += dp[i - coin];
对于面额为 coin 的硬币,当 coin≤i≤amount 时,如果存在一种硬币组合的金额之和等于i−coin,则在该硬币组合中增加一个面额为 coin 的硬币,即可得到一种金额之和等于 i 的硬币组合。因此需要遍历coins,对于其中的每一种面额的硬币,更新数组 }dp 中的每个大于或等于该面额的元素的值。
5. 二分法
猜数字,平方根,搜索旋转数组
两数相除 # 二分(left:0,right:被除数,判断除数*mid与被除数大小)+快速乘(while (k > 0) { if ((k & 1) == 1) ans += a; k >>= 1; a += a; }),每次循环将a的值翻倍,将k的值减半
指数运算 pow # 二分法,递归 如x的77次方,可以按照x的38,19,9,4,2,1的顺序
5. 滑动窗口,双指针
无重复字符的最长子串; #sss left = Math.max(map.get(s.charAt(i))+1,left);
盛最多水的容器 # 两边向中间收拢
三数之和 # 先排序,遍历,对于每个小于0的数,双指针从两边向中间靠拢,通过sss if(i > 0 && a[i] == a[i-1]) continue; 去除重复解
最接近的三数之和(同上)
链表倒数第n个节点(两个指针,一个走k步再走第二个)
是否有环(快慢指针)
6. 前缀和(hash存位置数量等,和为**的连续数组)
最长相等01子数组 # map存前缀和为a时的下标i;
最长偶数元音子数组
连续和为 k 倍 的子数组# 前缀和对k取余相同即存在
6. 堆
中位数 # 两个堆数量不一样,则放入大根堆,再remove大根堆的到小根堆,两个堆数量一样,则放入小根堆,再remove小根堆的到大根堆,小根堆元素多
7. 其他
z字形变换
下一个升序排列 # 思路:先从后往前找升序对,index= i(i为左边的数);i之后的一定降序;在【i+1,len)之间找到最小的大于num【i】的,然后交换值,然后【i+1,len)之间的升序
接雨水 # 对于每一个坐标i,雨水为两边最大高度的较小者减去高度:min(left_max,right_max)-height [i]
买卖股票 # 遍历一次,记录当前节点之前的最低价格,和最高利润
贪心
整数转罗马数字 # 思路:先枚举,再从大往小减
罗马转数字 # 有公共前缀,替换
8. 数学
整数反转 # 通过取余能求整数的最后一位,思路:通过对10取余求 最后一位,除法求剩余的数字,while循环中处理
格雷编码 # 原有的集合a(n),前面各位前面加0的集合a1(n) + a镜像后前面加1的集合a2(n) = 即为 a(n+1),其中a1(n) == a(n)
最大公约数 # public static int gcd(int x, int y) { return y == 0 ? x : gcd(y, x % y); } 除数和余数计算,当除数为0时返回被除数
[快排]
两个方法,第一个,当low《high 时,获取中间的指针,并递归调用左右两边
第二个,返回中间指针,遍历,当遇到比最右边小的时候,交换pointer和当前元素,遍历完成后交换pointer和最右边元素,返回中间的指针pointer
归并
分:从中间分开,左右两边循环调用,直至只有一个元素,然后调用第二个方法
和:第二个方法,申请一个数组做合并使用
LRU
get和set方法,基于linkedHashMap(双向链表)set时先删除,再判断容量(超过时删除最后一个元素),再put
get时如果存在,需要重置元素位置到第一个
海量数据的中位数:二分,二进制的最高位,次高位。。。,分为两个文件,统计个数
========================================================================================================
回溯模板
public static void dfs(){
//终止条件
for(){
//if()剪枝
//操作
dfs();//递归调用
//逆操作,回溯
}
}
重复全排列剪枝
要解决重复问题,我们只要设定一个规则,保证在填第
idx
idx 个数的时候重复数字只会被填入一次即可。而在本题解中,我们选择对原数组排序,保证相同的数字都相邻,然后每次填入的数一定是这个数所在重复数集合中「从左往右第一个未被填过的数字」,即如下的判断条件:
```java
if(i>0 && nums[i] == nums[i-1] && used[i-1] == 0){
continue;
}
01前缀和
if (map.get(prefixSum) != null) {
ans = Math.max(ans, i - map.get(prefixSum));
} else {
map.put(prefixSum, i);
}
========================================================================================================
comparator方法 (a,b)->{return a-b;} //升序
若只有一行可以改为 (a,b)-> a-b //升序
二进制String转十进制
Integer.parseInt(“1111”,2)
十进制转二进制
Integer.toBinaryString(7)
位运算
判断是否是 2 的幂 :A & (A - 1) == 0
最低位的 1 变为 0 :n &= (n - 1)
最低位的 1:A & (-A)
list转数组
list.toArray();
二维
list.toArray(new int[0][]);
========================================================================================================