一、数组 5

1. 基础知识

连续空间 相同类型元素。
注意java中二维数据是一些不连续的一维数组空间。
易读不易删除。

2. 典型解法

(1) 二分法

适用范围
有序、无重复元素。
时间复杂度O(log n)。
注意
循环不变量:定义区间左闭右闭或者左闭右开的写法是不同的。
相较左闭右开更简洁。
题目
704返回目标值下标

(2) 双指针

常用于数组、链表、字符串

适用范围
比暴力解法O(n^2)降低时间复杂度,O(n)。
注意
明确每个指针的含义,以及什么时候移动。

快慢指针(同向)
相向指针

题目
27移除元素
977.有序数组的平方

(3) 滑动窗口*

不断的调节子序列的起始位置和终止位置
一种特殊的双指针
适用范围
比暴力解法O(n^2)降低时间复杂度,O(n)。
注意
窗口内是什么?
如何移动窗口的起始位置?
如何移动窗口的结束位置?

复杂度O(n),虽然是两层循环,但是每个数组元素只被操作了2次(进窗口、出窗口)

for(窗口结束位置移动) {while {窗口起始位置移动}
}

滑动窗口的精妙之处在于根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)
题目
209.长度最小的子数组

(4) 模拟行为

适用范围
着重考察边界条件的处理。
注意
循环不变量原则:左闭右开
题目
59.螺旋矩阵II

二、链表 7

1. 基础知识

会写链表的定义

public class ListNode {int val;ListNode next;ListNode() {}ListNode(int val) { this.val = val; }ListNode(int val, ListNode next) { this.val = val; this.next = next; }
}

易增删O(1),不易查找O(n)。

单链、双链、循环。一般考察单链和循环。

2. 典型解法

(1) 用虚拟头节点操作链表

链表操作的两种方式:
直接使用原来的链表来进行删除操作。
设置一个虚拟头结点在进行删除操作。

适用范围
只要有改动必用虚拟头节点
注意
使用原链表需要单独处理头节点。删除头节点时只需要head = head.next;
处理头节点是需要注意1.head本身是null;2.全链表都是待删除节点。也就是需要考虑head为null时的返回。
题目
203.移除链表元素

注意
add操作可以合并。减少出错可能性。
一般要加入size,方便判断index是否有效。
题目
707. 设计链表

(2) 双指针(迭代)

pre、cur指针:我理解是在修改链表时,有暂存的作用。
适用范围
除了直球以外的题。暂时也没别的方法。
注意
分解步骤,每步改变哪个指针的指向,同时记得暂存原指向节点。
也可用递归方法,递归pre+cur,或者递归head(指把head后面看成下一个相同的问题)
题目
206.反转链表(经典必考题)
24.两两交换链表中的节点

快慢指针:两条链相关,不管是认为创建还是本身给定,用来对齐(结尾对齐或是初始对齐):判断环、判断相交、找链上的一点
题目
19.删除链表的倒数第N个节点
初始对齐,然后一起走
160.链表相交
初始对齐,然后一起走
142.环形链表II
找结尾对齐
快慢先判断有环,数学推导得出快指针从相遇点,慢指针从head,一起走到环入口。

三、哈希表 8

1. 基础知识

哈希函数:将传入的key映射到符号表的索引上。
哈希碰撞:处理有多个key映射到相同索引上时的情景。
拉链法:将所有关键字为同义词的结点链接在同一个单链表中
线性探测法:保证tableSize大于dataSize,当指定位置以有数据,则继续往下找空位置

牺牲了空间换取了时间。
三种哈希结构:

  1. 数组
  2. Set:只看有没有,true or false,不要求返回索引什么的。
  3. Map
集合/映射 底层实现 是否有序 数值是否可以重复 能否更改数值 查询效率 增删效率
HashSet
TreeSet
LinkedHaskSet
HashMap
LinkedHashMap
TreeMap
Hashtable
// 取不到用默认值
map.getOrDefault(a, 0)
// 循环
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {if (pq.size() < k) {pq.offer(new int[]{entry.getKey(), entry.getValue()});} else {if (pq.peek()[1] < entry.getValue()) {pq.poll();pq.offer(new int[]{entry.getKey(), entry.getValue()});}}
}

2. 典型解法

适用范围
用来快速判断一个元素是否出现集合里

(1) 数组

适用范围
适合有限且数量少的key,一般遇到字母相关的,只有26个,首选数组(比Map空间小)
注意
三遍循环:第一遍加,第二遍减,第三遍检查。
(有时可在第二遍中直接检查)
题目
242.有效的字母异位词
383.赎金信

(2) Set

适用范围
没有限制数值的大小,就无法使用数组。
注意

题目
349.两个数组的交集
202.快乐数

(3) Map

适用范围
有些需要存储的值供结果使用:索引(要索引就不能排序了)、个数
注意
四问:
为什么会想到用哈希表
哈希表为什么用map
本题map是用来存什么的
map中的key和value用来存什么的
题目
1.两数之和
454.四数相加:四个数组,两两分组。两次二层循环。

(4) 不用Map:双指针

适用范围
几数相加求目标和的问题:
如果是几个不同数组,无需考虑重复问题,使用Map
如果是一个数组中取数,三数及以上考虑去重和剪枝。
注意
先排序。
三数:循环(第一个数)+双指针(第二、三个)
四数:双循环(第一、二个数)+双指针(第三、四个)
去重:因为原数组中有重复的数。在移动指针后(做实际计算前),与指针之前指向比较。保证移动指针后指向一个新值。
剪枝:当循环表示的数已经不能组成目标值时(大于目标值且大于0),进行剪枝。

