整理进度:1-201,518-581,1017-1024
待归档:
记忆化广搜:
#576 Out of Boundary Paths
排序问题:
#581 Shortest Unsorted Continuous Subarray
哈希,重复模式:
#594 Longest Harmonious Subsequence
滑动窗口:
#611 Valid Triangle Number
找规律:
#621 Task Scheduler
动规/记忆化搜索/贪心:
#630 Course Schedule III
双指针滑动窗口
-
#3 Longest Substring Without Repeating Characters
- 思路1:
后指针向后滑动以添加新的元素,前指针向后滑动保证滑动窗口内无重复字符 - 思路2:
维护每个字符最后一次出现的位置,后指针发现一个前面出现过的字符时,前指针可以直接跳到该字符最后出现的位置后面,i = max(i, lastpos[s[j]])
,效率更高一些
- 思路1:
-
#11 Container With Most Water
前指针、后指针中间的区域为一个桶,桶的面积为高乘以宽,当i、j为两端时,桶宽最大,当i、j向中间滑动时,只有桶高增加,桶面积才有可能增大。若移动长板,桶高不可能增大,因此只能移动短板对应的指针。 -
#1 2Sum
当前后指针和大于k,前移后指针(和变小),否则后移前指针(和变大) -
#15 3Sum
遍历并固定第一个数,从而把问题转换为2Sum
同理 #16 3Sum Closest、#18 4Sum -
#76 Minimum Window Substring
滑动窗口前后指针都从起始位置开始,先移动后指针使得窗口内数组符合要求,然后移动前指针直到窗口内数组不再符合要求,随后再次移动后指针,依此类推。
分治策略
-
#4 Median of Two Sorted Arrays
- 思路1:双约束二分
寻找A数组中的指针i、B数组中的指针j,使得两指针之前的元素的并集构成两数组的前半部分,这样就会产生两个约束条件,也即:
①前后两部分元素个数相等:i + j = m - i + n - j
②前半部分的最大值小于后半部分最小值:A[i - 1] <= B[j] && B[j - 1] <= A[i]
由于i、j满足约束条件①,因此只要确定了i,就可以确定j,如此一来,只需要寻找i的合适位置
如此一来可以利用二分搜索维护 i 的区间 [i_min, i_max],下次搜索区间可以利用约束条件②来确定 - 思路2:归并
简单把两个数组归并后找中位数,但需要用到额外的空间
- 思路1:双约束二分
-
#33 Search in Rotated Sorted Array
首先用二分搜索找到数组中的最小元素,之后就可以构造出旋转前的数组元素对应在旋转后的数组中的位置索引,根据这一索引进行全局的二分搜索即可。#153 Find Minimum in Rotated Sorted Array 和这道题是一样的
若考虑数组中存在重复元素,则需要在查找数组最小元素的二分搜索步骤中添加一个预处理步骤,保证二分区间的左右端点值不同,见 #81 Search in Rotated Sorted Array II、#154 Find Minimum in Rotated Sorted Array II -
#50 Pow(x, n)
二分求快速幂,注意考虑幂的正负 -
#69 Sqrt(x)
二分确定x的范围,比较x^2与a的大小 -
#74 Search a 2D Matrix
其实不能算是分治策略,但形式有点像二维的二分搜索,所以干脆放在这里
从右上角开始进行搜索,若小于目标值,则向下搜索,若大于目标值,则向左搜索,时间复杂度O(m + n) -
#162 Find Peak Element
很像之前遇到过的一个面试题目
这道题如果用O(n)的算法是非常简单的,我们需要找到一个O(logn)的算法
二分策略的一般流程是,先找到区间的中点元素,将这个元素与某一个标准进行对比,从而决定接下来的区间选择哪一侧
对比标准常见的有:1、与某个目标值作对比,比如二分查找;2、与左右端点值作对比,比如上面的 #33 Search in Rotated Sorted Array;3、与中点元素两侧的元素作对比,相当于判断中点元素处的单调性,比如这道题就是。
我们判断nums[mid]
与左右两侧元素的大小,如果这一点正好是peak,就直接输出,如果某一侧单调递增,则在这一侧必存在一个peak元素(若递增后开始递减,则存在peak,若一直递增到数组末尾,因为设定nums[nums.size()] = -∞
,所以此时末尾元素是一个peak),进而可以将搜索区间决定到这一侧
动态规划
-
#5 Longest Palindromic Substring
- 思路1:与反向字符串计算最长公共子串
回文串的问题,一般都可以试试对比原string与反向string的方法 - 思路2:区间动规
dp[i][j]
表示子串 [i, j] 是否为回文子串 - 思路3:中心扩展判断回文串
遍历所有的回文串中心(考虑奇数、偶数长度),从中心向外扩展可以用O(n)的时间找到该中心位置对应的最长回文串,这个思路其实相当直接,但时空复杂度相对于上面两个动规反而更好
- 思路1:与反向字符串计算最长公共子串
-
#10 Regular Expression Matching
算是经典题目,.
表示任意匹配单字符,*
表示前一字符可以出现任意次数
dp[i][j]
表示模式串前 i 位是否能与字符串前 j 位匹配 -
#32 Longest Valid Parentheses
dp[i]
表示以i为结尾的最长合法括号子串长度
当s[i] == '('
时,dp[i] = 0
当s[i] == ')'
时,
①若s[i - 1] == '('
,则dp[i] = dp[i - 2] + 2
②若s[i - 1] == ')' && s[i - dp[i - 1] - 1] == '('
,则dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2]
-
#44 Wildcard Matching
和#10类似的字符串匹配问题,这里*
不再代表前一字符出现任意次数,而是代表任意序列(可为空)- 思路1:动规,可以考虑用滚动数组优化空间
dp[i][j]
表示字符串前前 i 位与模式串前 j 位匹配
dp[i][j] = dp[i - 1][j - 1] && (s[i - 1] == p[j - 1] || p[j - 1] == '?'), if p[j - 1] != '*'
dp[i][j] = dp[i][j - 1] || dp[i - 1][j], if p[j - 1] == '*'
- 思路2:贪心
若发现模式串出现*
,则记录当前模式串/字符串遍历位置pi与si,继续往下匹配,若匹配失败,则返回记录的位置,字符串每次匹配失败后重新开始匹配的位置需要前移一位
- 思路1:动规,可以考虑用滚动数组优化空间
-
#53 Maximum Subarray
用dp[i]
表示以i为结尾的子数组最大值,则dp[i] = max(dp[i - 1] + arr[i], arr[i])
,由于dp[i]
仅与dp[i - 1]
相关,所以可以降维到0维。
同理**#121 Best Time to Buy and Sell Stock** -
#188 Best Time to Buy and Sell Stock IV
同系列的还有 #121 Best Time to Buy and Sell Stock、#122 Best Time to Buy and Sell Stock II、#123 Best Time to Buy and Sell Stock III
第k次购入仅和第k-1次卖出时的收益相关,第k次卖出仅和第k次购入时的收益相关
以rele[i][j]
表示 i 时刻进行第 j 次卖出的最大收益,以hold[i][j]
表示 i 时刻进行第 j 次买入的最大收益for(int i=0; i<prices.size(); i++){ for(int j=k; j>=1; j--){ rele[j] = max(rele[j], prices[i]-hold[j]); hold[j] = min(hold[j], prices[i]-rele[j-1]); maxProfit=max(maxProfit, rele[j]); } }
-
#198 House Robber
同系列的还有 #213 House Robber II、#337 House Robber III
个人感觉和前面的Best Time to Buy and Sell Stock是同一类型的题目,都是在每个时间步有两种不同的状态,区别在于Stock题目两种状态之间必须相互转移,而Robber这道题状态转移不一定发生在两种状态之间。
这类题目可以推广到多个不同状态。 -
#72 Edit Distance
类似正则匹配的方程,dp[i][j]
表示a串0…i - 1位与b串0…j - 1位的编辑距离for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1]; else dp[i][j] = min(dp[i - 1][j - 1] + 1, min(dp[i][j - 1] + 1, dp[i - 1][j] + 1)); } }
-
#518 Coin Change 2
以dp[i]
表示 i 有多少种组合方法,这里需要注意,因为找零是一个组合问题,要忽略顺序&#