leetcode算法易错和知识点总结

1 82. 删除排序链表中的重复元素 II

错误原因:不确定要设定几个指针,以及指针的初始化

分析:

1.因为是重复元素都要删除(不是还要留一个)因此第一个元素也可能会删除,所以引入dummyNode和连接指针con

2.因为需要比较一个节点是否与他前面的节点相同,因此需要引入cur 和 curPre。

3.第一个节点就需要进行判读是否删除,因此cur = head, curPre = dummyNode, con = dummyNode.

4.最后con.next = null?怎么理解?把后面没连接的断掉。

2 49. 字母异位词分组

错误原因:思路错了,用的是朴素的O(N2)

分析:内容相同的string,他们的hash值是一样的,因此先对string数组里面的字符排序,然后放入一个

Map<String, List<String>> hm中

最后返回的new ArrayList<List>(hm.values());

Map<String, List<String>> hm = new HashMap<>();
for(String str : strs){
	char[] temp = str.toCharArray();
	Arrays.sort(temp);
	String key = temp.toString();
	List<String> list = hm.getOrDefault(key, new ArrayList<String>());
	list.add(key);
	hm.put(key, list);
}

return new ArrayList<List<String>>(hm.values());
3 15. 三数之和

错误原因: 每轮重置之后,没有重置j为nums.length-1 该死!

4 239. 滑动窗口最大值

错误原因:

for(int i = k; i<nums.length; i++){
    //移除左边的一位    //错误1,这里的i-k我写成了i;
    if(nums[i-k] == q.peekFirst()){
        q.pollFirst();
    }

    //增加右边的一位
    if(q.size() == 0){
        q.offerLast(nums[i]);
    }else if(nums[i] <= q.peekLast()){
        q.offerLast(nums[i]);
    }else if(nums[i] > q.peekLast()){
        while(true){
            q.pollLast();   //错误1,这里的>=我写成了>;
            if(q.size() == 0 || q.peekLast() >= nums[i]){
                break;
            }
        }
        q.offerLast(nums[i]);
    }

    res[i-k+1] = q.peekFirst();
}
5 347. 前 K 个高频元素

错误原因

1.不记得for(int key : hm.keySet()) hm.keySet()hm.values() 分别对应key的set和value的集合。

因此keySet可以用for each来遍历。

2.for(int i : nums) 不规范,foreach容易把i当成是下标

这里要声明,之后使用一定要是for(int num : nums),i专属于下标!!!

6 144. 二叉树的前序遍历

错误原因:

while(!stack.isEmpty() || nd != root)
应该写成
while(!stack.isEmpty() || nd != null)
7 117. 填充每个节点的下一个右侧节点指针 II

错误原因:遍历的顺序错了,要先递归root.right,再递归root.left

否则你会发现root.next还没有连接。。。

8 111. 二叉树的最小深度

错误原因:搞清楚定义

9 101. 对称二叉树

错误原因:比较两个节点相等是比较.val而不是节点本身!!

10 257. 二叉树的所有路径

错误原因:二叉树的回溯跟 排列组合的回溯是有区别的, 以后就像下面这么写吧,是一个比较接近的形式

    public void dfs(TreeNode root){
        if(root == null) return;
        sb.append(root.val);
        if(root.left == null && root.right == null){
            res.add(sb.toString());
            return;
        }

        if(root.left != null){
            dfs(root.left);
            sb.deleteCharAt(sb.length()-1);
        }

        if(root.right != null){
            dfs(root.right);
            sb.deleteCharAt(sb.length()-1);
        }
    }
11 617. 合并二叉树

错误原因:一直以root1为视角,root1为null的时候,必须return root2,否则就一直是在root1上做连接。那些本来差很远的root2的节点也连到了root1的子树上。

    public TreeNode mergeTrees(TreeNode root1, TreeNode root2) {
/*正确
        if(root1 == null) return root2;
        if(root2 == null) return root1;
*/

/*错误
        if(root1 == null && root2 == null) return null;
        if(root1 == null){
            root1 = new TreeNode(root2.val);
            return root1;
        }else if(root2 == null){
            return root1;
        }
*/        
        
        
        root1.val = root1.val + root2.val;
        root1.left = mergeTrees(root1.left, root2.left);
        root1.right = mergeTrees(root1.right, root2.right);
        return root1;
    }