题目
18.四数之和
15.三数之和

四、字符串 7

1. 基础知识

本章注意总结一些库函数。解题时关键部分不要直接使用库函数。并且清楚各个库函数的内部实现原理和空间时间复杂度。
注意s.length()ch.length

char[] ch = str.toCharArray();
String str = String.valueOf(ch);
String str = String.valueOf(ch, 0, slow);
String str = new String(ch)s.replaceAll(" ","%20");StringBuilder sb = new StringBuilder();
sb.append(s.charAt(i));
sb.reverse().toString();// 转化
int i = Integer.valueOf(s);// 除去开头和末尾的空白字符
s = s.trim();
// 正则匹配连续的空白字符作为分隔符分割
List<String> wordList = Arrays.asList(s.split("\\s+"));
Collections.reverse(wordList);
return String.join(" ", wordList);

2. 典型解法

(1) 双指针

适用范围
替换某些字符、反转(也是一种替换)、旋转(几次反转)
比暴力降低了时间复杂度。
注意
空间复杂度(在char[]的情况最好原地)、时间复杂度
确定双指针指向的意义,以及什么时候移动指针
如果是数组填充,注意从后向前进行操作保证不覆盖数据。
题目
替换型:
剑指 Offer 05. 替换空格
反转型:
344. 反转字符串
541.按条件反转:按条件移动指针到反转起点。当需要固定规律一段一段去处理字符串的时候,要想想在在for循环的表达式上做做文章
*151. 反转字符串中的单词:去空格(全去掉再加)、反转全部、反转每个单词
旋转型:
剑指 Offer 58 - II. 左旋转字符串:前n反转+剩下部分反转+整体反转

(2) KMP* ——subString()是如何实现的

适用范围
在一个串中查找是否出现过另一个串
前缀表:当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。

注意

KMP必须理解概念和逻辑,强记会忘……

1.构造样串的前缀表
前缀:指不包含最后一个字符的所有以第一个字符开头的连续子串。
后缀:指不包含第一个字符的所有以最后一个字符结尾的连续子串。
最大相同前缀后缀:aabaa,前缀有a,aa,aab,aaba。后缀有a,aa,baa,abaa,所以相同的前缀后缀是a,aa;最大相同前缀后缀是aa
int[] next前缀表:存储起始位置到下标i之前(包括i)的子串中,有多大长度的相同前缀后缀

因为要求的是“最大”,所以会有一个while循环,负责从长度最长的前缀(即j的最大情况,因为j指向前缀末尾位置+1,i指向后缀末尾位置)开始回退到更短一点的前缀(表现为j指向上一个最大相同前缀后缀的末尾的下一个位置,next[j - 1]),直到找到符合的。
为了方便理解,我一般用j = 0next[0] = 0初始化,每次从j开始比较,next存储的是最大相同前后缀的长度,也即下一个要比较的index。这样回退时,j=next[j - 1]
2. 比较字符串和样串,并根据前缀表回退
这一部分和构造前缀表很像,核心也是相同的话两个指针后移,不同的话j不断回退。需要注意1和2都要保证j > 0

题目
28. 实现 strStr()
459.重复的子字符串:
数学问题,s 去除 最长相等前后缀的部分就是最小重复字串。
数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环,len % (len - (next[len - 1] + 1)) == 0

方法总结:双指针 10(重复)

数组、链表、字符串:通过两个指针在一个for循环下完成两个for循环的工作
另外几数之和的求解也使用双指针,虽然与哈希表方法都是O(n^2)的时间复杂度,但是剪枝容易。

五、栈和队列 7

1. 基础知识

java中stack queue是容器么?
我们使用的stack queue是属于哪个版本的STL?
我们使用的STL中stack queue是如何实现的?
stack queue提供迭代器来遍历stack queue空间么?

java中的栈Stack
队列 Queue:接口,实现是LinkedList
双端:Deque,实现是ArrayDeque

自己的理解:ArrayDeque是标准的队列操作,LinkedList占用空间小。使用时注意选取。

// 入队 add会抛异常,用offer
ArrayDeque.offer(); //队尾加
ArrayDeque.offerFirst();
ArrayDeque.offerLast();// 出队 remove会抛异常,用poll
ArrayDeque.poll(); //队头删
ArrayDeque.pollFirst();
ArrayDeque.pollLast();// 取值,get会抛异常,用peek
ArrayDeque.peek(); //队头值
ArrayDeque.peekFirst();
ArrayDeque.peekLast();

2. 典型解法

明确什么时候使用栈、队列

用栈模拟队列需要两个栈,用队列模拟栈只需要一个,但是需要一个size标志栈元素个数。
同时注意peek可以复用pop方法, 只需要把弹出的元素加回去。

(1) 栈

适用范围
匹配:处理成对的符号(括号)、消消乐
递归的实现,如linux目录

递归的实现是栈:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中

注意
匹配问题技巧:将右符号入栈,这样出栈时直接比较和期望值是否相等。
一般用switch-case-default,用default处理逻辑

题目
20.有效的括号
1047.删除字符串中的所有相邻重复项
150.逆波兰表达式求值:理解下机器计算的逻辑,后缀算术表达式更方便计算
编号:71. 简化路径

(2) 单调队列*

适用范围
设计一个单调队列实现目的。队列中维护可能是结果的元素,并保证顺序(不是排序,是入队顺序,入不入队/出队需要判断)
注意
一般用ArrayDeque做队列

队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的

题目
239.滑动窗口最大值

(3) 优先级队列*

