• LeetCode做题笔记之动态规划


    LeetCode之动态规划

    时间有限只做了下面这几道:70、338、877、96、120、95、647,后续会继续更新

    70:爬楼梯

    先来道简单的练练手,一道经典的动态规划题目

    可以采用动态规划的备忘录法,第n节楼梯的数目等于第n-1节和n-2节的和,因为第n节一定由n-1或n-2上去的。可以不使用递归而使用简单的for循环实现:

    public int climbStairs(int n) {
        int[] ints = new int[n + 1];
        ints[1] = 1;
        if (n > 1) ints[2] = 2;
        for (int i = 3; i <= n; i++) {
            ints[i] = ints[i - 1] + ints[i - 2];
        }
        return ints[n];
    }
    

    这道题的难点在于想到第n节的数目等于n-1和n-2节的数目,对于没有接触过动态规划的菜鸟来说还是有点意思的。
    PS:如果数目过大超出了int类型的最大值,可以使用BigInteger类

    338:比特位计数

    这道题也还行,对二进制数来说,一个偶数的最后一位一定是0,基于这个原理可以得出下面的代码:

    for (int i = 0; i < arr.length; i++) {
        if (i % 2 == 0) {
            arr[i] = arr[i / 2];
        }else {
            arr[i] = arr[i /2] + 1;
        }
    }
    

    然而~~大佬们总是有强大的方法,我那if else用一行代码就搞定了:arr[i] = arr[i >> 1] + (i & 1),右移倒也还好,这个i & 1用的就简直了,佩服佩服。

    话说这和动态规划没什么关系吧?可我是专门挑的动态规划的题做的欸

    877:石子游戏

    感觉这题出的不太好,因为我想看到的是你的算法比我的算法厉害,而不是你直接给我说先手必赢,因为我完全没有往这方面考虑过(估计大佬能想出来吧)。先手必赢的原因是先手可以决定拿所有的偶数堆还是奇数堆,而对偶数堆和奇数堆两个一定有一个是数目比较多的(总数为奇数)
    最开始我觉得比较一下前后的的大小直接挑大的就行,但是对于[3,2,10,4]这样的排列先手会得7分而后手得12分,这显然是错误的。
    这一题的思路是以局部最优解得出整体最优解,典型的动态规划

    public static boolean stoneGame(int[] piles) {
        int length = piles.length;
        //results[i][j]存储的是piles中第i个数到第j个数组成序列的最佳拿取方式下的得分
        int[][] results = new int[length][length];
        //当集合中只有一个堆的时候,拿的那个人直接得分
        for(int i=0;i<length;i++){
            results[i][i]=piles[i];
        }
        //当集合中有两个数的时候,先选的人肯定是拿较大数,分数为max-min
        for(int i=0;i<length-1;i++){
            results[i][i+1]=Math.abs(piles[i]-piles[i+1]);
        }
    
        for(int i=length-3;i>=0;i--){
            for(int j=i+2;j<length;j++){
                results[i][j]=Math.max(piles[i]-results[i+1][j],piles[j]-results[i][j-1]);
            }
        }
        return results[0][length-1]>0;
    }
    

    对于二维数组results[i][j]
    第一个for循环计算的是绿色的方块,第二个for循环计算蓝色的方块,第三个填充右上的白色的方块
    877

    最后只要看右上角的值是否大于0即可。

    96:不同的二叉搜索树

    这一道题的难点在于找出节点增加时二叉搜索树种类如何增加。
    首先,新增加的节点默认是最大的(不然没什么意义),对于n+1个节点的树来说,有C(n+1) = C0 * Cn + C1 * C(n-1) + ... + C(n-1) * C1 + Cn * C0其中C0 = 1, C1 = 1解释:对于第n+1个节点的树来说,C0 * Cn对应以最小节点为根节点,左节点为0个,右节点为n个;C1 * C(n-1)对应以倒数第二小的为根节点,左边1个节点,右边n-1个节点......直到第n+1个节点为子节点。这样的话代码就很容易写出来了。

    public static int numTrees(int n) {
        if(n<=1)return 1;
        int[] dp=new int[n+1];
        dp[0]=1;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            for(int j=1;j<=i;j++){
                dp[i]+=dp[j-1]*dp[i-j];
            }
        }
        return dp[n];
    }
    

    话说这道题根本和动态规划没多大关系啊,完全是在计算CatAlan数。。。

    120:三角形最小路径和

    这一题和第877题比较像,可以把三角形斜向左上,就像这样:
    120
    解法也和877比较像:

    public static int minimumTotal(List<List<Integer>> triangle) {
        int[][] arr = new int[triangle.size()][triangle.size()];
        List<Integer> lastList = triangle.get(triangle.size() - 1);
        for (int i = 0; i < lastList.size(); i++) {
            arr[arr.length - 1 - i][i] = lastList.get(i);
        }
    
        for (int n = triangle.size() -2; n >= 0; n--) {
            List<Integer> list = triangle.get(n);
            for (int i = 0; i <= n; i++) {
                int j = n-i;
                arr[i][j] = list.get(j) + Math.min(arr[i + 1][j], arr[i][j + 1]);
            }
        }
        return arr[0][0];
    }
    

    然后再新建个一样的二维数组记录由该点到底部所需的最小和。

    95:不同的二叉搜索树2

    这一题是第96题的增强题,不仅要求求出多少个,还要求出每个什么样子。这一题其实最开始没解出来,看了答案之后才动了怎么做,原来是通过暴力递归(貌似也只能这么做了),题目做多了之后下意识地觉得肯定不是暴力解,总想着找一种更省力的方法,然而有时候就是会陷入其中。

    private static List<TreeNode> helper(int start, int end) {
        List<TreeNode> res = new ArrayList<>();
        if (start > end) {
            res.add(null);
            return res;
        }
        for (int val = start; val <= end; val++) {
            List<TreeNode> left = helper(start, val - 1);
            List<TreeNode> right = helper(val + 1, end);
            for (TreeNode l : left) {
                for (TreeNode r : right) {
                    TreeNode root = new TreeNode(val);
                    root.left = l;
                    root.right = r;
                    res.add(root);
                }
            }
        }
        return res;
    }
    

    647:回文子串

    最开始看到这一题时想到了第五题:最长回文子串,然后想到了Manacher(马拉车)算法,马拉车的数组P[]既然可以求出最长回文子串,那么就能求出回文子串的个数:

    //此方法对字符串进行加工
    public static String preProcess(String s) {
        StringBuilder sb = new StringBuilder(s.length()*2+3);
        sb.append("^");
        for (int i = 0; i < s.length(); i++) {
            sb.append("#").append(s.charAt(i));
        }
        sb.append("#$");
        return sb.toString();
    }
    
    // 马拉车算法
    public static int countSubstrings(String s) {
        String T = preProcess(s);
    //        System.out.println(Arrays.toString(T.toCharArray()));
        int n = T.length();
        int[] P = new int[n];
        int C = 0, R = 0;
        for (int i = 1; i < n - 1; i++) {
            int i_mirror = 2 * C - i;
            if (R > i) {
                P[i] = Math.min(R - i, P[i_mirror]);// 防止超出 R
            } else {
                P[i] = 0;// 等于 R 的情况
            }
    
            // 碰到之前讲的三种情况时候,需要继续扩展
            while (T.charAt(i + 1 + P[i]) == T.charAt(i - 1 - P[i])) {
                P[i]++;
            }
    
            // 判断是否需要更新 R
            if (i + P[i] > R) {
                C = i;
                R = i + P[i];
            }
        }
        //此时P[]数组已经求出,根据该数组可以获得回文子串的个数
        int sum = 0;
        for (int i = 1; i < P.length - 1; i++) {
            if (i % 2 == 0) {
                sum += (P[i] + 1) / 2;
            } else {
                sum += P[i] / 2;
            }
        }
    //        System.out.println(Arrays.toString(P));
        return sum;
    }
    

    结果一看评论全都是用的两边扫描……我就纳闷了,做第五题的时候都在马拉车,就我两边扫描,结果到这题了都是两边扫描就我马拉车,难道我思维比较奇葩?
    贴个中心扫描的答案:

    public int countSubstrings2(String s) {
        for (int i=0; i < s.length(); i++){
            count(s, i, i);//回文串长度为奇数
            count(s, i, i+1);//回文串长度为偶数
        }
        return num;
    }
    
    public void count(String s, int start, int end){
        while(start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)){
            num++;
            start--;
            end++;
        }
    }
    

    ┓(;´_`)┏

  • 相关阅读:
    pytest ini配置文件格式
    C#星辰之路
    bootstrap
    rabbitmq 安装
    curl 下载文件
    mysql 面试题
    mvn --version
    后台运行的nohup vs &
    scp
    linux下.tar.gz和.gz文件解压详解
  • 原文地址:https://www.cnblogs.com/lixin-link/p/11066938.html
Copyright © 2020-2023  润新知