12 501. 二叉搜索树中的众数

错误原因:细节,需要自己把握。

13 236. 二叉树的最近公共祖先

错误原因:

1.因为是从最底层往上return,应该用后序遍历

2.细节上,root.left == q && root.right == q 这样的错误。。。

14 450. 删除二叉搜索树中的节点

错误原因:没思路

如果删除的节点存在左右节点,假设root = 被删除节点,这里有两种写法

/*******************第一种写法************************/
cur = root.right;
然后cur像左遍历找到那个最小的值
巧妙的来了,交换root和cur的值,然后root.right = dfs(root.right, root.val); 真聪明啊

/*******************第二种写法************************/
cur找到那个最小值后
cur.left = root.left;
return root.right;
相当于是把root的right给return上去了呢,,妙啊

==15 491. 递增子序列==

有重复元素的全排列问题,和有重复元素的递增子序列问题,使用used数组都是为了防止在同一层pick相等的元素。

但是:

  • 全排列是有序的,重复的元素会相邻出现,我们只需要判断nums[i]和nums[i-1] 并判断used[i-1] == 0就知道是同一层使用了重复值

​ 而递增子序列是无序的,因此我们必须把元素的值nums[i]而不是下标i 当成下标,准备一个足够大的used[]数组。

  • 我们又要保证,这个元素只是在同一层无法再使用,怎么办呢?

    我们知道for循环代表这一层,因此在for() 之前使得used 置为零,在for循环体最后才使得used[nums[i]] = 1;

    这样就会使得return之后,重新回到for循环时,这个新加的值变成了使用过的值。

    for()循环跑完后,返回到上一层时,我们的used又是新的全部是0的值,完美。

            used = new int[201];      //******在for循环整体跑完后,return到上一层,used[]又新为0
            for(int i = start; i<nums.length; i++){
                if(list.size()>0 && nums[i] < list.get(list.size()-1)) continue;
                if(used[nums[i] + 100] == 1) continue;
                list.add(nums[i]);
                dfs(nums, used, i+1);
                list.remove(list.size()-1);
                used[nums[i] + 100] = 1;   //*******这个点return之后,前往同一层的下一个节点,此时将used[]置为1
            }
    
16 746. 使用最小花费爬楼梯

错误原因:注意这里台阶数是要比cost数多1的哦,因为最后一级台阶不需要花费。

    
    //dp[i] 表示登上第i级台阶的最小花费。
    public int minCostClimbingStairs(int[] cost) {
        int n = cost.length;
        int[] dp = new int[n + 1];
        dp[0] = dp[1] = 0;
        for (int i = 2; i <= n; i++) {
            dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        }
        return dp[n];
    }
17 63. 不同路径 II

错误原因:dp[0] [0] 并不是直接等于1,要看00这个点有没有障碍物,初始值错了。。

18 343. 整数拆分

错误原因:dp[i]表示i这个数至少切成两段的最大值,因此有切两段,切n段。。取最大值,这个切n段用 j*dp[i-j]来表示。

        for(int i = 3; i<=n; i++){
            for(int j = 1; j < i; j++){  //j代表第一刀切的位置
                dp[i] = Math.max(dp[i], Math.max(j*(i-j), j*dp[i-j]));
            }                                    //切两刀     切三刀
        }

19 96. 不同的二叉搜索树

其实这道题我做对了,但是跟官方思路不太一样

我的思路:新增的节点最大,他要么做前面结构的右子树,要么前面结构做他的左子树,这就是dp[i] = dp[i-1] + dp[i-1];

​ 另外,这个最大的节点还可以做中间节点,其他节点分别在他上面和下面,于是temp += dp[j]dp[i-1-j];

​ 最终得到 dp[i] = 2*dp[i-1] + temp;