适用范围
一般要求时间复杂度,我理解是一个排序问题。
一个披着队列外衣的堆,所以大顶堆、小顶堆可以用优先级队列去实现。

堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。

优先级队列内部元素是自动依照元素的权值排列。创建时要写清楚排序顺序,comparator可以简化为lambda式

PriorityQueue<int[]> pq = new PriorityQueue<>((p1, p2) -> p1[1] - p2[1]);

注意
时间复杂度O(n log n)
循环时可以只保证队列只有需要的前k值,超过k之后对队列进行出队入队操作。
题目
347.前 K 个高频元素

六、二叉树——递归三部曲 28

1. 基础知识

种类

满二叉树:2^(n-1)节点
完全:叶子节点在最后一层且靠左(一定是平衡的)
平衡:深度差<=1
搜索:有序,左中右增大
平衡二叉搜索树:AVL树
搞清楚容器map、set等的底层是用了什么

存储

链式:指针
顺序:数组

如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。

遍历方式

深度优先遍历
前序遍历(递归法,迭代法)
中序遍历(递归法,迭代法)
后序遍历(递归法,迭代法)
广度优先遍历
层次遍历(迭代法)

2. 典型解法

如果是模拟前中后序遍历就用栈,如果是适合层序遍历就用队列,当然还是其他情况,那么就是 先用队列试试行不行,不行就用栈

(1) 深度遍历

深度遍历可递归可迭代(用栈模拟递归)

适用范围
求深度用前序遍历
求高度用后序遍历
用递归时最好用后序遍历
题目
144.二叉树的前序遍历(opens new window)
145.二叉树的后序遍历(opens new window)
94.二叉树的中序遍历

<1>递归

递归思考方法:
确定递归函数的参数和返回值
确定终止条件
确定单层递归的逻辑
对于前中后序遍历,只需改变递归逻辑执行递归方法的顺序。

<2> 迭代

使用栈存储待处理节点。
两个动作:访问和处理。
前序访问和处理同序,只需将访问的节点放入栈中。
中序需要一个指针访问节点,一个栈存储待处理节点。

迭代方法一:
后序=前序改变左右顺序+结果反向
中序,访问节点不为空就加入到栈,一直访问左节点。为空时开始从栈中取节点处理,并开始访问右节点。

迭代方法二:前中后序代码只需要改变几行顺序
只处理中间节点(保证每个节点只被处理一次),在中间节点进栈后使用null分隔。出栈时如果是null处理下一个出栈节点,不是null则先出栈,再按指定顺序(前中后序反向进栈)。这样是出栈顺序保证是处理顺序。

(2) 广度遍历

二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现

适用范围
深度、行/层相关
注意
每层的size在处理前先存储固定下来(队列的size)
题目
102.二叉树的层序遍历
107.二叉树的层次遍历II
199.二叉树的右视图
637.二叉树的层平均值
429.N叉树的前序遍历
515.在每个树行中找最大值
116.填充每个节点的下一个右侧节点指针
117.填充每个节点的下一个右侧节点指针II
104.二叉树的最大深度
111.二叉树的最小深度

(3) 属性相关的

<1>最大最小深度/高度

前序深度,后序高度,一般是递归。后序容易点,前序需要求深度回溯(没懂)
迭代还是层序容易理解
注意
最小,一定要保证是叶子节点!注意排除左/ 右树空的情况

104.二叉树的最大深度
559. N 叉树的最大深度

<2>对称-比较两个树

外侧相比,内侧相比->左.左和右.右比,左.右和右.左比
*处理null的情况
递归:比较两颗树是不是相同。
迭代:层序,用一个容器(栈或队列)装待比较的两个节点,同时入同时出(注意遇到null不能直接true,而应该下一轮循环)
101. 对称二叉树
100.相同的树
572.另一个树的子树

<3>完全二叉树

抓住和满二叉树的区别往上靠。2^n -1
递归:出现满二叉树时结束
222.完全二叉树的节点个数

<4>平衡二叉树

特殊的求高度题。使用后序遍历
递归(迭代效率太低了):注意一旦出现非平衡就用-1去提前结束。
110. 平衡二叉树

<5>所有路径、路径和(叶子节点)——回溯*

注意
放入结果集时,一定要新建存储路径list,否则结果集记录的是list地址,接下来路径list变化会影响结果集中的结果

递归回溯:回溯一定与递归相伴,不能用大括号把它们分开!
有几个递归就有几个回溯(恢复操作:添加进去了就删除)

回溯隐藏在traversal(cur->left, path + “->”, result);中的 path + “->”。 每次函数调用完,path依然是没有加上"->" 的,这就是回溯了

257.二叉树的所有路径

路径和一般用递归(迭代会复杂),递归是否需要返回值有标准:
if 需要搜索全树 {
if 需要处理递归返回值 有返回值
else 不需要处理递归返回值 不需要返回值
} else 一条符合路径就行 {
有返回值
}
递归函数的返回值是bool类型,是为了找到一条边立刻返回。

还是前序遍历顺手,注意回溯。

112.路径总和 一条就行
113.路径总和 II 所有符合

<6>左叶子、左下角

先辨别目标节点是什么:
左叶子:叶子+是一个节点的左孩子,所以需要在它的父节点做判断。——深度遍历
左下角:深度最深的左叶子节点

404.左叶子之和
递归法:求和还是用后序,这样根= 左+右
迭代法:因为不需要处理节点,只是找左叶子,用什么序遍历都可以

513.左下角的值
递归法:因为不需要处理节点,只是找左下角,用什么序遍历都可以,左优先就行。这里用前序。记录新深度的第一个节点值(因为左优先,所以一定是左下角)
迭代法:深度用层序,找最后一层左数第一个节点

