学习更新2021年11月

2021-11-04

有的时候真的是坚持做一件事非常难,再而衰,三而竭。

L75. 颜色分类

L75. 颜色分类
双指针解法,最开始写了一个双指针解法,发现除了点儿问题。没考虑到二次交换的问题

//[1,2,0]
    public void sortColors(int[] nums) {
        //0的指针
        int z = 0;
        //2的指针
        int t = nums.length - 1;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == 0) {

                //而前面0后面必定跟的是1,比如0,0,1,2,0
                // 当前i如果指向2,则2会交换到后面去,所以当前位置不可能出现交换后
                // 当前元素只有是0的时候会和前面的1交换,此时因为只可能是1,所以不存在二次交换的问题

                swap(nums, z++, i);
            } else if (nums[i] == 2 && i < t) {
                swap(nums, t--, i);
                //因为新换过来的数可能是0,那么久还可能再交换一次
                i--;
            }
        }
    }

    public void swap(int[] nums, int i, int j) {
        if (i == j) {
            return;
        }
        nums[i] = nums[i] ^ nums[j];
        nums[j] = nums[i] ^ nums[j];
        nums[i] = nums[i] ^ nums[j];
    }

2021-11-05

今天刷了两道简单题,不过都挺有意思的

543. 二叉树的直径

P543. 二叉树的直径

第一次见到这道题的时候,觉得很复杂,没啥思路,今天认真想了一下,你会发现要想使得路径最长,一定是一个点为基准,分别向左子树方向和右子树方向走出的最长路径,再加起来。因为dfs只能递归高度给上层调用,那么就还需要另外一个遍历记录最大路径。如果是java支持多返回值,或许就不用全局变量来记录最大路径的值了。

class Solution {
    int max = 0;
    public int diameterOfBinaryTree(TreeNode root) {
        diameterOfBinaryTreeDepth(root);
        return max;
    }
     public int diameterOfBinaryTreeDepth(TreeNode root) {
        if (root == null ) {
            return 0;
        }
        int r = diameterOfBinaryTreeDepth(root.right);
        int l = diameterOfBinaryTreeDepth(root.left);
        max = Math.max(max, r+l);
        return Math.max(l, r) + 1;
    }
}

283. 移动零

283. 移动零
自己想的最直接的方法肯定是把0直接往尾部较换,但因为其他元素得保持位置,所以想了半天还是没相处指针的解法。该题目明确说了不能复制数组,后来实在没找,想了一个map,其实还是复制数组,这个方法的原始思路就是,你能明确的知道最终每个元素的索引都是什么位置了,但完全没用,得开一个新数组来赋值,或者就是map了。算是不太满足要求。

class Solution {
    public void moveZeroes(int[] nums) {
        //int index[] = new int[nums.length];
        Map<Integer,Integer> map= new HashMap<>();

        //普通位置的偏移量
        int flow = 0;
        //高位0的占位偏移
        int t = nums.length - 1;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] == 0) {
                flow++;
                map.put(t--,nums[i]);
            } else {
                map.put(i - flow,nums[i]);
            }
        }

        for (int i = 0; i < nums.length; i++) {
            nums[i]= map.get(i);
        }

    }
}

看了其他网友得解。思路是:把不是0的数字往前换,换第一个0,如果前面没有0都可以不换。h指针记录第一个0,但如果第一个不是0,可以不换,也可以换,但h指针只要遇到不是指针就往后移动

class Solution {
    public void moveZeroes(int[] nums) {

        //h需要通第一个元素较换的非0元素
        int h = 0;
        for (int i = 0; i < nums.length; i++) {
            if (nums[i] != 0) {
                swap(nums, i, h);
                h++;
            }
        }
    }

    public void swap(int[] nums, int i, int j) {
        if (i == j) {
            return;
        }
        nums[i] = nums[i] ^ nums[j];
        nums[j] = nums[i] ^ nums[j];
        nums[i] = nums[i] ^ nums[j];
    }
}

上面这个还有个技巧,因为总是和0较换,所以可以直接把所有不为0的元素往前移动,最后补充后面的0就可以了。这里要记录需要补多少0,较换完所有的元素h指针指向第一个要赋值为0的位置

public void moveZeroes(int[] nums) {
        int h = 0;
        int i = 0;
        while (i < nums.length) {
            if (nums[i] != 0) {
                nums[h++] = nums[i];
            }
            i++;
        }
        for(;h<nums.length;h++){
            nums[h]=0;
        }
    }

2021-11-05

1218. 最长定差子序列

1218. 最长定差子序列

做过最长递增子序列的同学肯定想都不想就写出了下面的解法。

class Solution {
    public int longestSubsequence(int[] arr, int difference) {
        if (arr == null) {
            return 0;
        }
        int dp[] = new int[arr.length];
        int max = 1;
        for (int i = 0; i < arr.length; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (arr[i] - arr[j] == difference) {
                    dp[i] = Math.max(dp[j] + 1, dp[i]);
                }
            }
            max = Math.max(dp[i], max);
        }
        return max;
    }
}