官方思路:每个节点分别做根节点,他的左右子树的类是dp[j]*dp[i-1-j] ;j从1到i,即从第一个节点开始到最新节点分别做根节点。

20 01背包问题

https://programmercarl.com/%E8%83%8C%E5%8C%85%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%8001%E8%83%8C%E5%8C%85-1.html

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qlXxnql8-1681708954746)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211224142459150.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9FLTkbc0-1681708954747)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20211224142536975.png)]

①二维数组的模式

  • dp[i] [j]表示从0-i物品中任意取,背包容量为j的背包最多能装下的物品价值。
  • 物品i和容量j的顺序可以互换
for(int i = 1; i<x; i++){
	for(int j = 1; j<=y; j++){
		if(j < weight[i]){
            dp[i][j] = dp[i-1][j]; 
		}else{
            dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]);
        }
	}

}

②一维数组的模式

  • dp[i] [j]表示从0-i物品中任意取,背包容量为j的背包最多能装下的物品价值。
  • 必须先物品后容量,且容量是倒序,否则可能同一个物品被拿多次。dp[2] = dp[2-1] + value[0]; 此时dp[1]表示拿了物品0,现在又拿一次。而用dp[2] = dp[2-1](值为0) + value[0],则是这个背包第一次拿物品0.
for(int i = 0; i<x; i++){
    for(int j = y; j>=0; j--){
		if(j < weight[i]){
            dp[j] = dp[j];
        }else{
            dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i]);
        }
    }
}

//可以再修饰下,让j的容量始终大于weight[i],就不会有if else
for(int i = 0; i<x; i++){
    for(int j = y; j>=weight[i]; j--){
            dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i]);
        }
    }
}
21 1049. 最后一块石头的重量 II

错误原因: return (sum - dp[sum/2]) - dp[sum/2];

22 494. 目标和

错误原因:题目问的是有几种方法,因此dp[j] 也应表示为方法数。

解题思路: 凡是问几种方法的,都是dp[j] += dp[j-nums[i]];

dp[0] = 1;
dp[j] += dp[j-nums[i]];
//而且size可能是负数!
23 474. 一和零

错误原因:没思路

解题思路:翻译下,装0的背包容量m, 装1的背包n,请问最多能装的物品数量。(没有价值呢还)

24 完全背包
  • 由于物品可以拿多次,因此j的顺序是正的。
  • 而且物品和背包的顺序可以颠倒。
for(int i = 0; i<x; i++){
    for(int j = weight[i]; j<= y; j++){
            dp[j] = Math.max(dp[j], dp[j-weight[i]] + value[i]);
        }
    }
}
25 377. 组合总和 Ⅳ

错误原因:没有搞清楚是先循环物品还是先循环背包

由于{1,2}和{2,1}都有效,因此先循环背包再循环物品,否则2永远在1后面,不会有{2,1}的情况。

for(int j = 1; j<=target; j++){
    for(int i = 0; i<nums.length; i++){
        if(j < nums[i]){
            dp[j] = dp[j];
        }else if(j >= nums[i]){
            dp[j] += dp[j-nums[i]];
        }
    }
}
26 322. 零钱兑换

错误原因:

1.初始化 dp[0] = 0,而其他值都应该初始化为一个大于最大值的值amount + 1,因为我们将使用Math.min(xx);

2.递推关系 dp[j] = Math.min(dp[j], dp[j-coins[i]] + 1);

27 121. 买卖股票的最佳时机

错误原因:dp[i-1] [0] 不等于0!

//dp[i][0] 代表不持有
//dp[i][1] 代表持有

dp[0][0] = 0;
dp[0][1] = -prices[0];
for(int i = 1; i<prices.length; i++){
    //今天没有,代表可能是之前就没有,或者之前有,今天卖了。
    //之前没有,可能是已经交易了一次,因此dp[i-1][0]不能写成0
    dp[i][0] = Math.max(dp[i-1][0], dp[i-1][1] + prices[i]);
    //今天持有,代表可能是之前就持有,或者之前没有,今天买了。
    //之前没有,因为今天要买,所以之前没有代表没有交易,dp[i-1][0] = 0;
    dp[i][1] = Math.max(dp[i-1][1],  - prices[i]);
}
28 123. 买卖股票的最佳时机 III

