有序数组就想到二分查找,没有重复元素,查找下标唯一。写的时候要注意边界的判断。
在数组和链表的操作中,快慢指针非常常见。本题中,只有当快指针的值不是要删除的元素,才会赋值给慢指针。
平方的最大值一定在数组的两端出现,双指针可以巧妙解决。
用双指针,也可以叫滑动窗口。当子数组不够大时,移动快指针来放大窗口;当子数组大于target时,移动慢指针来缩小窗口。
按上右下左进行循环,每循环一次,对应行(或列)要向内缩小一次。
设置一个首结点,让所有位置的操作统一。熟悉链表的写法。
链表的基本操作。
迭代:双指针法,保存当前结点的下一结点,然后将当前指针指向前一结点。
递归:本质还是双指针。
手动模拟过程,需要提前记录后面的结点。
快慢指针,快指针先走n步。
双指针法很巧妙,两指针各自遍历完两条链,若相交,则一定同时到达交点。
快慢指针,快指针每次走两步,慢指针走一步,如果有环,两者一定在环内相遇。
此时一指针从头结点出发,一指针从相遇结点出发,两者相遇处即为环入口。
要快速判断一个元素是否出现在集合里,或者是否出现过,就要考虑哈希法。题目如果未限制数值大小,就不适合用数组。如果是返回索引下标不能用双指针法,返回数值则可以。
建立一个s中字符出现次数的哈希表(数组),再减去t中字符出现次数,最后观察哈希表中次数是否都是0。
建立数组1的哈希表(字典),再遍历数组2,如果出现在哈希表中,则添加到结果中。
本题直接用集合来做也可以。
建立哈希表,一个数如果出现过,则返回false。
遍历数组,如果一个数它的对应数不在哈希表中,则将这个数插入哈希表中,否则输出下标。
对a b数组的和建立哈希表,对c d数组的和进行遍历,将和为0的值记入结果。
用数组作为哈希表。
先排序数组,通过i,i+1,right三个指针来搜索满足条件,注意要跳过重复解。
与上题方法类似,只不过多加一层循环。注意target是不固定的,所以不能和上题一样去重。
交换法,双指针法均可。
间隔2k来处理。Python中字符串不可变,要将字符串转列表处理。
双指针法。先给数组扩容到填充后大小,再从后向前操作。
先去掉多余空格,然后翻转整个列表,最后对每个单词进行翻转。
利用取模操作。
本题关键在于理解KMP算法。
当原串和模式串不匹配时,会跳到模式串的最长相同前缀后的位置进行匹配,如果仍不匹配,则才从模式串开头重新匹配。
需要事先构建出模式串的next数组。即最大相同前后缀的长度。
需要用到KMP算法。先构造next数组,j从0,i从2开始取。len(s)如果是len(s)-next[-1]的整数倍,则是重复子串。(当然next[-1]需要大于0)
需要一个输入栈和输出栈。输入栈用于append元素。当需要输出队列头时,若输出栈不空,则只要弹出输出栈顶元素;若输出栈为空,则需要将输入栈所有元素添加到输出栈,再弹出栈顶元素。
如果只需要返回队列头的值,则可以先pop操作,再将这个元素压入输出栈。
判空时,需要两个栈均为空,队列才为空。
只需要一个队列即可(用双向队列但只使用popleft和append操作)。弹出栈顶元素时,需要将队列元素先依次弹出,并添加至队列尾,最后弹出原队尾元素。返回栈顶元素是利用索引。
栈很适合用来做对称匹配。
当字符为左括号时,将对应右括号入栈,当字符为右括号且与栈顶不同或者栈为空时,匹配失败,否则匹配成功,弹出栈顶元素。最后栈为空则全部匹配成功。
利用栈,相同就弹出栈顶,不同就入栈。
后缀表达式,遇到数字就入栈,遇到操作符则从栈顶弹出两个元素并计算结果,将结果入栈。
注意写法eval(f’{num1} {i} {num2}')可以直接运算求值。
使用单调队列(递减)来模拟。队列头存储最大元素的下标。队列满就将队头移出。如果队尾元素比当前元素小,则依次移出队尾元素,最后将当前元素入队。
•递归的三要素:确定递归函数的参数和返回值,确定终止条件,确定单层递归的逻辑。
•求高度用后序遍历,求深度用前序遍历。
• 如果需要遍历整棵树,且不用处理递归返回值,递归函数就不能有返回值。如果需要遍历某一固定路线,就一定要有返回值。
• 用迭代方式记录所有路径比较麻烦。
• 构造树一般采用前序遍历。
• 如果让空节点进入递归,就不加if。如果不让,就加if限制一下,两种终止条件不同。
利用栈,根节点先入栈,然后出栈访问结点值,接着非空的右结点,左结点相继入栈。
根节点先不入栈,先迭代访问最底层的左子树结点,然后访问其父节点,然后取右结点。
调整前序遍历的代码顺序,最后反转result数组。
访问但还没处理的结点,添加空节点作为标记。只有遇到空节点,才将下一个结点放进结果集。
利用队列,队列头出队时,访问该结点,并将左右非空结点入队。
注:stack = [root]和que=deque([root])的差异。
最后反转结果即可。
只输出每层的最后一个结点。
每层求和并求均值。
extend(cur.children)。
max操作。
每个结点的next指向队列头。注意跳出循环。
同上。
遍历完一层,深度+1。
当某结点左右子树都空,就跳出循环。
递归写法,层次遍历迭代法。
两棵树对称,不是两个结点对称。left.left 和 right.right,left.right 和 right.left.
迭代法要借助队列。
当成普通二叉树用层次遍历很简单。
完全二叉树可以用递归来做,当左右子树为满二叉树时,可以用公式来计算结点数。
递归写法很简单,只要不满足平衡性,就返回-1,否则就计算该子树的高度。
递归+回溯,当非叶子结点时,继续递归,注意回溯要紧接着递归,当为叶子结点时,将路径添加到结果集合。
递归法,左叶子结点值,加上左子树左叶子和,加上右子树左叶子和。
迭代法,借助栈。
层次遍历。
递归法,只查找某条路径所以一定要有返回值,使用target值减去结点值,如果到达叶子结点时,值为0则说明找到。如果叶子结点返回true,则函数立即返回true。
迭代法,需要两个栈,第二个栈用来存放target值。
递归+回溯,注意res.add(new ArrayList<>(paths))。
使用递归,根据后序最后一个元素先找分割点,然后再分割中序、后序,接着递归遍历。
与上题类似。
递归的分割数组,还是只对索引进行操作,可以节省时空开销。
当一棵树为空了,直接返回另一棵树。
利用二叉搜索树的特性,写递归和迭代都很方便。
简单做法是中序遍历得到数组,然后验证数组是否从小到大。
使用递归效率更高,设置一个前结点,遇到不符合的就返回false。
要会在递归中记录前一个结点的指针。TreeNode pre。
使用了maxCount,结合集合的clear操作从而只需要遍历一次。
使用后序遍历的递归,要知道怎么一层层把值给返回的。
最近公共祖先一定是值在两个结点之间的值。
递归法要用root.left等接住返回值。迭代法比较简单。
删除要考虑删除结点的几种情况。
先返回符合条件的结点,然后上面再接收结点
选取数组中间的元素作为分割点。
按右中左的顺序遍历。
• 组合问题需要index,不同集合取元素是index+1,同一集合取元素是i+1.
• 排列问题不需要index,因为每次都从头(从下标0)开始选元素。排列问题需要对树枝去重,也就是路径上去重,已经选过的元素不再选,需要用到pathUsed数组来标记,元素不重复时也可以直接用path.contains()来判断。
• 对同一层进行去重操作,也可以叫剪枝,通常需要的是一个映射,相同的值不会被重复取,可以用数组来做映射。可以排序的情况下,可以用i和i-1的元素是否相同来做。
• 子集问题不需要return,因为要返回所有的结点。
回溯算法,记模板。可以剪枝。
和上题类似。
学会做映射,以及字符串的加减。
关键在于,数组要排序,for循环要记得及时跳出循环。
要对同层元素进行去重,candidate[i]==candidate[i-1],就跳出本次循环。
回文串用双指针判断。学会字符串的切片操作。
需要判断区间内字符串是否合法,对原串进行操作,有些细节需要注意。
子集问题是收集树的所有节点的结果。
需要排序去重。
不能排序,需要用数组记录同一层已使用元素。
每次要从0开始遍历,需要对路径上元素去重。
排列问题需要路径去重,有重复元素需要在同一层去重。
在二维数组进行操作,最后将二维数组转为列表添加到结果中。本题关键在于如何将二维数组转为列表,以及怎么判断插入位置合法。本题还需要再做一遍。
需要用到二维递归。回溯方法需要有boolean的返回值,可以防止棋盘永远填不满。要会判断当前位置插入数字是否合法。
直接循环就行,可以先满足小胃口的,也可以先满足大胃口的。
其实就是找非单调序列的长度,用一个flag来标记增减。
当总和为负时,需要重新从0开始计算。
每个增区间都进行买卖。
模拟流程,走完一步每次用(剩余步数,该位置能跳步数)的较大者更新剩余步数
模拟流程会超时。需要记录当前最远覆盖,和下一步最远覆盖。索引只要走到倒数第二位就算成功。最远覆盖随着索引更新。当索引来到当前最远覆盖时,就需要再走一步了,同时更新当前最远覆盖。
首先对数组按绝对值从大到小排序,然后从头开始遍历,遇到负数就取反,若最后还剩奇数次k,则对最小的数取反。
先计算每个站的剩余,如果总剩余为负,则一定不行。如果累积到当前位置的剩余为负,说明至少要从下一个位置开始。
需要先考虑右边评分大于左边,从左向右遍历。然后再考虑左边评分大于右边评分,从右往左遍历。
模拟找零过程就行。
本题主要在于排序。首先按身高从大到小排,相同身高的k小的在前。然后再从左到右依次插入即可。
两个气球不重叠,就需要多射一支箭。当重叠时,每次都要更新重叠气球的最小右边界。
和上题类似。当两个区间重叠时,就需要移出一个区间,并更新重叠区间最小右边界。
首先利用数组做哈希,得到每个字母出现的最远下标。然后遍历字符串,每次更新最远边界,当索引和最远边界相同时,就可以进行分割。
两个区间有重叠就更新最大重叠右边界,当不重叠时,把左右边界添加到结果中。因为最后一个区间后面没有不重叠区间,所以需要单独在最后加上。
这道题要转为字符数组来操作。从后往前遍历,每当左边大于右边时,就需要将左边的数减一,最后统一将右边的数置为9。
当卖出股票可以盈利时,就开始累加收益,但此时未必是真正卖出。minPrice = price[i] - fee 这里很巧妙,当后面一天的price高于这里的minPrice,就说明此时并未真正卖出。
叶子结点不放摄像头,在叶子的父节点放摄像头,然后隔两个结点放摄像头。这道题巧妙的地方在于,设置了三种状态:0表示结点无覆盖,1表示结点有摄像头,2表示结点有覆盖。对于空节点,分析得知应该是有覆盖。最后如果根节点无覆盖,那么需要再放一个摄像头。
动态规划五部曲:1.确定dp数组以及下标的含义,2.确定递推公式,3.dp数组的初始化,4.确定遍历顺序,5.举例推导dp数组。
只用两个位置来存储即可。
本质还是斐波那契数列。
dp[i]是从位置i到终点的最小花费,其等于cost[i]+min(dp[i+1],dp[i+2])。
初始化很重要,本题要将左和上边界都初始化为1。
和上题类似。初始化时,遇到有障碍物,则应保持dp数组中初始值为0。遍历时遇到障碍物应该跳过此次循环。
这题用贪心效率更高。用dp,就是将dp[i],分成j*(i-j)和j*dp[i-j],取两者中大的。
dp[i] += dp[以j为头节点左子树结点数量] * dp[以j为头节点右子树结点数量]。
dp[i][j]表示,在背包容量j内,前i个物品可以得到的最大价值。对于物品i,有放和不放两种选择,对应的dp[i][j] = max(dp[i-1][j],dp[i-1][j-wi] + vi)。初始化,i为0时,当背包容量够放物品0时,就应该初始化为物品0对应价值。遍历顺序,先遍历物品,与先遍历背包重量都可以,因为需要的数据都在左上方。
本质是在一维数组上反复遍历更新。dp[j]表示容量j内,可以得到的最大价值。对于物品i还是两种选择,dp[j]= max(dp[j],dp[j-wi] + vi)。初始化,都初始化为0即可。遍历顺序,只能先遍历物品,再遍历背包容量,且容量j要从后往前遍历。如果j从前往后遍历,那么物品0可能被重复放多次,而从后往前不会有状态重合。如果先遍历背包容量,再遍历物品,那么每个dp[j]就只会放一个物品。
可以转为背包问题。背包容量为sum/2,物品的重量和价值都是元素的值。dp[j]表示的是容量j内可以得到的最大价值。当容量为目标值时,如果最大价值等于目标值,则说明可以找到这样的集合。
和上题类似。本题要尽可能让石头分成重量相同的两堆。背包容量为sum/2。最终用两堆石头重量作差得到结果。
首先要注意如果target+sum为奇数,是无解的。dp[j]表示装满容量为j的背包的方法数。初始化,dp[0]必须初始化为1。dp[j] += dp[j - nums[i]]。
这题背包容量有两个维度,分别是0和1的容量。
完全背包问题,背包容量也正序遍历。对于组合问题,先物品,后容量,因为这样物品的顺序是固定的。对于排列问题,先容量,后物品。
完全背包排列问题。需要先遍历容量。
可以转化为完全背包排列问题。相当于只用1和2组成n有多少种排列。
完全背包。求最少,初始化要为最大值。当dp[j-coins[i]]仍为初始值时,就不用更新dp[j]。
和上题类似。容量就是n,数的平方就相当于物品的重量。
单词就是物品,字符串就是背包,就是求物品能不能把背包塞满。dp[j]表示长度为j的字符串可以被拆分。如果dp[i]为true,且[i,j]区间内的字符串出现在字典里,那么dp[j]为true。对于本题,先遍历容量(对字符串遍历),后遍历物品更方便。
第i家如果偷,就只考虑前i-2家,不偷就考虑前i-1家。
要么在[0,n-2]中偷,要么在[1,n-1]中偷。
树型dp。因为需要通过递归函数的返回值做下一步计算,所以一定用后序遍历。定义一个长度为2的数组[不偷,偷],用来保存不偷该节点,和偷该节点所能得到的最大价值。
dp[i][0]表示第i天持有股票的最大利润,dp[i][1]表示第i天不持有股票的最大利润。最终结果是dp[length-1][1],因为本题不持有股票一定比持有股票利润高。本题用贪心做更方便。
和上题区别在于可以多次买卖。使用贪心更方便。
股票有5个状态:无操作,第一次买入/卖出,第二次买入/卖出。然后根据当天有无买卖写出递推关系。
上一题的升级版。k次交易就最多有2k个买卖状态。第二层循环要对状态进行遍历,奇数对应买入,偶数对应卖出。
dp[i][0],dp[i][1]分别表示持有股票,不持有股票的最大利润。需要初始化两天,dp[1][1]一定是非负的,需要在p(1-0)和0中取最大值。持有股票状态在更新时,如果当天买入,应该要从i-2天卖出状态计算,因为有一天的冷静期。
当天卖出时要减去手续费。
• 只有一个序列/数组时,定义dp[i]是到下标i即可。当有两个序列时,定义dp[i][j]是到下标i-1,j-1更有利于初始化方便,因为只需要置0就好。
• 要求是连续子序列时,只需要看前一个下标的状态即可。可以是不连续时,就需要考虑更多下标。
dp[i]表示以nums[i]结尾的最长递增子序列。dp数组需要初始化为1。dp[i]等于i之前的dp[j]的最大值+1。
和上题类似,但只需要前一个的状态。
子数组就是连续子序列。定义dp[i][j]是以下标i-1,j-1结尾的两个数组的最长重复子数组,这样定义方便初始化,只需要都初始化为0即可。
dp[i][j]表示长度为i-1,j-1的字符串的最长公共子序列。注意本题和上题递推公式的差异。
和上题一模一样。
dp[i]表示以i结尾的最大子数组和。dp[i]的来源有两个。
这道题可以完全当做求最长公共子序列来做。也可以在i-1和j-1不同时,只对j作减法。本题也可以用双指针做。
dp[i][j]表示,到i-1的s的子序列中,出现到j-1的t的个数。初始化,dp[i][0]要初始化为1。当最后一个元素相等时,可以选择用s[i-1]来匹配,也可以选择不用。
dp[i][j]表示到i-1,j-1的字符串要删除的最少次数。初始化很重要。本题也可以求出最大公共子序列,然后用长度作减法。
当最后一个字符不同时,有三种操作:删除一个w1的字符,删除一个w2的字符,和把一个w1的字符替换掉。
dp[i][j]表示下标[i,j]之间的字符串是不是回文串。当字符i和j相等时,如果j-i的值小于等于2,那么一定是回文串,否则就需要去看dp[i+1][j-1]的值。遍历顺序必须从下往上,从前往后。
也可以用双指针,要么是单个元素i,要么是相邻元素i和i+1,同时向往扩。
dp[i][j]表示i和j之间的最长回文子序列长度。初始化,需要将对角线上元素置为1。最后输出为0到n-1的的结果。
通常是一维数组,要找一个元素右边或者左边第一个比它大或者小的元素的位置,想到用单调栈。单调栈里存放的是下标。顺序是指从栈顶到栈底的顺序。
要找下一个比它大的,那么顺序是递增的。对于小于等于栈顶元素的直接入栈,而大于栈顶的则需要先计算距离并出栈,然后再入栈。
首先结果数组都初始化为-1,然后要建立一个哈希表,key是数组1的值,value是对应的下标。对于小于等于栈顶元素的直接入栈,而大于栈顶的,需要看栈顶元素是否在数组1中出现,没有出现就直接弹出,如果出现了就需要先取得栈顶元素在数组1中的下标,然后对结果数组进行赋值,注意,当前元素就是第一个更大元素。最后将当前元素入栈。
走两遍nums数组。
单调栈做法,遇到比栈顶小的就入栈,相等的先出栈,再入栈。当比栈顶大时,先出栈栈顶作为底边,此时若栈不空则栈顶作为左边界,然后计算体积。直到while循环结束,再把当前元素入栈。
本题也可以用双指针和动态规划来做,主要都是找到每个位置的左边和右边的最高柱子,然后利用短的来计算体积。
单调栈需要在最前面和最后面分别插入一个0。此时是要求单调栈递减的。即找到当前柱子的左右第一个小于它的柱子来计算面积。此题需要再做一遍。