leetcode 刷题日记 week5-8

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)解法,逐行扫描,只保留行信息,从上一行到下一行可以直接替代。
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

博客学习:一个有意思的数学解法,看成向下m-1,向右n-1,组合数m+n-2-----m-1,用公式计算即可。注意分子分母要用double,否则可能越界。

63

和上题一样,若有障碍则将dp值设为0.

64

较容易的dp问题。容易想到用一个O(n)的数组存dp,空间100%,但较费时间。
博客学习:直接在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

相对复杂一些的dp问题。相对容易想到在triangle上直接操作。
博客学习:空间优化:只使用最后一排的空间,从下向上更新。
结构为:
(i-1,j-1),(i-1,j)
(i,j)

131

palindrome是回文。
重要题目:因为要找到所有的情况,一定要遍历。DFS的思路进行遍历。可用dp优化判断是否是回文。
更多解法可看到:https://www.cnblogs.com/grandyang/p/4270008.html

139

学习:数组或者子字符串且求极值的题,常用DP, DP和memo的想法可互换

memo的递归:memo[i] 定义为范围为 [i, n] 的子字符串是否可以拆分,初始化为 -1,表示没有计算过,如果可以拆分,则赋值为1,反之为0
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.html

1186

DP的题目,思路有启发性。和最大和的区别是去除一个元素,即前面和+后面和,用两个dp数组即可。时间O(n),空间O(n)

待学习:使用dp,从不删除和删除一个中选择较大的,时间O(n),空间O(1)。不容易理解。

class Solution {
public:
    int maximumSum(vector& arr) {
        int sum = arr[0], sumWithDeletion = 0, ans = arr[0];
        for(int i = 1; i < arr.size(); ++i){
            ans = max(ans, sumWithDeletion + arr[i]);
            sumWithDeletion = max(sumWithDeletion + arr[i], sum);
            sum = sum < 0 ? 0 : sum;
            sum += arr[i];
            ans = max(ans, sum);
        }
        return ans;
    }
};

easy

53

经典的dp题目。

122

比较直观,记录premin,premax并O(n)遍历即可。
最后注意加上premax-premin

week 6

easy

303

做过

392

最直观的方法遍历即可。
follow up: 进行优化的方法是使用哈希表和二分查找(upper_bound)。

medium

5

回文的题目+1,可以沿用之前的思路用dp,同时记录长度和left即可。
待学习:O(n)的方法马拉车算法https://www.cnblogs.com/grandyang/p/4475985.html
https://www.cnblogs.com/grandyang/p/4464476.html

54

剑指offer原题,用一个辅助函数打印一圈,之后遍历行号即可。要核对好xy坐标

59

304

303的延申,dp[i][j] 表示(0,0)---(i,j) 的矩形和,使用dp
trick:辅助数组,dp的大小多一行多一列,这样可以不用特殊处理第一行第一列的情况(因为dp初始化为0)
相应的行列号要+1

380

因为获取随机数有O(1)的要求,所以要用一个数组来辅助hashmap

413

使用dp数组,从i=2开始。dp[i]表示i为末位有多少个这样的seq. dp[i] = dp[i-1]+1 如果能和前两个组成序列,因为: 止于i-1的可以延申一个到达i位置,此外多出一个(i-2,i-1,i)。如果不能和之前的组成序列,则dp[i] = 0.
可以继续用变量优化dp空间,不构造数组。

516

回文字符串+1, 使用dp, dp[i][j] 表示i--j的字串最大长度,

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]

i遍历从n-1开始-1, j从i+1开始+1,dp[i][i] =1;

718

dp[i][j]表示以A以i为结尾,B以j为结尾时,最长的重复字串的长度。空间满分,时间较低。

if(A[i]==B[j]) dp[i][j] = A[i-1][j-1]+1;
else{dp[i][j] = 0}

更多:https://www.cnblogs.com/grandyang/p/7801533.html

hard

72

非常经典的dp问题,转移方程对应增加,修改,删除三种方式

dp[i][j] = min(min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1]) +1;

更多见:https://www.cnblogs.com/grandyang/p/4344107.html

381

380的改进版本,可以重复以后,哈希表的值用一个set来代替。 其余和之前的相同。remove操作最重要,要注意删除、增加等操作的顺序,否则会报错。(m[val].erase(cur_idx); 一句的位置重要)

week6

2020.4.19-2020.4.26

easy:

67

二进制求和,先从末尾开始相加,最后reverse即可。

70

dp问题,斐波那契数列。

medium:

31

全排列的问题:46 https://www.cnblogs.com/grandyang/p/4358848.html

开始没有理解题意。 permutation是全排列中的排列。
操作方法:从后向前,找到第一个开始变大的数i,并在后面找到从后向前第一个比它大的数j,交换两数,最后reverse(i+1,end)
排列数本应该是从小到大的顺序,i即是从i+1到end的排列数是倒序的,因此之后应该把i处的数字换成一个比它大的数开始排列,然后之后的序列因为排到了最后,变成了原本的倒序,因此reverse一下就成了原始的顺序。
解答:https://www.cnblogs.com/grandyang/p/4428207.html

39

类似题目:

  • 39. Combination Sum 组合之和
  • 113. Path Sum II 二叉树路径之和之二
  • 90. Subsets II 子集合之二
  • 46. Permutations 全排列
  • 47. Permutations II 全排列之二
  • Combinations 组合项