(4) 修改/构造二叉树

<1>翻转——左右节点交换

递归:前序、后序遍历都可以,中序不行,会交换两次
迭代:层序遍历,每个节点处理(也可用前序、后序迭代)

226.翻转二叉树

<2>构造

构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。
一层一层切割,用递归。
一般情况来说:如果让空节点(空指针)进入递归,就不加if,如果不让空节点进入递归,就加if限制一下, 终止条件也会相应的调整

中+后、中+前构造树
前+后无法构造,不唯一。

中+后为例:
第一步:递归结束条件:如果数组大小为零的话,返回空节点。
第二步:取后序数组最后一个元素作为根节点。
第三步:找根节点在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
第五步:切割后序数组,切成后序左数组和后序右数组
第六步:递归处理左区间和右区间
第七步:根节点左右叶子赋值,返回根节点

注意

  1. 区间一定要保证一致,一般是左闭右开
  2. 递归中传递索引index比传递切割数组好
  3. 区间的左右计算要注意,从入参计算而不是0或者原数组大小

105.从前序与中序遍历序列构造二叉树
106.从中序与后序遍历序列构造二叉树

最大树
同样是以根节点做分割

654.最大二叉树

<3>合并

操作两个树一般用层序遍历,加入同一个队列。
注意
这里没有size的那层循环了。(因为队列加入了两棵树的节点。size需要/2)
617.合并二叉树

(5) 搜索二叉树

<1>属性

左节点均小于根节点,右节点均大于根节点。且左右子树均为搜索二叉树。
最大特征是有序,即中序遍历时,会得到一个严格递增数列。(所以没思路时想想转化成递增数列怎么做)
因此解题一般都用中序遍历,修改处理根节点的逻辑即可。
中序遍历左中右,有特殊情况是右中左。

注意
1.有时候有等于情况,看清题。
2.尽量遍历一次,因为有序,路径是已经规划好的
3.叶节点是最小值/最大值陷阱技巧:中序比较的话,设置前一个节点pre,和当前节点比较即可。(前一个节点一定是比当前节点小一点点的那个节点,有序的性质决定的)
4.多个结果,遍历一次的技巧:如果 频率count 等于 maxCount(最大频率),要把这个元素加入到结果集中。频率count 大于 maxCount的时候,不仅要更新maxCount,而且要清空结果集

题目
700.二叉搜索树中的搜索
98.验证二叉搜索树
530.二叉搜索树的最小绝对差
501.二叉搜索树中的众数
538.把二叉搜索树转换为累加树

<2>修改和构造

插入节点,不需要遍历!找空节点
删除和剪枝比较复杂,涉及结构改变。
删除中心思想是将剩下的左节点作为右子树的最左叶子节点的左节点。
剪枝:处理不在[low, high]区间的节点处理。(既要找到不符合的节点,也要处理符合节点的左右子树,找有没有子节点不符合)
构造出的是平衡树,从中间开始(递归前序,迭代层序遍历)。

注意
可以使用返回值实现插入和删除等。

题目
701.二叉搜索树中的插入操作
450.删除二叉搜索树中的节点*
669.修剪二叉搜索树*
108.将有序数组转换为二叉搜索树

(6) 最近公共祖先

深度最大,想到从底到顶,需要用回溯,即递归实现后序遍历。(也就必须遍历整棵树了,不然没办法到中节点。)
按左右中处理,中节点的处理是判断是不是公共祖先。
结果获得依靠返回值,遇到两个节点即返回。

二叉搜索树结果就唯一了。从顶至底寻找第一个在[p, q]区间的节点。

需要理解为什么从根节点搜索的第一个在[p, q]区间的节点一定是最近公共祖先。

题目
236.二叉树的最近公共祖先
235.二叉搜索树的最近公共祖先

具体用什么顺序参考总结。
普遍的:
搜索树是中序。深度高度等属性看是前序还是后序。
涉及修改构造的一般前序。
回溯用后序递归的良好性质。
两个树一般用层序。

七、回溯——回溯三部曲 16

1. 基础知识

回溯就是递归中返回上一步的操作。所以回溯算法也就是递归算法。
是搜索穷举,仅高于暴力的低效算法,效率不高,但有的题只能这么做。所以回溯暴搜就不考虑什么时间空间复杂度了,没意义。只能剪枝提高一点效率。

等效于递归中放for循环。

与树的关系:
对应的是N叉树,优化就是剪枝,结果就是树的一个从根到叶节点路径。
集合中找子集的操作:
for循环 - 横向遍历是给定集合的大小【取值范围】。
递归 - 纵向遍历是所求结果的长度【子集大小】。

三部曲:

  1. 确定回溯结束条件
  2. 回溯输入参数,缺啥加啥(返回值一般都是void)
  3. 回溯遍历过程:
void backtracking(参数) {// 到叶节点结束if (到叶子节点 || 已经不满足子集条件) {按需保存路径结果;return;}for (元素 : 本层集合) {处理,元素添加到路径;backtracking(参数 变更) ;回溯,完全对应处理;}
}

种类:
组合问题:N个数里面按一定规则找出k个数的集合
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
排列问题:N个数按一定规则全排列,有几种排列方式
棋盘问题:N皇后,解数独等等

2. 类型

(1) 组合

剪枝:
for循环时:本层最后几个元素不会被遍历到(组成子集的个数不够了)
递归时:当前路径已经不满足子集的条件了。
提前结束
重复元素剪枝前需要排序。

