week 5
2020.4.5-2020.4.12
hard:
84:
在最后push高度为0,便于处理最后一个。
递增栈:可能出现最大面积的位置都在当高度开始下降的时候,若在上升阶段,向后可以更高。
维持一个递增栈,一旦遇到了下降,就开始全部出栈,逐一算出可能的最大面积。之后继续入一个新的栈。
85
此题和84有关,相当于在每一行构造一个立柱,立柱的高度为累加值。
同样需要扩充一个位置以应对最后一个出现极值的情况。
还有另外的解法见:https://www.cnblogs.com/grandyang/p/4322667.html
132
基于131的题目,使用两个dp,第一个用来保存是否是回文数组,第二个是dp[i]:0---i的最小切割的数量,dp[i]= min(dp[i],dp[j] +1) 如果0--j为回问,且j---i为回文,和第一个dp数组的判断条件相同,因此结构是一样的。
更多的解法见:https://www.cnblogs.com/grandyang/p/4271456.html
medium
62
一开始想到的是从右下角斜向扫描,dp[i,j] = dp[i,j+1] + dp[i+1,j] (i 博客学习:有一个更巧妙优化的空间O(N)解法,逐行扫描,只保留行信息,从上一行到下一行可以直接替代。 博客学习:一个有意思的数学解法,看成向下m-1,向右n-1,组合数m+n-2-----m-1,用公式计算即可。注意分子分母要用double,否则可能越界。 和上题一样,若有障碍则将dp值设为0. 较容易的dp问题。容易想到用一个O(n)的数组存dp,空间100%,但较费时间。 细节较为复杂,技巧性强 相对复杂一些的dp问题。相对容易想到在triangle上直接操作。 palindrome是回文。 学习:数组或者子字符串且求极值的题,常用DP, DP和memo的想法可互换 memo的递归:memo[i] 定义为范围为 [i, n] 的子字符串是否可以拆分,初始化为 -1,表示没有计算过,如果可以拆分,则赋值为1,反之为0 DP的题目,思路有启发性。和最大和的区别是去除一个元素,即前面和+后面和,用两个dp数组即可。时间O(n),空间O(n) 待学习:使用dp,从不删除和删除一个中选择较大的,时间O(n),空间O(1)。不容易理解。 经典的dp题目。 比较直观,记录premin,premax并O(n)遍历即可。 做过 最直观的方法遍历即可。 回文的题目+1,可以沿用之前的思路用dp,同时记录长度和left即可。 剑指offer原题,用一个辅助函数打印一圈,之后遍历行号即可。要核对好xy坐标 303的延申,dp[i][j] 表示(0,0)---(i,j) 的矩形和,使用dp 因为获取随机数有O(1)的要求,所以要用一个数组来辅助hashmap 使用dp数组,从i=2开始。dp[i]表示i为末位有多少个这样的seq. dp[i] = dp[i-1]+1 如果能和前两个组成序列,因为: 止于i-1的可以延申一个到达i位置,此外多出一个(i-2,i-1,i)。如果不能和之前的组成序列,则dp[i] = 0. 回文字符串+1, 使用dp, dp[i][j] 表示i--j的字串最大长度, i遍历从n-1开始-1, j从i+1开始+1,dp[i][i] =1; dp[i][j]表示以A以i为结尾,B以j为结尾时,最长的重复字串的长度。空间满分,时间较低。 更多:https://www.cnblogs.com/grandyang/p/7801533.html 非常经典的dp问题,转移方程对应增加,修改,删除三种方式 更多见:https://www.cnblogs.com/grandyang/p/4344107.html 380的改进版本,可以重复以后,哈希表的值用一个set来代替。 其余和之前的相同。remove操作最重要,要注意删除、增加等操作的顺序,否则会报错。(m[val].erase(cur_idx); 一句的位置重要) 2020.4.19-2020.4.26 二进制求和,先从末尾开始相加,最后reverse即可。 dp问题,斐波那契数列。 全排列的问题:46 https://www.cnblogs.com/grandyang/p/4358848.html 开始没有理解题意。 permutation是全排列中的排列。 类似题目: 需要另写一个递归函数,这里我们新加入三个变量,start 记录当前的递归到的下标,out 为一个解,res 保存所有已经得到的解,每次调用新的递归函数时,此时的 target 要减去当前数组的的数 可以想到使用递归和DFS,但是顾虑到有重复的情况,因此要加一个参数start,每次遍历从start开始,这样就不会有(3,2,2)和(2,2,3)的情况。 参考:https://www.cnblogs.com/grandyang/p/4341590.html 自己解出的DFS方法。 参考:https://www.cnblogs.com/grandyang/p/4332313.html 中序遍历:144 中序遍历把res.push_back放到前半部分即可。 一个易错(自己的解法)是节点不仅要考虑相邻的节点,整个树的左右都要满足条件。 一个巧妙办法是设定一个重载函数,包含最大和最小的参数。 注意: 最大和最小的参数要用LONG_MIN 和 LONG_MAX. 相似的思路,DFS的方法,有一个out和res 直观方法:遍历,发现所有偶数节点,把孙子节点的值加起来。用了两个辅助函数 一开始想了一个dp的O(n2)方法,结果超时。dp[i] = dp[j] + 1 if nums[j] +j >=i else dp[j] + i-j; 之后用一个O(n)的类似贪婪的算法,记录一个cur是下一次跳到的最远的一格, last是本次能跳到的最远一格,若已经走到了last, 则应该再跳一次, res++, last更新为cur。注意, i的循环范围为0--nums.size()-2 字符串的子序列或是匹配问题直接就上动态规划 Dynamic Programming,递归可能超时 dp[i][j] 表示s1 0--i s2 0--j 能否匹配s3 的0--(i+j) 之后的矩阵,两种匹配方式是从上面或者左面过来,对用s1多匹配一位或者s2多匹配一位,两种有一种为true即可。注意i,j对应的位置其实是i-1,j-1,以及i+j-1 待学习:更多解法:https://www.cnblogs.com/grandyang/p/4298664.html 2020.4.26-5.2 经典:链表有环 经典反转链表 迭代: 也有递归和迭代两种方法。 最开始想的设计4个节点过于低效。 从右上角开始,要变小向左,要变大向下。 直观的想法。注意:在发现相同的词,向后推移的时候,要时刻检查是否达到最后一个。 想出的较复杂的方法: 记住开始点start,结束点post, 中间一段进行反转后,和前后拼接。 更简洁的方法: 在反转的过程中,pre一直是1,cur的指针一直在2上(2本身在向后移) 快慢指针 拓展:如何判断两个单链表是否有交点?先判断两个链表是否有环,如果一个有环一个没环,肯定不相交;如果两个都没有环,判断两个列表的尾部是否相等;如果两个都有环,判断一个链表上的Z点是否在另一个链表上。 两个单链表如何找到第一个相交的节点?求出两个链表的长度L1,L2(如果有环,则将Y点当做尾节点来算),假设L1 没有想出成功的解法。 参考:74 从右上角开始 开始写了一个递归的写法,从右下角开始,但是递归的层数过多,超出时间限制。 用pre,cur两个指针。 使用分治法,两两合并链表。 参考:https://www.cnblogs.com/grandyang/p/5148030.html
res[i][j]=res[i-1][j]+res[i][j-1]public int uniquePaths(int m, int n) {
if(m<=0 || n<=0)
return 0;
int[] res = new int[n];
res[0] = 1;
for(int i=0;i
63
64
博客学习:直接在grid上累加。当i=0,j=0, continue; 当i=0,直接从左边加;当j=0,直接从上面加;否则找个最小的加。91
思路容易想到使用动态规划,但需要一些技巧。
构造dp[n+1]要为size+1,如”2020“ 如果2看作单独字符,则0无法配对。因此”0“较为特殊,一定要与前一个配成“1”--“26”间的字符。若i-1位为0,这样如果到了i位时发现前两位不能组成字符,则dp[i]=0,且之后的均会为0,因为到了i+1位,前两位仍不能组成字符,则dp[i+1]=0,此后由递归便均为0.
多构造1位长度的原因是要看前两位,到了n+1位也可以看到最后两位能否组成字符。如果看前一位和本位置,如"1110",则“11”是可以的,但最后一个0必须要和前面的1配合出现。120
博客学习:空间优化:只使用最后一排的空间,从下向上更新。
结构为:
(i-1,j-1),(i-1,j)
(i,j)131
重要题目:因为要找到所有的情况,一定要遍历。DFS的思路进行遍历。可用dp优化判断是否是回文。
更多解法可看到:https://www.cnblogs.com/grandyang/p/4270008.html139
memo是向后找,DFS是向前找。
DFS:使用一个O(n^2)的遍历, dp[i] 表示0--i是否能分割,用j分割0---i的字串,若dp[j] 且j----i的字符串在字典中,dp[i]为true。
注意:dp的长度为s.size()+1,可以处理空字符串的情况;i的遍历范围是0----dp.size();dp[0]初始化为1;因此子字符串的长度为i-j而不是i-j+1,因为i其实比实际位置多1。
BFS:和递归的方法类似。
解法见:https://www.cnblogs.com/grandyang/p/4257740.html1186
class Solution {
public:
int maximumSum(vector
easy
53
122
最后注意加上premax-preminweek 6
easy
303
392
follow up: 进行优化的方法是使用哈希表和二分查找(upper_bound)。medium
5
待学习:O(n)的方法马拉车算法https://www.cnblogs.com/grandyang/p/4475985.html
https://www.cnblogs.com/grandyang/p/4464476.html54
59
304
trick:辅助数组,dp的大小多一行多一列,这样可以不用特殊处理第一行第一列的情况(因为dp初始化为0)
相应的行列号要+1380
413
可以继续用变量优化dp空间,不构造数组。516
if (s[i]==s[j]) dp[i][j] = dp[i+1][j-1];
else dp[i][j] = max(dp[i+1][j],dp[i,j-1]
718
if(A[i]==B[j]) dp[i][j] = A[i-1][j-1]+1;
else{dp[i][j] = 0}
hard
72
dp[i][j] = min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1]) +1;
381
week6
easy:
67
70
medium:
31
操作方法:从后向前,找到第一个开始变大的数i,并在后面找到从后向前第一个比它大的数j,交换两数,最后reverse(i+1,end)
排列数本应该是从小到大的顺序,i即是从i+1到end的排列数是倒序的,因此之后应该把i处的数字换成一个比它大的数开始排列,然后之后的序列因为排到了最后,变成了原本的倒序,因此reverse一下就成了原始的顺序。
解答:https://www.cnblogs.com/grandyang/p/4428207.html39
73
O(m+n)的方法可以想到,即用两个数组存每行每列的情况。
常数空间的方法:用第一行第一列存,同时用两个变量存第一列和第一行的情况。79
文中的思路一致,写法更加简洁,但时间和空间复杂度都要高很多。94
前序:
开始想到的数据结构课的计数器,第一次不出栈,入左节点;第二次出栈,入右节点。
值得记住的非递归做法:
如果有左儿子,优先访问,但并不读取,放入stack;
没有了左儿子,从stack顶取出一个(第一次取出最左的一个),然后访问其右儿子,重复上述流程class Solution {
public:
vector
待学习:Morris Traversal 空间复杂度为常量,见:https://www.cnblogs.com/grandyang/p/4297300.html98
113
容易的bug: 因为必须是root到leaf的路径,需要防止终结结点不是叶子节点。
发现:调整函数结构简化了代码,但是耗费的时间变多,可能在递归函数内的开销会更大。1315
学习: DFS代码非常简洁的方法, 直接写一个DFS, 把DFS的调用写在return语句里,传递patrent, grandparent 两个参数。hard:
45
因为不需要再多跳一次。97
dp的大小是m+1, n+1, 多出的第0行第0列表示空字符串的情况。
首先初始化第一行第一列, dp[0][0]=true; 之后匹配条件是前一个为true及该位相等。
含有DFS,BFS的简化week 8:
easy
141
快慢指针,若追上了说明有环206
有递归和迭代两种解法
递归:
递归的方法是对每段分成head和剩下的,反转剩下的,将head放在尾部,返回rest。
注意要把head-》next设为null,否则会形成环。
边界条件要注意至少要有head和第二个节点second。class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head==NULL ||head->next == NULL){
return head;
}
ListNode *second = head->next;
head->next = NULL;
ListNode *rest = reverseList(second);
second->next = head;
return rest;
}
};
pre,cur, next三个节点在一次往前走,颠倒pre,cur之间的指针方向。
迭代终止时,cur指向null,pre指向的是最后一个(即第一个,返回)class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *pre = NULL, *cur = head;
while(cur!=NULL){
ListNode *next = cur->next;
cur->next = pre;
pre = cur;
cur = next;
}
return pre;
}
};
medium
24
链表操作多想想递归,迭代方法要画个图。74
82
92
1 -> 2 -> 3 -> 4 -> 5 -> NULL
1 -> 3 -> 2
-> 4 -> 5 -> NULL
1 -> 4 -> 3 -> 2
-> 5 -> NULL
在每一步,都是把cur的面移到pre的后面(反转串的最前面) ListNode *reverseBetween(ListNode *head, int m, int n) {
ListNode *dummy = new ListNode(-1), *pre = dummy;
dummy->next = head;
for (int i = 0; i < m - 1; ++i) pre = pre->next;
ListNode *cur = pre->next;
for (int i = m; i < n; ++i) {
ListNode *t = cur->next;
cur->next = t->next;//cur和后面相连结
t->next = pre->next;//post接管pre的下家
pre->next = t;//post和pre连接
}
return dummy->next;
}
142
先追上以后,慢指针再从head开始,再次相遇时为环的入口。
更多解答见:https://www.cnblogs.com/hiddenfox/p/3408931.html143
方法:找出链表的后半段,然后倒置插入前半段。倒置的方法是用一个栈。240
https://www.cnblogs.com/grandyang/p/4323301.html
和I不同,没有蛇形的关系,因此从左下角或者右上角开始。328
hard
23
更多方法:包括最小堆(结果会稍差一些)
https://www.cnblogs.com/grandyang/p/4606710.html329
dp + dfs能够完成,但是时间和空间都较差。