提交之后,用例超时,这里注意到整个时间复杂度还是O(n2),数据量大,题目下方还有数据规模说明。在没有想到其他方法的情况下我用优先级队列,来做中间这个循环的优化,这样不用比较所有的前面的元素了

class Solution {
   public int longestSubsequence(int[] arr, int difference) {
        if (arr == null) {
            return 0;
        }
        PriorityQueue<Integer[]> queue = new PriorityQueue<Integer[]>((o1, o2) -> o2[1] - o1[1]);
        int dp[] = new int[arr.length];
        int max = 1;
        for (int i = 0; i < arr.length; i++) {
            dp[i] = 1;
            PriorityQueue<Integer[]> cp = new PriorityQueue<Integer[]>(queue);
            while (!cp.isEmpty()) {
                Integer[] e = cp.poll();
                int j = e[0];
                if (arr[i] - arr[j] == difference) {
                    dp[i] = Math.max(dp[j] + 1, dp[i]);
                }
            }
            queue.add(new Integer[]{i, dp[i]});
            max = Math.max(dp[i], max);
        }
        return max;
    }
}

完全没用。考虑到这里因为有的用例基本上没有连续等差子序列,这种优先级排序基本没多大的优化效果。最后看了一下map的解法.

我是之前从dp数组来的,这题感觉固定思维了,开始写了个连续递增序列的思路超时了,就是没想到这里等差,还能运用和两束之和的思想。两数之和,两数之差,等差,都能map降维

class Solution {
    public int longestSubsequence(int[] arr, int difference) {
        Map<Integer, Integer> dpm = new HashMap<>();
        //引入dp数组,只为更贴近之前做的dp,方便理解一些
        int dp[] = new int[arr.length];
        int max = 1;
        for (int i = 0; i < arr.length; i++) {
            int v = arr[i];
            if (dpm.containsKey(v)) {
                dp[i] = dpm.get(v);
            } else {
                dp[i] = 1;
            }
            dpm.put(v + difference, dp[i] + 1);
            max = Math.max(dp[i], max);
        }
        return max;
    }
}

2021-11-05

最大子矩阵

最大子矩阵

public class Solution {
    /**
     * @param matrix: the given matrix
     * @return: the largest possible sum
     */
    public int maxSubmatrix(int[][] matrix) {
        // write your code here
        if(matrix==null||matrix.length==0|| matrix[0].length==0)
            return 0;

        int h = matrix.length;
        int w = matrix[0].length;
        int[][] m = new int[h][w+1];

        for (int i = 0; i < h; i++) {
            for (int j = 1; j <=w; j++) {
                m[i][j] = m[i][j - 1] + matrix[i][j-1];
            }
        }


        int max = 0;
        for (int f = 0; f < h; f++) {
            for (int l = f+1; l <=w; l++) {
                int childMax = 0;
                for (int i = 0; i < h; i++) {
                    int x = m[i][l] - m[i][f];
                    if(x+childMax<=0){
                        childMax=0;
                    }else{
                        childMax= childMax+x;
                        max= Math.max(childMax,max);
                    }
                }
            }
        }
        //System.out.println(max);
        return max;
    }
}

这题是一本通中的一道动态规划,咋看这个题的题解是没啥dp的样子,但用到了前缀和,前缀和是dp的一个知识点。这题大的结构上还用到了贪心策略。这个解法有个小问题,就是如果所有元素都是负数,这个答案是有问题的,还需要调整一下。

2021-11-17

剑指 Offer II 052. 展平二叉搜索树

剑指 Offer II 052. 展平二叉搜索树

class Solution {
   TreeNode h = new TreeNode(-1);
    TreeNode cur =h;

    public TreeNode increasingBST(TreeNode root) {
        dfs(root);
        cur.left=null;
        return h.right;
    }
    public void dfs(TreeNode root) {
        if (root == null) {
            return;
        }
        if (root.left != null) {
            dfs(root.left);
        }
        cur.left = null;
        cur.right = root;
        cur = root;
        if (root.right != null) {
            dfs(root.right);
        }
    }
}

这题如果用普通的解法,就直接遍历构造。而上面这个解法是比较秀的。感觉很像连表迭代。但这个题还要注意一点,dfs后,一定要把cur.left赋值为null,不然可能出现环。比如如下树。因为最后遍历的节点是4,遍历完之后就直接跳出dfs了,而此时4的右边节点还是引用着3节点。

   2
  / 
1    4
   /
  3

2021-11-18

二叉搜索树中两个节点之和

二叉搜索树中两个节点之和
这道题第一种比较好想得解法是,先遍历所有节点,把所有节点放到数组里,这样这道题就转换为两数之和了。下面这种方法是两数之和的变形,避免了重复计算的问题,最多的情况一次遍历搞定。

class Solution {
    public boolean findTarget(TreeNode root, int k) {

        //Map<Integer,Integer> map= new HashMap<>();
        Set<Integer> set = new HashSet<>();
        return dfs(root,k,new HashSet<>());
    }