去重:
同一层的相同元素?结果集中的子集可不可以重复
同一枝的相同元素?看子集中元素可不可以重复
需要used[]判断是层还是枝的

集合:
有时是从同一集合选取,有时是不同集合选取组成子集

两个重要变量:
startIndex:同一组合中选取就需要,作为for循环的初始,不然不同顺序的同一组合也会输出。(变成排列了,也就是第一次取了2,第二次取3,第三次又取2。组合的遍历应该保证顺序是2,2,3)
递归时传入的startIndex:+1 代表下一次选取不包括现在的数。不加表示可以重复选取

注意
1.保存结果时要新建一个再放入结果集。
2.递归参数决定了下一层的集合范围,决定了可不可以重复使用元素。
3.stringBuilder、map的运用

题目
77.范围 [1, n] 中所有可能的 k 个数的组合
216.组合总和 III:相加之和为 n 的 k 个数的组合
17.电话号码的字母组合:不同集合
39. 组合总和:指定集合(无重复元素),可重复使用元素
40. 组合总和:指定集合(有重复元素),不可重复使用元素,结果不可重复

(2) 子集

和组合很像。但组合求叶节点,子集求全部节点。(递归可以不设终止条件,每个节点都放入结果集)。
去重剪枝,还是同层不可重复就排序

注意
递增题的坑在于原数组不可排序,于是需要一个哈希标识本层这个元素有没有用过。数少可用数组,多了用map、set。且因这个哈希不参与递归,不用回溯!

题目
78.子集:互不相同元素数组,结果不重复
90.子集:有重复元素数组,结果不重复【二刷-用used】
491.递增子序列:有重复元素数组,结果不重复,子集元素要顺序递增

(3) 分割

选元素变成了选择从哪里分割。其实和组合一样。
模拟切割就是选择一段区间,选取还是用index。
终止条件是分割完毕了,要么分割线已到结尾,要么已分成指定的份数。
一般是中间同层for循环时要判断下到这个元素的区间符不符合要求,符合再递归。

题目
131.分割回文串:【二刷-用动态规划优化】
93. 复原 IP 地址:中间还要对原始字符串做变动

(4) 排列

不需要startIndex了。
去重还是靠剪枝。先排序。剪掉树层的。
使用used数组,作为参数传进去(也要回溯)
【二刷提示:搞清楚剪枝这里的false和true】

if (i > 0 && nums[i] == nums [i - 1] && used[i - 1] == false) continue;

深搜拓展:332.重新安排行程:【二刷提示】

题目
46.全排列,输入数字不相同
47.全排列,输入数字有相同,结果要去重。

(5) 棋盘

行对应树的深度。列对应树的宽度。叶子节点对应结果。
一行填一个:遍历行靠递归,遍历列靠for循环。每次判断符不符合要求。
每个空都要填:行列都要for循环。备选数字集也for循环。【二刷提示:搞懂这里为什么是二维递归】

题目
51.N皇后
37.解数独

【二刷:时空复杂度】

八、贪心算法 17

1. 基础知识

什么时候用:手动模拟一下感觉可以局部最优推出整体最优,而且想不到反例,那么就试一试贪心。
其实没有什么规律。

2. 类型

(1) 简单类型

题目
455.发饼干:小喂小。或者大喂大。注意用什么做循环
1005.K 次取反后最大化的数组和:两次贪心。注意翻转剩余次数为偶数时不用操作。
860.柠檬水找零钱:只需列出需要处理的几种情景

(2) 动规

题目
376.摆动序列:求波峰波谷,注意有平坡。【一刷动规】
53.最大子序列:【一刷动规】
122.买卖股票的最佳时机 II :分割为小问题【动规】
55.45.跳跃游戏:重要的是范围覆盖。跳几步和数组长度的关系也比较绕。

(3) 模拟场景

题目
134.加油站:本质还是需要数学判断下
738.单调递增的数字:char数组可以原地修改。
968.监控二叉树:和二叉树交叉题。自底向下遍历,后序递归遍历。每个节点的情况只有0未覆盖1有摄像头2被覆盖三种情况(null属于2)。需要额外处理根节点。

(4) 两维度排序

如果涉及二维排序,一定要先固定一个顺序先排一遍,不然会乱。
题目
135.按分数发糖果:两次遍历
406.根据身高重建队列:两个维度,先排身高

(5) 区间重叠问题

基础都是气球问题:需要按区间左/右排序。遍历并区别处理重叠和不重叠两种情况。
题目
452.最少的箭射气球
435.无重叠区间
56. 合并区间
763.划分字母区间:取得末次出现位置,二次遍历不断更新区间最大值,当到达这个值时意味着找到了一个区间。统计字母用数组而不是map。

九、动态规划 38

1. 基础知识

动规是由前一个状态推导出来的,而贪心是局部直接选最优的。

步骤-五部曲:

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序:几层循环,谁外谁内;从前到后还是从后到前
  5. 举例推导dp数组

Debug:出错很正常
找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!

2. 类型

(1) 基础问题

注意
初始化要考虑数组越界问题。
初始化不一定从0开始,如果无意义可跳过。见70题。
题目
509.斐波那契数列:递推公式和初始化都给了
70.爬楼梯:难度在于得到递推公式,实际是斐波那契数列。
746. 使用最小花费爬楼梯:递推公式和初始化都要从题目中推。

二维(可优化为一维)
62.不同路径
63.不同路径 II:障碍在初始化时就要注意,遇到障碍就结束。有条件的去更新dp的值。