错误原因: 初始化不完全,所有dp[0] [j] [1] 都要初始化为-prices[0]才行。

        dp[0][0][1] = -prices[0];
        dp[0][1][1] = -prices[0];
        dp[0][2][1] = -prices[0];
29 300. 最长递增子序列

错误原因: 没思路

总结:

  • 像最长递增子序列,最长递增的连续子序列,最大子序列和。属于单条序列。
  1. 设置dp数组长度为dp[len];

  2. dp[i]表示以nums[i]为结尾。

  • 像最长公共序列,编辑距离,最长回文子序列,最长回文连续子序列,最长公共连续序列。这样是有两条序列的:
  1. 设置dp数组长度为dp[len+1](除了回文系列是dp[len],因为回文dp[i] [j] = dp[i+1] [j-1] xx 与dp[i-1]无关)
  2. 连续子序列dp[i]仍然表示以nums[i]为结尾。
  3. 非连续子序列dp[i]表示前i个数据。
for(int i = 1; i<nums.length; i++){
    for(int j = 0; j<i; j++){ //对于num[i]来说,前面nums[0]-nums[i-1]都是可以利用的对象。
        if(nums[i] > nums[j]){
            dp[i] = Math.max(dp[i], dp[j]+1);
        }else{
            dp[i] = Math.max(dp[i], 1);
        }
    }
}
30 647. 回文子串的个数

dp[i] [j]表示以nums[i]和nums[j]结尾的子串是否是回文。

5. 最长回文子串

可以把序列翻转,然后求两条序列的最长公共连续序列(最长公共子串)??

不一定可以哦

https://leetcode-cn.com/problems/longest-palindromic-substring/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-bao-gu/

516. 最长回文子序列

可以把序列翻转,然后求两条序列的最长公共序列。

31 将字符串里面的字符按照出现的频率降序排列
由于要比较每个字符的频率
建立一个字符串数组,分隔开各个字符串,然后按照字符串的长度进行排序。
Arrays.sort(strs, (o1,o2)->o2.length()-o1.length());
32 用rand(7)实现rand(10)
(rand(7)-1)*7 + rand(7) 会等概率生成1-49的数
33 402. 移掉 K 位数字

错误原因:没考虑到好多细节:

class Solution {
    public String removeKdigits(String num, int k) {
        //贪心算法,让数保持递增他就是最小的
        //删除递减序列的第一个数字
        if(k >= num.length()) return "0";
        StringBuilder sb = new StringBuilder(num);
        int count = 0;
        for(int i = 0; i<sb.length()-1 && count < k; i++){
            if(sb.charAt(i) > sb.charAt(i+1)){
                sb.deleteCharAt(i);
                if(i == 0){
                    i--;
                }else{
                    i = i-2;  //i应该-2看i的左边是否跟他也形成了降序!
                }
                count++;
            }
        }

        //如果最后没有用完,那么说明是一个递增的数(至少不递减),只需要舍弃末尾的数就行了。
        System.out.println(count);
        if(count != k){
            for(int i = 0; i<k-count; i++){
                sb.deleteCharAt(sb.length()-1);
            }
        }

        //最后得到的数可能以0为开头,也可能只剩下0
        while(sb.charAt(0) == '0' && sb.length() > 1){
            sb.deleteCharAt(0);
        }
        return sb.toString();
    }
}
33 最大正方形

https://leetcode-cn.com/problems/maximal-square/solution/zui-da-zheng-fang-xing-by-leetcode-solution/

错误原因:没思路

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

dp[i] 表示以i为右下角的正方形的最大边长。