    public boolean dfs(TreeNode root, int k, Set<Integer> set) {
        if (root == null)
            return false;
        if (set.contains(root.val)) {
            return true;
        }
        set.add(k - root.val);
        return dfs(root.left, k, set)||dfs(root.right, k, set);
    }

}

剑指 Offer 34. 二叉树中和为某一值的路径

剑指 Offer 34. 二叉树中和为某一值的路径
dfs和回溯法,不断收集定点到根节点的路径

class Solution {
    public List<List<Integer>> pathSum(TreeNode root, int target) {
        List<List<Integer>> st = new ArrayList<>();
        List<Integer> c = new ArrayList<>();
        dfs(root,target,st,c,0);
        return st;
    }

     public void dfs(TreeNode root, int target, List<List<Integer>> st, List<Integer> c, int sum) {
        if (root == null) {
            return;
        }
        if (root.left == null && root.right == null) {
            if (sum + root.val == target) {
                ArrayList<Integer> path = new ArrayList<>(c);
                path.add(root.val);
                st.add(path);
            }
            return;
        }
        c.add(root.val);
        if (root.left != null) {
            dfs(root.left, target, st, c, sum + root.val);
        }
        if (root.right != null) {
            dfs(root.right, target, st, c, sum + root.val);
        }
        //回溯后一定要移除当前节点,因为当前节点在最后一个,所以移除最后一个
        c.remove(c.size()-1);
    }
}

2021-11-25

92 · 背包问题

92 · 背包问题
这道最原始背包问题,在我之前写过一下,以前对动态规划理解不够深,代码写得比较复杂了。经过大规模的训练之后,能把这块写的相对简单易于理解了。

  • 1.dp[i]代表重量和为i是否能够拼出来,则dp[0]=true是一个初始化
  • 2.每次拿着当前已经拼凑好的dp[i]去和新的物品凑新重量,因为这里dp[i]=true的情况只能在当前这轮的最大值以内产生,所以边界值是curMax=max
  • 3.遍历dp[j]时,j一定不能从0到curMax,一定是从大到小的,不然将会出现一个物品被塞入两次的情况。比如【5,1,3,3】的物品,当i=3时,因为dp[1]=true推导出dp[4]=true,接下来又会遍历到dp[4]=true推导出dp[7]=true,事时上dp[4]是当前这个3加入背包后产生的,再次加入就相当于加入两次。
public class Solution {
    /**
     * @param m: An integer m denotes the size of a backpack
     * @param A: Given n items with size A[i]
     * @return: The maximum size
     */
     public int backPack(int m, int[] A) {
        // write your code here
        //n个物件的重量放在 A数组中

        boolean[] dp = new boolean[m + 1];
        dp[0] = true;

        int max = 0;
        for (int i : A) {
            int curMax = max;
            for (int j = curMax; j >= 0; j--) {
                if (dp[j] && j + i <= m) {
                    max = Math.max(j + i, max);
                    dp[j + i]=true;
                }
            }
        }
        return max;
    }
}

2021-11-26

125 · 背包问题(二)

125 · 背包问题(二)
本道题才是背包问题的本来的面目。也就是经典的0,1背包问题。前面的背包问题,仅仅考虑重量。
这里考虑dp[i] 代表 背包装i重量的体积的最大价值是多少。

往后推:dp[j + A[i]] = Math.max(dp[j + A[i]], dp[j] + V[i]);

public class Solution {
    /**
     * @param m: An integer m denotes the size of a backpack
     * @param A: Given n items with size A[i]
     * @param V: Given n items with value V[i]
     * @return: The maximum value
     */
      public int backPackII(int m, int[] A, int[] V) {
        //write your code here
        int dp[] = new int[m + 1];
        int mv = 0;
        for (int i = 0; i < A.length; i++) {
            int cw = A[i];
            for (int j = m; j >= 0; j--) {
                if (j == 0 || dp[j] > 0 && j + cw <= m) {
                    dp[j + cw] = Math.max(dp[j + cw], dp[j] + V[i]);
                    mv = Math.max(mv, dp[j + cw]);
                }
            }
        }
        return mv;
    }
}

另外一种往前推的 状态转移
dp[j]= dp[j-A[i]]+ V[i] 满足的条件是j这个体积够装A[i] 这个重量

public class P1267_3 {
    public int backPackII(int m, int[] A, int[] V) {
        // write your code here
        int dp[] = new int[m + 1];
        //dp[j]表示选择了第i个元素时, 重量为j时的最大价值
        dp[0] = 0;
        int max=0;
        for (int i = 0; i < A.length; i++) {
            for (int j = m; j >= 0; j--) {
                if (j >= A[i]){
                    dp[j] = Math.max(dp[j - A[i]] + V[i], dp[j]);
                    max=Math.max(max,dp[j]);
                }
            }
        }
        return max;
    }
}
原文地址:https://www.cnblogs.com/mxjhaima/p/15506447.html