343.整数拆分:难想到dp定义+递推公式。给dp赋值时也是可以遍历不断更新选取最大的值
96. 不同的二叉搜索树:难想到dp定义+递推公式。得找规律式分析。和343一样,赋值dp的时候可以通过不断遍历最终给到合适的值。

(2) 背包问题

1 01背包-每个物品只用一次

注意
第一步:dp[i][j]的定义:
一般说的背包问题,指物品(重量,价值),背包(容量)。

  1. dp[i][j]指 使用前0-i个物品装容量为j的背包,实现最大的价值。
  2. dp[i][j]指 使用前0-i个物品装容量为j的背包,有多少种装法。
    物品不需要排序!
    背包容量可能不是一个维度,比如可能限制0和1的个数(或长宽高等)。此时j其实增长为二/多维的。整体dp就变成了三/多维的。这时可以看出dp用一维会更简单,省去初始化问题,内循环条件判断也简化进循环范围了。

第二步:dp[i][j]的递推公式

所有背包都可先用二维写法,即物品-背包容量去计算。熟悉后可知道一般dp[i][j]是由dp[i][j - x]去计算的,即一定由物品为行、容量为列的表格的左上角计算出的。可以降为一维(即算本行时去一边使用一边覆盖上一行的值)。在递推公式和初始化甚至推导dp数组时脑子里都要用这个表格。

// 最大价值:背包放i这个物品和不放这个物品,选一个大的。
dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
// 装满,多少种装法:不装这个i物品就满了有X 种,装了这个才满有Y种,要加起来
dp[i][j] = dp[i - 1][j] + dp[i - 1][j - weight[i]];
dp[j] += dp[j - nums[i]];

降为一维很简单,i - 1变成i。

第三步:dp初始化
二维:
0号物品那行,0容量那列。初始化时选择合适的循环条件只赋值非0的(本身数组就初始化为0了)。
一维:
因为内层循环是倒序遍历,所以初始化为0(个别的需要带进去考虑下dp[0]应该是什么)。

第四步:循环顺序
二维:
双循环内外无所谓,一般物品-背包,从小到大遍历。初始化做了行列为0的情况,这里就从1开始。
一维:
外物品(从0开始)内背包,且内背包要从后(最终容量)向前(weight[i]),保证i这个物品只被放入一次(区分完全背包)。
特别的,根据weight[i]设置合适的循环条件。这样内层循环不需要再判断会不会由j - weight[i]带来数组越界。

题目 都是变种
找背包容量:是分堆的目标值。当然计算这个目标值时要考虑边界情况。
416.分割等和子集
1049.最后一块石头的重量II

装满多少种装法:
494.目标和:考虑下物品是weight是0时,初始化怎么搞?

背包容量是多维:
474.一和零:只要是容量的维度,一维内循环都得从后向前遍历。并且物品weight多个维度都能满足背包才能放进去,否则直接看下一个物品。

2 完全背包-每个物品可用多次

与01背包区别在于,一维循环顺序。
纯完全不分内外,且从前向后。
但装满背包有几种方式, 循环的先后顺序就有很大区别了。
组合数:外物品内背包容量
排列数:外背包内物品,内层需判断j - weight[i]带来数组越界

注意
初始化也不一定是有意义的,可能只为了递推方便。(注意!377)