class Solution {
    public int maximalSquare(char[][] matrix) {
        int maxSide = 0;
        if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
            return maxSide;
        }
        int rows = matrix.length, columns = matrix[0].length;
        int[][] dp = new int[rows][columns];
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < columns; j++) {
                if (matrix[i][j] == '1') {
                    if (i == 0 || j == 0) {
                        dp[i][j] = 1;
                    } else {
                        dp[i][j] = Math.min(Math.min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1;
                    }
                    maxSide = Math.max(maxSide, dp[i][j]);
                }
            }
        }
        int maxSquare = maxSide * maxSide;
        return maxSquare;
    }
}
34 126. 单词接龙 II
35 97. 交错字符串

翻译成:s3的前i+j个字符能否由s1的前i个字符和s2的前j个字符组成?

class Solution {
    public boolean isInterleave(String s1, String s2, String s3) {
        if(s3.length() != s1.length() + s2.length()){
            return false;
        }

        boolean[][] dp = new boolean[s1.length()+1][s2.length()+1];
        dp[0][0] = true;

        for(int i = 1; i<=s1.length(); i++){
            dp[i][0] = dp[i-1][0] && (s3.charAt(i-1) == s1.charAt(i-1));
        }

        for(int i = 1; i<=s2.length(); i++){
            dp[0][i] = dp[0][i-1] && (s3.charAt(i-1) == s2.charAt(i-1));
        }

        for(int i = 1; i<=s1.length(); i++){
            for(int j = 1; j<=s2.length(); j++){
                dp[i][j] = ((dp[i-1][j] && s3.charAt(i+j-1) == s1.charAt(i-1)) || (dp[i][j-1] && s3.charAt(i+j-1) == s2.charAt(j-1)));
            }
        }

        return dp[s1.length()][s2.length()];
    }
}
36.124. 二叉树中的最大路径和
对于树的题目,有统一的思考方法,那就是站在树(子树)的顶端根节点root思考。

那么对于此题,我们思考如下问题:如果当前处在root节点,左右节点应该告诉我们什么信息才能得到答案?

根据题中对路径的定义,对于此题我们来回答以上问题。当我们遍历到树中某个节点时,我希望左子节点告诉我,在左子树中,以左子节点为开始(端点)的路径和最大为多少,同理我也希望右子节点告诉我类似的信息。

如果有了以上信息,再来思考最后一个问题:有了这个信息如何得到答案?

显然,对于当前节点有四个选择:

我自己就是一条路径
只跟左子节点合并成一条路径
只跟右子节点合并成一条路径
以自己为桥梁,跟左、右子节点合并成一条路径
需要注意的是,我们在递归求解的时候,第四种情况是不能作为递归的返回值的,因为它不符合我们对递归所期望返回值的定义(因为此时该子节点并不是拥有最大路径和路径的起点(端点)),但它也是一个可能的解,所以我们用一个全局变量记录上面四种值的最大值,递归结束后,该变量就是答案。
    
class Solution {
    int pathSum = Integer.MIN_VALUE;

    public int maxPathSum(TreeNode root) {
        dfs(root);
        return pathSum;
    }

    // dfs 返回以该节点为端点的最大路径和
    public int dfs(TreeNode node) {
        if (node == null) return 0;
        int left = dfs(node.left);
        int right = dfs(node.right);
        // 当前节点有四个选择:
        // 1)独立成线,直接返回自己的值 
        // 2)跟左子节点合成一条路径 
        // 3)跟右子节点合成一条路径
        int ret = Math.max(node.val, node.val + Math.max(left, right));
        // 4)以自己为桥梁,跟左、右子节点合并成一条路径
        pathSum = Math.max(pathSum, Math.max(ret, node.val + left + right));
        return ret;
    }
}

37 295. 数据流的中位数

错误原因:最大堆和最小堆的size()没有控制好。

38 剑指 Offer 39. 数组中出现次数超过一半的数字

int candidate = nums[0];
int vote = 0;

for(int i = 0; i<nums.length; i++){
    if(vote <= 0){  //这个判断一定要放到最前面,不然会出错。
        candidate = nums[i];
    }
    if(nums[i] != candidate){
        vote--;
    }else{
        vote++;
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值