需要另写一个递归函数,这里我们新加入三个变量,start 记录当前的递归到的下标,out 为一个解,res 保存所有已经得到的解,每次调用新的递归函数时,此时的 target 要减去当前数组的的数

可以想到使用递归和DFS,但是顾虑到有重复的情况,因此要加一个参数start,每次遍历从start开始,这样就不会有(3,2,2)和(2,2,3)的情况。

73

参考:https://www.cnblogs.com/grandyang/p/4341590.html
O(m+n)的方法可以想到,即用两个数组存每行每列的情况。
常数空间的方法:用第一行第一列存,同时用两个变量存第一列和第一行的情况。

79

自己解出的DFS方法。

参考:https://www.cnblogs.com/grandyang/p/4332313.html
文中的思路一致,写法更加简洁,但时间和空间复杂度都要高很多。

94

中序遍历:144
前序:
开始想到的数据结构课的计数器,第一次不出栈,入左节点;第二次出栈,入右节点。
值得记住的非递归做法:
如果有左儿子,优先访问,但并不读取,放入stack;
没有了左儿子,从stack顶取出一个(第一次取出最左的一个),然后访问其右儿子,重复上述流程

class Solution {
public:
    vector inorderTraversal(TreeNode* root) {
        vector res;
        stack st;
        TreeNode *p = root;
        while(p||!st.empty()){
            if(p){
                st.push(p);
                p = p->left;
            }
            else{
                p = st.top(); st.pop();
                res.push_back(p->val);
                p = p->right;
            }
        }
        return res;
    }
};

中序遍历把res.push_back放到前半部分即可。
待学习:Morris Traversal 空间复杂度为常量,见:https://www.cnblogs.com/grandyang/p/4297300.html

98

一个易错(自己的解法)是节点不仅要考虑相邻的节点,整个树的左右都要满足条件。 一个巧妙办法是设定一个重载函数,包含最大和最小的参数。 注意: 最大和最小的参数要用LONG_MIN 和 LONG_MAX.

113

相似的思路,DFS的方法,有一个out和res
容易的bug: 因为必须是root到leaf的路径,需要防止终结结点不是叶子节点。
发现:调整函数结构简化了代码,但是耗费的时间变多,可能在递归函数内的开销会更大。

1315

直观方法:遍历,发现所有偶数节点,把孙子节点的值加起来。用了两个辅助函数
学习: DFS代码非常简洁的方法, 直接写一个DFS, 把DFS的调用写在return语句里,传递patrent, grandparent 两个参数。

hard:

45

一开始想了一个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
因为不需要再多跳一次。

97

字符串的子序列或是匹配问题直接就上动态规划 Dynamic Programming,递归可能超时

dp[i][j] 表示s1 0--i s2 0--j 能否匹配s3 的0--(i+j)
dp的大小是m+1, n+1, 多出的第0行第0列表示空字符串的情况。
首先初始化第一行第一列, dp[0][0]=true; 之后匹配条件是前一个为true及该位相等。

之后的矩阵,两种匹配方式是从上面或者左面过来,对用s1多匹配一位或者s2多匹配一位,两种有一种为true即可。注意i,j对应的位置其实是i-1,j-1,以及i+j-1

待学习:更多解法:https://www.cnblogs.com/grandyang/p/4298664.html
含有DFS,BFS的简化

week 8:

2020.4.26-5.2

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

也有递归和迭代两种方法。 最开始想的设计4个节点过于低效。
链表操作多想想递归,迭代方法要画个图。

74

从右上角开始,要变小向左,要变大向下。

82

直观的想法。注意:在发现相同的词,向后推移的时候,要时刻检查是否达到最后一个。

92

想出的较复杂的方法:


image.png

记住开始点start,结束点post, 中间一段进行反转后,和前后拼接。

更简洁的方法:
1 -> 2 -> 3 -> 4 -> 5 -> NULL
1 -> 3 -> 2 -> 4 -> 5 -> NULL
1 -> 4 -> 3 -> 2 -> 5 -> NULL

在反转的过程中,pre一直是1,cur的指针一直在2上(2本身在向后移)
在每一步,都是把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.html

image.png

拓展:如何判断两个单链表是否有交点?先判断两个链表是否有环,如果一个有环一个没环,肯定不相交;如果两个都没有环,判断两个列表的尾部是否相等;如果两个都有环,判断一个链表上的Z点是否在另一个链表上。

两个单链表如何找到第一个相交的节点?求出两个链表的长度L1,L2(如果有环,则将Y点当做尾节点来算),假设L1

143

没有想出成功的解法。
方法:找出链表的后半段,然后倒置插入前半段。倒置的方法是用一个栈。

240

参考:74 从右上角开始
https://www.cnblogs.com/grandyang/p/4323301.html

开始写了一个递归的写法,从右下角开始,但是递归的层数过多,超出时间限制。
和I不同,没有蛇形的关系,因此从左下角或者右上角开始。

328

用pre,cur两个指针。

hard

23

使用分治法,两两合并链表。
更多方法:包括最小堆(结果会稍差一些)
https://www.cnblogs.com/grandyang/p/4606710.html

329

参考:https://www.cnblogs.com/grandyang/p/5148030.html
dp + dfs能够完成,但是时间和空间都较差。

你可能感兴趣的:(leetcode 刷题日记 week5-8)