新题型:最少的个数:与最大的价值对应。

  • 递推变成min(dp[j], dp[j - weight[i]] + 1, dp更新条件为dp[j - weight[i] != Integer.MAX_VALUE
  • 注意初始化dp为最大值,并手动初始化dp[0]
  • 返回值也处理下为Integer.MAX_VALUE的情况,是返回-1还是0

题目
装满多少种装法:
518.零钱兑换II

装满多少种装法,求排列
377.数组找目标值组合
70.爬楼梯:可扩展至每次走1-m阶

装满,物品最少个数(不在意遍历顺序了):
322.零钱兑换
279.由完全平方数组成:注意循环边界可以缩小。以及物品是什么?

能不能装满:
139.单词拆分:dp定义 能/不能;递推公式dp[j] 与dp[j - word.length()]的关系;循环遍历顺序:有序用排列

3 多重背包-每个物品N个

方法一:多于一个的物品加入到物品数组中,用展开的物品集去遍历,变成01背包
方法二:01背包遍历,背包容量里加一层循环,遍历这个物品的个数

(3) 打家劫舍

基础的比背包简单,难在变种。
递推公式:当前节点偷不偷,与前两个节点有关

dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);

遍历顺序:从前向后。

之前的遍历都是迭代,用for。这里可看到树形情况用递归后序去遍历合适。

题目
198. 打家劫舍
213.围成一圈的房子:(考虑)偷首就不能偷末,分开计算这两种,选最大的
337.树形房子:偷父就不偷子。重点在于遍历顺序,用递归后序。递推公式还是考虑偷不偷。注意既然遍历顺序是后序,dp是从叶子节点向根节点推的。初始化即初始化空节点和叶子节点【二刷注意典型题】

(4) 股票问题

dp的定义是最困难的。困难在本题有几个可能的状态j:一般定义为dp[i][j]j 一般是指持有的最大利润和不持有的最大利润 的变体。
初始化:多次买卖下,dp[0][j]初始化持有状态,注意考虑前一个状态(实际上因为是0开始,允许当天买卖多次的话都初始化为第一次持有)
结果:最后一天近似于不持有的状态一定能获得最大利润,对应几个状态要具体分析。

注意
不建议简化为一维数组,容易解释不清楚。
题目
121.买卖股票:一次买卖。俩状态:持有不持有。
122.可买卖多次:递推公式变化,买入时考虑上一次的不持有状态的利润
123.两次交易:状态变化,(没买),第一次持有,第一次不持有,第二次持有,第二次不持有
188. K次交易:123推广,2变成k,用循环解决初始化和递推
309.含冷冻期:状态变化,多了冷冻期和当天卖出状态,画下状态转移图来写递推公式
714.多次交易,收手续费:比122多扣一个手续费。注意和初始化一致,要么买入扣,要么卖出时扣

(5) 子序列问题**

dp定义:区别以XX结尾的子串 和 以XX结尾的A,B的最大/小XXX。

不连续

300.最长递增子序列:dp定义以nums[i]结尾的子序列。不断更新result选最大的。因为不连续,所以双层循环,每次求dp[i]都要计算0~i-1子序列中最长的长度。
1143. 最长公共子序列:718不连续的情况。这个dp[i][j]定义不好理解,好好看下。指的是text1.substring[0, i-1]和text2.substring[0, i-1]的最长公共子序列。其实和718是一致的。 递推公式比718要多处理一个不相等的情况。(因为可以不连续)
1035. 不相交的线,相对顺序不变的序列长度。就是1143.

连续

674.最长连续递增序列:比300多了连续。就只需要考虑i-1子序列了。
718.最长重复子数组:dp[i][j]标识以i-1,j-1结尾。不断更新result选最大的。
53.最大子数组和:以nums[i]为结尾。不断更新result选最大的。要么加入,要么从头开始,两者选最大的。(注意不能简单的认为是负数就只能从头开始)

编辑距离问题

dp的定义非常关键。为了使用word.charAt()比较字符相等,需要定义为 结尾是i 结尾是j的子串的XXX(题目所求)。为了初始化方便,使用dp[i][j]标识i-1 j-1情况。
状态转移:讨论这次比较字符的结果,dp会有不同的推导。总体来说从左上角、上、左三个方向推导。两个串谁可以变化就谁对应的索引就要-1。
脑子里要有转化矩阵,就是dp[i-1]dp[j-1]dp[i-1]dp[j]dp[i]dp[j-1]dp[i]dp[j]
初始化:具体分析,因为标识i-1,所以dp[0][0]实际相当于考虑空字符串。
392.判断 s 是否为 t 的子序列
115.在 s 的 子序列 中 t 出现的个数。因为是个数,考虑多种情况的结果相加。注意bagg和bag这种情况(二刷考虑:为什么只考虑-1,不考虑-2、-3……?
583.word1 和 word2可随意删除,求使相同的最少删除操作数:
方法一:总长度- 2*最长公共前缀
方法二:dp设置为题目所求。注意当word1[i - 1]word2[j - 1]不相同的时候,有三种情况。
72. 编辑距离:可增删和修改,实际都是一种操作。

回文

回文串和回文子序列是不同的,回文子序列可以不连续。
dp定义为索引i到j之间的XXXX。
遍历顺序考虑j是从i开始还是从i+1开始,关系到初始化。回文的遍历是以左下角向↗遍历的,和其他的动规不同。所以要额外注意数组越界问题。

647.统计回文子串个数:统计还是用result累计。dp用来判断是不是回文串。
516.最长回文子序列:单独初始化

十、单调栈 5

1. 基础知识

求解 一维数组 中,一个数的下一个比它大/小的数。
一个栈,栈底->栈顶 是单调递增的,可用来求 下一个比它小的数。
单调递减的,求下一个比它大的数。
栈中存放的是index。都是和栈顶去比较,根据< = >的结果分别处理。
注意
初始化:按题意,用-1或者0初始化。如果涉及到三数比较的问题,考虑前后是不是添加0
注意判断栈空情况。
什么时候用if,什么时候用while。
什么时候push,什么时候pop。
题目
739.求下一个更高的温度:单调递减的栈,一出现即将加入的值比栈顶大,即开始处理。这个值就是栈顶当天的下一个更高温度。
496.nums1中的数在nums2中的下一个更大元素:739加上nums1变成map,在遍历nums2时,如果是nums1的值就去处理结果。
503.循环数组的下一个更大值:循环就是数据遍历两遍,应用索引时需要i%nums.length
下面这两个题是反着的,需要好好理解。
42.接雨水:大-小-大组合,求长*宽
84.柱状图中最大的矩形:小-大-小组合

总结

每周/两周刷题一天,断断续续的搞了大半年。希望二刷进度快点(三个月搞定吧)。
进度:
由于基础,开始一直没找到感觉,一直到贪心才有点明白。做题还是连续的好,可以一星期一次但必须连续,最好3-4天一次。
心态:
克服畏难情绪!可以看了不会,多看几次总会会的。不可以没看就害怕。比如在二叉树卡了两个月,严重影响了刷题的效果。
一刷是大概了解典型题做法的。
二刷是巩固典型题做法的。
三刷再考虑典型题多解法,各个解法的优劣,时空复杂度,选取的数据结构。
四刷可以看看其他题型和额外题目。
难点:
KMP
二叉树
动规的dp定义
接雨水+柱状图面积

记不住的方法

char也可以直接用作for循环变量。

ArrayList

没有直接获取/移除最后一个元素的方法,需要size() - 1

get(int index)
// 这里若是Integer有坑,移除的是index不是该元素的值
remove(int index)// 排列int[]
Arrays.sort(nums);//填充
for (char[] c : chessboard) {Arrays.fill(c, '.');
}// 转成 int[]
result.toArray(new int[result.size()][]);
// 初始化数组
Arrays.fill(nums, 1);

String

// 子字符串左闭右开
substring()## Map,TreeMap有序
containsKey()
getOrDefault(key, 0)
// put方法可以重复使用,每次更新//循环
for (Map.Entry<String, Integer> entry : temp.entrySet()) {entry.getValue();entry.getKey();
}// char[]转 string
String.valueOf(c)//string转int
Integer. parseInt

排序

按绝对值倒序排序

nums = IntStream.of(nums).boxed().sorted((o1, o2) -> Math.abs(o2) - Math.abs(o1)).mapToInt(Integer::intValue).toArray();
Arrays.sort(people, (o1, o2) -> {if (o1[0] == o2[0]) {return o1[1] - o2[1];} return o2[0] - o1[0];});

比较时,注意int边界

# 没有问题
Arrays.sort(points, (o1, o2) -> Integer.compare(o1[0], o2[0]));
if (points[i][0] > points[i - 1][1]) # [[-2147483646,-2147483645],[2147483646,2147483647]]不通过
# [[-2147483648,2147483647],[-2147483648,2147483647]]不通过
Arrays.sort(points, (o1, o2) -> {return o1[0] - o2[0];});
if (points[i][0] - points[i - 1][1] > 0) 

代码随想录 一刷总结(完结)相关推荐

  1. 【代码随想录二刷】Day23-二叉树-C++

    代码随想录二刷Day23 今日任务 669.修剪二叉搜索树 108.将有序数组转换为二叉搜索树 538.把二叉搜索树转换为累加树 语言:C++ 669. 修剪二叉搜索树 链接:https://leet ...

  2. 【代码随想录二刷】Day21-二叉树-C++

    代码随想录二刷Day21 今日任务 530.二叉搜索树的最小绝对差 501.二叉搜索树中的众数 236.二叉树的最近公共祖先 语言:C++ 530. 二叉搜索树的最小绝对差 链接:https://le ...

  3. 【代码随想录二刷】Day15-二叉树-C++

    代码随想录二刷Day15 今日任务 层序遍历 226.翻转二叉树 101.对称二叉树 语言:C++ 层序遍历 102.二叉树的层序遍历 class Solution {public:vector< ...

  4. 【代码随想录二刷】Day20-二叉树-C++

    代码随想录二刷Day20 今日任务 654.最大二叉树 617.合并二叉树 700.二叉搜索树中的搜索 98.验证二叉搜索树 语言:C++ 654. 最大二叉树 链接:https://leetcode ...

  5. 代码随想录一刷个人记录

    代码随想录一刷个人记录 2022/7/14(leetcode) 2022/7/15(leetcode) 2022/7/17[字符串] 2022/7/18[字符串] 2022/7/20[双指针] 202 ...

  6. 代码随想录1刷—单调栈篇

    代码随想录1刷-单调栈篇 什么时候想到单调栈? 单调栈的原理? 单调栈工作过程? [739. 每日温度](https://leetcode.cn/problems/daily-temperatures ...

  7. 代码随想录1刷—贪心算法篇(二)

    代码随想录1刷-贪心算法篇(二) [452. 用最少数量的箭引爆气球](https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-b ...

  8. 代码随想录二刷——动规篇章

    dp 动态规划篇 dp篇二刷复习 路径种数问题 63.不同路径 II 拆分问题 343. 整数拆分 96.不同的二叉搜索树 背包问题 0-1背包,最多选一个,选or不选 406.分割等和子集 1049 ...

  9. 【代码随想录】刷题笔记Day5

    前言 竟然足足一星期没刷题了,上周毕设紧急赶了波工,就没什么动力,希望不会因此生疏了,组会过后刷新了焦虑值,又是新的一周,干巴爹 209. 长度最小的子数组 暴力解法:时间复杂度O(n2),两个for ...

最新文章

  1. 雷鸣----总结下男人30岁之前要知道的事
  2. libinjection开源库的研究总结
  3. C#中使用DTS来导入数据及相关问题
  4. Win10技巧:16个系统优化设置小技巧,大幅度提升你的电脑性能!
  5. Google Maps地图投影全解析
  6. RTX移植到STM32F103
  7. Java 1.1字符串
  8. monkey 查找闪退页面的方法
  9. Python机器人-最简单的机器人答复
  10. WebStorm配置Sass
  11. 飞利浦css5530+g评测,飞利浦这套入门家庭影院CSS5530竟毫不逊色于自家旗舰!
  12. 王之泰201771010131《面向对象程序设计(java)》第十五周学习总结
  13. 当保险遇上AI,泰康保险集团智能化升级有秘籍
  14. Oracle 11g如何清理数据库的历史日志详解
  15. Unity 关于Destroy 和 OnDestroy失效(延迟)的问题
  16. LabVIEW字符串中显示多种字体
  17. WorkFlow学习分享:WFGOTask线
  18. 升级Windows10,安装程序无法正常启动无法初始化工作目录
  19. 160cracked-1
  20. IIS部署Silverlight

热门文章

  1. 自己做的html5手机网站
  2. Python asyncio模块
  3. 海难(有n个人在一艘海上航行的船上Java循环链表解)
  4. Flutter ValueNotifier错误用法 插眼
  5. 荷兰 转专业 计算机,去荷兰读硕确实可以转专业,来看看有适合你的专业吗?...
  6. ButterKnife(黄油刀)基本使用与源码解析
  7. 超宽带无线电:汽车传统钥匙演化手机智能钥匙的关键技术
  8. Android踩坑小记:在onResume中申请权限
  9. 单片机控制灯光亮度c语言程序,STC89C52RC单片机按键控制PWM输出LED灯亮度C语言程序...
  10. sign (数学符号函数)