动态规划详解

要点:

动规解题的一般思路

    1. 将原问题分解为子问题

    把原问题分解为若干个子问题,子问题和原问题形式相同或类似,只不过规模变小了。子问题都解决,原问题即解决(数字三角形例)。
    子问题的解一旦求出就会被保存,所以每个子问题只需求 解一次。
    2.确定状态

    在用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状 态”。一个“状态”对应于一个或多个子问题, 所谓某个“状态”下的“值”,就是这个“状 态”所对应的子问题的解。
    所有“状态”的集合,构成问题的“状态空间”。“状态空间”的大小,与用动态规划解决问题的时间复杂度直接相关。 在数字三角形的例子里,一共有N×(N+1)/2个数字,所以这个问题的状态空间里一共就有N×(N+1)/2个状态。
    整个问题的时间复杂度是状态数目乘以计算每个状态所需时间。在数字三角形里每个“状态”只需要经过一次,且在每个状态上作计算所花的时间都是和N无关的常数。

    3.确定一些初始状态(边界状态)的值

    以“数字三角形”为例,初始状态就是底边数字,值就是底边数字值。

    4. 确定状态转移方程

     定义出什么是“状态”,以及在该“状态”下的“值”后,就要找出不同的状态之间如何迁移――即如何从一个或多个“值”已知的 “状态”,求出另一个“状态”的“值”(递推型)。状态的迁移可以用递推公式表示,此递推公式也可被称作“状态转移方程”。

    数字三角形的状态转移方程:

  能用动规解决的问题的特点

    1) 问题具有最优子结构性质。如果问题的最优解所包含的 子问题的解也是最优的,我们就称该问题具有最优子结 构性质。

    2) 无后效性。当前的若干个状态值一旦确定,则此后过程的演变就只和这若干个状态的值有关,和之前是采取哪种手段或经过哪条路径演变到当前的这若干个状态,没有关系。
————————————————
版权声明:本文为CSDN博主「ChrisYoung1314」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/baidu_28312631/article/details/47418773

70. 爬楼梯 - 力扣(LeetCode)

爬楼梯问题:

先列出表达式

f(n)=f(n-1)+f(n-2)

到第n阶楼梯只有两种办法,一个是只走一层,一个只走两层,所以分别对应从n-1和n-2来的两种办法

再寻找初始化值:

f(1)= 1;

f(2)=2;

public int climbStairs(int n) {
        if(n == 1)
            return 1;
        //创建数组来存储对应值
        int[] dp = new int[n];
        //初始化相关值
        dp[0] = 1;
        dp[1] = 2;
        int i =2;
        while(i < n){
            dp[i] = dp[i-1] + dp[i-2];
            i++;
        }
        return dp[n - 1];
    }

62. 不同路径 - 力扣(LeetCode)

这道题为本人考试的时候的一道题

经典采用动态规划

在(i,j)点的路径只跟来它这个地方的两个坐标有关(i-1,j)he(i,j-1)有关

而跟路径无关

来求的公式

f(i,j)=f(i-1,j)+f(i,j-1)

而初始化的值全部都是路边上的点

把路边上的点的值全部变成1

public static int uniquePaths(int m, int n) {
        //使用dp来存储
        int[][] dp = new int[m][n];
        //进行初始化
        for (int i = 0; i < m; i++) {
            //把路边上的点全部变成1
            dp[i][0] = 1;
        }
        for (int i = 0; i < n; i++) {
            dp[0][i] = 1;

        }
        if (m<=1||n<=1)
            return dp[m-1][n-1];
        //循环算每一步
        for (int i = 1; i < m; i++) {
            for (int j = 1; j <n; j++) {
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[m-1][n-1];

    }

64. 最小路径和 - 力扣(LeetCode)

这道题和上面一道题类似

因为只能向下或者向右

所以到(i,j)点只有两种情况还是(i-1,j)(i,j-1)

所以到(i,j)点的最短距离就是,(i-1,j)加上这点到(i,j)的距离与(i,j-1)加上这点到(i,j)点的距离这两个距离的最小值

首先还是初始化dp的值

将路边上的点全部初始化

再写出递归公式

f(i,j) = min{f(i-1,j),f(i,j-1)} + grid(i,j)

public static int minPathSum(int[][] grid) {
        //创建dp
        int[][] dp = new int[grid.length][grid[0].length];
        //初始化dp值
        dp[0][0] = grid[0][0];
        if (grid.length >= 2) {
            for (int i = 1; i < grid.length; i++) {
                dp[i][0] = dp[i - 1][0] + grid[i][0];
            }
        }
        if (grid[0].length >= 2) {
            for (int i = 1; i < grid[0].length; i++) {
                dp[0][i] = dp[0][i - 1] + grid[0][i];
            }
        }
        if (grid.length == 1||grid[0].length==1)
            return dp[grid.length - 1][grid[0].length - 1];
        //根据递归公式求解
        for (int i = 1; i < grid.length; i++) {
            for (int j = 1; j < grid[0].length; j++) {
                if (dp[i-1][j] > dp[i][j-1])
                    dp[i][j] = dp[i][j-1] + grid[i][j];
                else 
                    dp[i][j] = dp[i-1][j] + grid[i][j];
            }
        }
        return dp[grid.length-1][grid[0].length-1];
    }

72. 编辑距离 - 力扣(LeetCode)

不会先跳过

5. 最长回文子串 - 力扣(LeetCode)

public static String longestPalindrome(String s) {

        /*
                状态方程
        dp[i][j] = (i == j)&&dp[i+1][j-1]
        如果发生i到j相邻的情况,就直接判断,相等就为ture反之相反
        实际含义就是查看回文串的时候,先看比自己这个字符串左右各缩小一个之后是否为回文串,如果是就再判断增加之后的两个是否相同,相同就返回true
         */
        //先进行dp的初始化,对角线上都为ture
        boolean[][] dp = new boolean[s.length()][s.length()];
        for (int i = 0; i < s.length(); i++) {
            for (int j = 0; j < s.length(); j++) {
                if (i == j)
                    dp[i][j] = true;
                else
                    dp[i][j] = false;
            }
        }
        //内行外列的循环
        for (int i = 0; i < s.length(); i++) {
            for (int j = 0; j < i; j++) {
                //j为前,i为后
                //判断i与j的距离
                if (i - j <= 1) {
                    //直接判断这两个是否相等
                    if (s.charAt(i) == s.charAt(j))
                        dp[j][i] = true;
                    else
                        dp[j][i] = false;
                }else {
                    //大于等于2就进行判断
                    if (dp[j+1][i-1] == true&&s.charAt(j) == s.charAt(i))
                        dp[j][i] = true;
                    else
                        dp[j][i] = false;
                }
            }
        }
        //循环找最大,记录下标
        int i1 = 0;
        int j1 = 0;
        int cnt = 0;
        for (int i = 0; i < s.length(); i++) {
            for (int j = 0; j <= i; j++) {
                if (dp[j][i]){
                    if (j1 - i1 < i - j) {
                        j1 = i;
                        i1 = j;
                    }

                }
            }
        }
        String t = "";
        for (int i = i1; i <= j1; i++) {
            t += s.charAt(i);
        }
        return t;

    }

121. 买卖股票的最佳时机 - 力扣(LeetCode)

dp[i]表示前i天的最大收入

用一个cnt来存储前i天的最小买入点

dp(i)的值由dp(i-1)与cnt共同决定

比较前一天的收入 与 当前这个点与cnt的差 的值,取最大

public int maxProfit(int[] prices) {
        int dp[] = new int[prices.length];
        int cnt = prices[0];
        dp[0] = 0;
        for (int i = 1; i < prices.length; i++) {
            //如若当前值比cnt还小,并且前一项dp为0,就无法卖出
            if (prices[i] <= cnt&&dp[i-1] == 0){
                dp[i] = 0;
            }else {
                //比较前一天的收入 与 当前这个点与cnt的差 的值,取最大
                if (prices[i] - cnt > dp[i-1]){
                    dp[i] = prices[i] - cnt;
                } else  {
                    dp[i] = dp[i-1];
                }
            }
            //更新int的值
            if (prices[i] < cnt)
                cnt = prices[i];
        }
        return dp[prices.length-1];
    }

122. 买卖股票的最佳时机 II - 力扣(LeetCode)

动态算法想着想着,找到了规律,联合股市的涨跌图想象,最赚钱的方法就是,只要一赚钱就卖掉

public static int maxProfit(int[] prices) {
        //创建dp数组
        int[] dp = new int[prices.length];
        dp[0] = 0;
        //用end点记录前i个点中最大点的位置
        for (int i = 1; i < prices.length; i++) {
            if (prices[i] <= prices[i-1]){
                //前面比后面大,无法买入卖出
                dp[i] = dp[i-1];
            }else {
                dp[i] = prices[i] - prices[i-1] + dp[i-1];
            }
        }
        return dp[prices.length-1];
    }

198. 打家劫舍 - 力扣(LeetCode)

核心思想用d(i)来表示前i个数字的最大收益

因为考虑到有相邻报警的情况存在,所以第i个数收益得看第i-1个数字有没有取,以及第i-1个数字是多少,还有i-2影响。在i-1个数字取之后,与i-2有关,比较取i-2与第i的和and i-1相比较,看第i个数字取不取。i-1个数字没取,则最好情况就是直接取第i个。

public int rob(int[] nums) {
        if (nums.length == 1)
            return nums[0];
        //创建dp
        int [] dp = new int[nums.length];
        //创建标志位
        int flag = 1;//第一个数字初始化,必取
        dp[0] = nums[0];
        //为了防止越界,将第二个数也初始化
        flag = nums[0] > nums[1] ? 0 : 1;
        dp[1] = nums[0] > nums[1] ? nums[0] : nums[1];
        for (int i = 2; i < nums.length; i++) {
            if (flag == 1){
                //前一位数字已经取过了
                if (dp[i-1] > dp[i-2] + nums[i]){
                    //第i位不取最好
                    dp[i] = dp[i-1];
                    //更新flag
                    flag = 0;
                }else {
                    //取第i位
                    dp[i] = dp[i-2] + nums[i];
                    flag = 1;
                }
            }else {
                //前一位数字没取,直接加就行
                dp[i] = dp[i-1] + nums[i];
                flag = 1;
            }
        }
        return dp[nums.length - 1];
    }

继续进行优化,实际到i家就两种选择,偷i和不偷i,再将偷i和不偷i进行大小比较,就可,最后还可将其空间变成回滚数组进行优化,就不演示了

public int rob(int[] nums) {
        if (nums.length == 1)
            return nums[0];
        //定义数组
        int[] dp = new int[nums.length];
        //初始化
        dp[0] = nums[0];
        dp[1] = nums[1] > nums[0] ? nums[1] : nums[0];
        for (int i = 2; i < nums.length; i++) {
            //第i家不偷 > 第i家偷
            if (dp[i-1] > dp[i-2] + nums[i]){
                dp[i] = dp[i-1];
            }else {
                dp[i] = dp[i-2] + nums[i];
            }
        }
        return dp[nums.length - 1];

    }



213. 打家劫舍 II - 力扣(LeetCode)

其实还是两种情况,一个是抢第一家最后一家不抢,一个是抢最后一家第一家不抢,如上进行两次dp运算,即可

public int rob(int[] nums) {
        if (nums.length == 1)
            return nums[0];
            if (nums.length == 2)
            return nums[1] > nums[0] ? nums[1] : nums[0];
        //定义数组
        int[] dp1 = new int[nums.length - 1];
        int[] dp2 = new int[nums.length - 1];
        //初始化
        dp1[0] = nums[0];
        dp1[1] = nums[1] > nums[0] ? nums[1] : nums[0];
        dp2[0] = nums[1];
        dp2[1] = nums[2] > nums[1] ? nums[2] : nums[1];
        //dp1进行记录
        for (int i = 2; i < nums.length - 1; i++) {
            //第i家不偷 > 第i家偷
            if (dp1[i-1] > dp1[i-2] + nums[i]){
                dp1[i] = dp1[i-1];
            }else {
                dp1[i] = dp1[i-2] + nums[i];
            }
        }
        //dp2进行记录
        for (int i = 2; i < nums.length - 1; i++) {
            //第i家不偷 > 第i家偷
            if (dp2[i-1] > dp2[i-2] + nums[i+1]){
                dp2[i] = dp2[i-1];
            }else {
                dp2[i] = dp2[i-2] + nums[i+1];
            }
        }
        return dp1[nums.length - 2] > dp2[nums.length - 2] ? dp1[nums.length - 2] : dp2[nums.length - 2];

    }

740. 删除并获得点数 - 力扣(LeetCode)

用map去重并计算各个元素的总的大小,然后用上面的算法计算dp

其实还可以进行空间时间优化,就不优化了

public static int deleteAndEarn(int[] nums) {
        //用map来接
        Map<Integer,Integer> map = new HashMap<>();
        for (int num : nums) {
            if (map.containsKey(num)){
                map.put(num,map.get(num) + num);
            }else {
                map.put(num,num);
            }
        }
        Object[] tmp = map.keySet().toArray();
        Arrays.sort(tmp);
        if (tmp.length == 1)
            return map.get(tmp[0]);
        int[] dp =new int[map.size()];
        List<Integer> list = new ArrayList<>();
        //初始化前两个
        dp[0] = map.get(tmp[0]);
        if ((int)tmp[0] == (int)tmp[1] - 1) {
            dp[1] = map.get(tmp[0]) > map.get(tmp[1]) ? map.get(tmp[0]) : map.get(tmp[1]);
            if (map.get(tmp[0]) > map.get(tmp[1])){
                list.add((int)tmp[0]);
                list.add((int) tmp[0] + 1);
            }else {
                list.add((int)tmp[1]);
                list.add((int) tmp[1] + 1);
            }
        }else {
            dp[1] = map.get(tmp[1]) + dp[0];
            list.add((int)tmp[1] + 1);
        }
        if (map.get(tmp[0]) > map.get(tmp[1])){
            list.add((int)tmp[0]);
            list.add((int) tmp[0] + 1);
        }else {
            list.add((int)tmp[1]);
            list.add((int) tmp[1] + 1);
        }
        for (int i = 2; i < tmp.length; i++) {
            if (list.contains((int)tmp[i])){
                //包含进行大小判断
                if (dp[i-2] + map.get(tmp[i]) > dp[i-1]){
                    //加入这个数
                    dp[i] = dp[i-2] + map.get(tmp[i]);
                    //修改list
                    list.add((int)tmp[i] + 1);
                }else {
                    //不加入这个数
                    dp[i] = dp[i-1];
                    //不修改lsit
                }
            }else {
                //不包含直接加入
                dp[i] = dp[i-1] + map.get(tmp[i]);
                //修改list
                list.add((int)tmp[i] + 1);
            }
        }
        return dp[map.size() - 1];
    }

55. 跳跃游戏 - 力扣(LeetCode)

到某一点分为两种点数

一种是到该点前面点剩余的点数,一个是当前点数。用贪心,下一步取这两种点数中最大的点数即可

如果出现剩余点数与自身点数都是零的情况返回flase即可

public static boolean canJump(int[] nums) {
        //两种点数
        int rest,have;
        rest = nums[0];
        have = rest;
        if (have == 0 && rest == 0 && nums.length > 1)
            return false;
        for (int i = 1; i < nums.length; i++) {
            //剩余点数就是上一轮最大的里面选一个减一
            rest = rest > have ? rest : have;
            rest--;
            //自身点数直接就等于自身
            have = nums[i];
            if (have == 0 && rest == 0 &&i != nums.length - 1){
                //运行到这就是无法进行下去了
                return false;
            }
        }
        return true;
    }

45. 跳跃游戏 II - 力扣(LeetCode)

这道题只要在前面的基础上加上一个flag来记录步数

如果rest点数继承自have就要flag++

如果rest点数继承自rest,就flag不动

上面就是我的错误理解方法,下面为真的

dp(i)表示前i个点能到达的最远距离

也就两种情况这个点到底加不加入所跳的点,如果dp(i-1)比现在所处的点加上这个点的点数大,就不加入,反之则加入

dp(i)=max(i+1+这个点拥有的点数,dp(i-1))

这个算法有点问题,但是改一下就是贪心了这里就不细想了

这里就用一下别人的算法

本题主流解法是利用贪心,每次跳跃到可能跳的最远的位置上,不断求局部最优,最终达到全局最优。

在这里提供一种动态规划的思路:

我们维持一个数组dp,dp[i]的意义是:到达位置i需要的最小跳跃数。

初始状态:dp[0]=0,最开始我们就站在0。其余dp[i]是一个极大的数值,因为初始我们假设跳不到这些位置。

状态更新:假设dp[i]可以跳n下,我们就更新dp[i+1]到dp[i+n],也就是dp[i]可以一次跳跃到达的位置。

根据dp[i]的意义,可以分析出dp[i+j]状态转移的两种状况:

1.到达i+n位置需要的最小跳跃,是由i跳跃过来的,dp[i+j]=dp[i]+1;

2.到达i+n位置需要的最小跳跃,是由i之前的某次跳跃来的,也就是dp[i+j]<dp[i]+1,dp[i+j]维持原状。

这样就得到了状态转移方程,在i+1到i+n的范围内进行更新:

dp[i+j]<dp[i]+1 时:dp[i+j]=dp[i+n];

否则:dp[i+n]=dp[i]+1;
————————————————
版权声明:本文为CSDN博主「Darius吴贺」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Dariuswuhe/article/details/124298010

public static int jump(int[] nums) {
        int[] dp = new int[nums.length];
        //初始化
        for (int i = 0; i < nums.length; i++) {
            dp[i] = 100000;
        }
        dp[0] = 0;
        if (nums.length == 1)
            return 0;
        for (int i = 0; i < nums.length; i++) {
            for (int j = i+1; j <= i + nums[i]; j++) {
                if (j > nums.length - 1)
                    //越界
                    break;
                dp[j] = dp[j] < dp[i] + 1 ? dp[j] : dp[i] + 1;
                if (dp[nums.length - 1] < 100000)
                    return dp[nums.length-1];
            }
        }
        return dp[nums.length - 1];
    }

53. 最大子数组和 - 力扣(LeetCode)

https://leetcode.cn/problems/maximum-subarray/solution/dong-tai-gui-hua-fen-zhi-fa-python-dai-ma-java-dai/

附上链接,后效性讲的太牛了

dp(i)为第i个元素为结尾的最大和

递归公式

dp(i)=max(dp(i-1)+nums(i),nums(i))

如果i-1为负数,直接另起炉灶

如果为正数,无脑加

public int maxSubArray(int[] nums) {
        //定义dp
        int[] dp = new int[nums.length];
        //初始化
        dp[0] = nums[0];
        for (int i = 1; i < nums.length; i++) {
            if (dp[i-1] <= 0)
                //前面小于等于0,就直接另起炉灶
                dp[i] = nums[i];
            else 
                //大于0,就直接加
                dp[i] = dp[i-1] + nums[i];
        }
        //遍历寻找最大元素
        int tmp = dp[0];
        for (int i = 1; i < nums.length; i++) {
            tmp = tmp > dp[i] ? tmp : dp[i];
        }
        return tmp;

    }

918. 环形子数组的最大和 - 力扣(LeetCode)

最大的环形子数组和 = max(最大子数组和,数组总和-最小子数组和)

直接看答案的,我的思路是将上面的所有情况遍历一遍,也就是每一个元素作为起点遍历找最大情况

答案思路就是两种情况,最大数组没有跨第一个元素循环和跨第一个元素循环两种情况,第二种情况要找最小数组和,用total减去最小数组和就是最大数组和

public static int maxSubarraySumCircular(int[] nums) {
        //初始化元素
        int first = nums[0];//第一种情况下的循环量
        int firstMax = first;//第一种情况下的最大值
        int second = nums[0];
        int secondMin = second;
        int total = 0;
        //第一种情况不跨第一个元素找
        for (int i = 1; i < nums.length; i++) {
            first = Math.max(first+nums[i],nums[i]);
            firstMax = Math.max(first,firstMax);
        }
        //第二种情况下找最小数组和
        for (int i = 1; i < nums.length; i++) {
            second = Math.min(nums[i], second+nums[i]);
            secondMin = Math.min(secondMin,second);
        }
        //找total
        for (int i = 0; i < nums.length; i++) {
            total += nums[i];
        }
        //算出第二种情况最大值
        int secondMax = total - secondMin;
        //如果相减全部为0就说明这个数组全为负数
        if (secondMax == 0)
            return firstMax;
        return firstMax > secondMax ? firstMax : secondMax;
    }

152. 乘积最大子数组 - 力扣(LeetCode)

直接看的答案

  public int maxProduct(int[] nums) {
        int max = Integer.MIN_VALUE, imax = 1, imin = 1;
        for(int i=0; i<nums.length; i++){
            if(nums[i] < 0){ 
              int tmp = imax;
              imax = imin;
              imin = tmp;
            }
            imax = Math.max(imax*nums[i], nums[i]);
            imin = Math.min(imin*nums[i], nums[i]);
            
            max = Math.max(max, imax);
        }
        return max;
    }

1014. 最佳观光组合 - 力扣(LeetCode)

dp表示前面的点到i点的最大值

这个最大值与前一个dp和这两个values有关

递归公式为:

dp(i) = max(dp[i-1] - values[i-1] + values[i] - 1 , values[i] + values[i-1] - 2)

前面一个是前面的点到该点的值,后面的是前面一个点到该点的值

public static int maxScoreSightseeingPair(int[] values) {
        //设置dp
        int[] dp = new int[values.length];
        dp[1] = values[1] + values[0] - 1;
        for (int i = 2; i < values.length; i++) {
            if (dp[i-1] - values[i-1] + values[i] - 1 > values[i] + values[i-1] - 2){
                dp[i] = dp[i-1] - values[i-1] + values[i] - 1;
            }else {
                dp[i] = values[i] + values[i-1] - 1;
            }
        }
        int tmp = 0;
        for (int i = 0; i < values.length; i++) {
            if (tmp < dp[i])
                tmp = dp[i];
        }
        return tmp;
    }

122. 买卖股票的最佳时机 II - 力扣(LeetCode)

这里直接用别人解答(74条消息) [Leetcode] 买卖股票合集(动态规划)_leetcode买卖股票_wy_hhxx的博客-CSDN博客

public int maxProfit(int[] prices) {
        //设置dp
        int[][] dp = new int[prices.length][2];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for (int i = 1; i < prices.length; i++) {
            //dp[i][0] 代表第i天收盘后不持有股票的最大收益 --> 前日不持有今日无操作、前日持有今日卖出
            //dp[i][1] 代表第i天收盘后持有股票的最大收益 --> 前日已持有今日无操作 、前日不持有今日买入
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0] - prices[i]);
        }
        return dp[prices.length-1][0];
    }

714. 买卖股票的最佳时机含手续费 - 力扣(LeetCode)

 public int maxProfit(int[] prices, int fee) {
        //设置dp
        int[][] dp = new int[prices.length][2];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for (int i = 1; i < prices.length; i++) {
            //dp[i][0] 代表第i天收盘后不持有股票的最大收益 --> 前日不持有今日无操作、前日持有今日卖出
            //dp[i][1] 代表第i天收盘后持有股票的最大收益 --> 前日已持有今日无操作 、前日不持有今日买入
            //加入小费即可
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1] + prices[i]-fee);
            dp[i][1] = Math.max(dp[i-1][1],dp[i-1][0] - prices[i]);
        }
        return dp[prices.length-1][0];
    }

309. 最佳买卖股票时机含冷冻期 - 力扣(LeetCode)

public int maxProfit(int[] prices) {
        int[][] dp = new int[prices.length][3];
        dp[0][0] = 0;
        dp[0][2] = -prices[0];
        dp[0][1] = 0;
        for (int i = 1; i < prices.length; i++) {
            dp[i][0] = Math.max(dp[i-1][0],dp[i-1][1]);
            dp[i][2] = Math.max(dp[i-1][2],dp[i-1][0] - prices[i]);
            dp[i][1] = dp[i-1][2] + prices[i];
        }
        return Math.max(dp[prices.length-1][0],dp[prices.length-1][1]);
    }

139. 单词拆分 - 力扣(LeetCode)

public static boolean wordBreak(String s, List<String> wordDict) {
       boolean[] dp = new boolean[s.length()+1];
       dp[0] = true;
        for (int i = 0; i < s.length(); i++) {
            for (int j = i; j >= 0; j--) {
                if (wordDict.contains(s.substring(j,i+1))&&dp[j]){
                    dp[i+1] = true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }

42. 接雨水 - 力扣(LeetCode)

第i列接到的雨水的值,为左右两面的最大值减去当前列的值

dp(i)= min(left(i),right(i))- height(i)

为了解决当前列为最大值的情况,将left包括自己的最大值,right也包括自己的最大值,这样相减起来就是0

public static int trap(int[] arr) {
        // 找出每个arr[i]左边的最大值(包含它自己)
        int[] leftMax = new int[ arr.length ];
        leftMax[0] = arr[0];
        for ( int i=1; i<arr.length; i++ )
            leftMax[i] = Math.max( leftMax[i-1], arr[i] );

        // 找出每个arr[i]右边的最大值(包含它自己)
        int[] rightMax = new int[arr.length];
        rightMax[arr.length-1] = arr[arr.length-1];
        for ( int i=arr.length-2; i>=0; i-- )
            rightMax[i] = Math.max( rightMax[i+1], arr[i] );

        // 用max( iLeft, iRight) - arr[i]求出每个arr[i]的最大接水量
        int ret = 0;
        for ( int i=0; i<arr.length; i++ )
            ret += Math.min( leftMax[i], rightMax[i] ) - arr[i];
        return ret;
    }

413. 等差数列划分 - 力扣(LeetCode)

用一个数组来存前一个与后一个的差值,如果够三个就加入集合中,最后将集合中的元素都取出来进行总体的数量计算

public static int numberOfArithmeticSlices(int[] nums) {
        //创建等差数列组
        int[] dp = new int[nums.length-1];
        for (int i = 0; i < nums.length-1; i++) {
            dp[i] = nums[i+1] - nums[i];
        }
        //查找等差数组中相等数量超过三个的
        List<Integer> list = new ArrayList<>();
        int t = 0;
        for (int i = 1; i < dp.length; i++) {
            if (dp[t] != dp[i]){
                if (i-t>=2){
                    //足够三个就加入集合
                    list.add(i-t+1);
                }
                t = i;
            }
        }
        if (dp.length-t>=2){
            list.add(dp.length-t+1);
        }
        System.out.println(list);
        int cnt = 0;
        if (list.isEmpty())
            return 0;
        for (Integer integer : list) {
            for (int i = integer-3+1; i >= 1; i--) {
                cnt += i;
            }
        }
        return cnt;
    }

91. 解码方法 - 力扣(LeetCode)

分类讨论,进行状态切换

public static int numDecodings(String s) {
        //创建dp,dp(i)表示前i个元素的最多个数
        int[] dp = new int[s.length()+1];
        dp[0] = 0;
        if (s.charAt(0) - '0' != 0)
            dp[1] = 1;
        else
            return 0;
        for (int i = 1; i < s.length(); i++) {
            if (s.charAt(i) - '0' == 0){
                //这一位等于0,就表明不能单独成位,这时判断前面一位是多少,如果大于等于3,就表明不能成立
                if (s.charAt(i-1) - '0' >= 3||s.charAt(i-1) - '0' <= 0)
                    return 0;
                else {
                    if (i-2 <= 0)
                        dp[i+1] = 1;
                    else
                        dp[i+1] = dp[i-1];
                }
            }else{
                //判断是不是7到9,,如果前一位为1开头才行
                if (s.charAt(i) - '0' >= 7) {
                    if (s.charAt(i-1) - '0' == 1) {
                        if (i-1 == 0)
                         dp[i + 1] = dp[i] + 1;
                        else
                         dp[i + 1] = dp[i] + dp[i - 1];
                    }else {
                        dp[i+1] = dp[i];
                    }
                } else {
                    //如果小于6,则直接判断前面一位是不是大于0小于3
                    if (s.charAt(i-1) - '0' > 0 && s.charAt(i-1) - '0' < 3) {
                        if (i-1 == 0)
                            dp[i + 1] = dp[i] + 1;
                        else
                           dp[i + 1] = dp[i] + dp[i - 1];
                    }  else
                        dp[i+1] = dp[i];
                }
            }
        }
        return dp[s.length()];
    }

96. 不同的二叉搜索树 - 力扣(LeetCode)、用卡特兰数

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

        return dp[n];
    }

931. 下降路径最小和 - 力扣(LeetCode)

简单题

public static int minFallingPathSum(int[][] matrix) {
        int[][] dp = new int[matrix.length][matrix[0].length];
        for (int i = 0; i < matrix[0].length; i++) {
            dp[0][i] = matrix[0][i];
        }
        for (int i = 1; i < matrix.length; i++) {
            for (int j = 0; j < matrix[0].length; j++) {
                if (j-1 < 0){
                    //左面越界
                    dp[i][j] = Math.min(dp[i-1][j] + matrix[i][j],dp[i-1][j+1]+ matrix[i][j]);
                }else if (j+1 >= matrix[0].length){
                    //右面越界
                    dp[i][j] = Math.min(dp[i-1][j]+ matrix[i][j],dp[i-1][j-1]+ matrix[i][j]);
                }else {
                    dp[i][j] = Math.min(Math.min(dp[i-1][j]+ matrix[i][j],dp[i-1][j-1]+ matrix[i][j]),dp[i-1][j+1]+ matrix[i][j]);
                }
            }
        }
        int tmp = dp[matrix.length-1][0];
        for (int i = 0; i < dp[0].length; i++) {
            if (dp[matrix.length-1][i] < tmp)
                tmp = dp[matrix.length-1][i];
        }
        return tmp;
    }

120. 三角形最小路径和 - 力扣(LeetCode)

简单题

public static int minimumTotal(List<List<Integer>> triangle) {
        int[][] dp = new int[triangle.size()][triangle.get(triangle.size()-1).size()];
        dp[0][0] = triangle.get(0).get(0);
        for (int i = 1; i < triangle.size(); i++) {
            for (int j = 0; j < triangle.get(i).size(); j++) {
                if (j-1 < 0){
                    //左面越界
                    dp[i][j] = dp[i-1][j] + triangle.get(i).get(j);
                }else if (j+1 >= triangle.get(i).size()){
                    //右面越界
                    dp[i][j] = dp[i-1][j-1] + triangle.get(i).get(j);
                }else {
                    dp[i][j] = Math.min(dp[i-1][j-1] + triangle.get(i).get(j),dp[i-1][j] + triangle.get(i).get(j));
                }
            }
        }
        int tmp = dp[triangle.size()-1][0];
        for (int i = 1; i < dp[0].length; i++) {
            if (tmp > dp[dp.length-1][i])
                tmp = dp[dp.length-1][i];
        }
        return tmp;
    }

1314. 矩阵区域和 - 力扣(LeetCode)

直接用答案

动态规划|二维前缀和算法 + 图解 - 矩阵区域和 - 力扣(LeetCode)

public static int[][] matrixBlockSum(int[][] mat, int k) {
        //设置dp数组,dp(i)(j)表示以dp[i][j] 表示数组 mat 中以 (0, 0) 为左上角,(i, j) 为右下角的矩形子数组的元素之和。
        int[][] dp = new int[mat.length][mat[0].length];
        //设置初始值
        dp[0][0] = mat[0][0];
        for (int i = 1; i < mat.length; i++) {
            dp[i][0] = dp[i-1][0] + mat[i][0];
        }
        for (int i = 1; i < mat[0].length; i++) {
            dp[0][i] = dp[0][i-1] + mat[0][i];
        }
        //开始循环得值
        for (int i = 1; i < mat.length; i++) {
            for (int j = 1; j < mat[0].length; j++) {
                dp[i][j] = dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1] + mat[i][j];
            }
        }
        //设置得到的数组
        int[][] answer = new int[mat.length][mat[0].length];
        //开始
        int x1,x2,y1,y2;
        for (int i = 0; i < mat.length; i++) {
            for (int j = 0; j < mat[0].length; j++) {
                //进行越界处理
                if (i-k < 0) x1 = 0;
                else x1 = i-k;

                if (j-k < 0)  y1 = 0;
                else y1 = j-k;

                if (i+k >= mat.length) x2 = mat.length-1;
                else x2 = i + k;

                if (j+k >= mat[0].length) y2 = mat[0].length-1;
                else y2 = j + k;

                //两个都越界
                if (x1-1 < 0&&y1-1 < 0)
                    answer[i][j] = dp[x2][y2];
                else if (x1-1 < 0)
                    //越界一个
                    answer[i][j] = dp[x2][y2] - dp[x2][y1 - 1];
                else if (y1 - 1 < 0)
                    //越界一个
                    answer[i][j] = dp[x2][y2] - dp[x1-1][y2];
                else
                    //不越界
                    answer[i][j] = dp[x2][y2] - dp[x2][y1-1] - dp[x1-1][y2] + dp[x1-1][y1-1];

            }
        }
        return answer;
    }

304. 二维区域和检索 - 矩阵不可变 - 力扣(LeetCode)

跟上面一道题一样

public class NumMatrix {
    int[][] s;
    int[][] dp;
    public NumMatrix(int[][] matrix) {
        this.s = matrix;
        {
            //计算前缀和
            int[][] dp = new int[s.length][s[0].length];
            dp[0][0] = matrix[0][0];
            for (int i = 1; i < s.length; i++) {
                dp[i][0] = dp[i-1][0] + s[i][0];
            }
            for (int i = 1; i < s[0].length; i++) {
                dp[0][i] = dp[0][i-1] + s[0][i];
            }
            //开始循环得值
            for (int i = 1; i < s.length; i++) {
                for (int j = 1; j < s[0].length; j++) {
                    dp[i][j] = dp[i][j-1] + dp[i-1][j] - dp[i-1][j-1] + s[i][j];
                }
            }
            this.dp = dp;
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        //两个都越界
        if (row1-1 < 0&&col1-1 < 0)
            return dp[row2][col2];
        else if (row1-1 < 0)
            //越界一个
            return dp[row2][col2] - dp[row2][col1 - 1];
        else if (col1 - 1 < 0)
            //越界一个
            return dp[row2][col2] - dp[row1-1][col2];
        else
            //不越界
            return dp[row2][col2] - dp[row2][col1-1] - dp[row1-1][col2] + dp[row1-1][col1-1];
    }
}

63. 不同路径 II - 力扣(LeetCode)

简单题

看注释吧

public static int uniquePathsWithObstacles(int[][] obstacleGrid) {
        //设置dp,dp[i][j]表示到ij点有多少路径
        int[][] dp = new int[obstacleGrid.length][obstacleGrid[0].length];
        //将边路上的dp全部设置成1
        for (int i = 0; i < obstacleGrid.length; i++) {
            //如果该点不为障碍物才设置为1,否则设置为0,后面的也设置为0
            if (obstacleGrid[i][0] == 1){
                for (int j = i; j < obstacleGrid.length; j++) {
                    dp[i][0] = 0;
                }
                break;
            }else
                dp[i][0] = 1;
        }
        for (int i = 0; i < obstacleGrid[0].length; i++) {
            //如果该点不为障碍物才设置为1,否则设置为0,后面的也设置为0
            if (obstacleGrid[0][i] == 1){
                for (int j = i; j < obstacleGrid[0].length; j++) {
                    dp[0][i] = 0;
                }
                break;
            }else
                dp[0][i] = 1;
        }
        //开始动态规划
        for (int i = 1; i < obstacleGrid.length; i++) {
            for (int j = 1; j <obstacleGrid[0].length ; j++) {
                //先判断这个点是不是障碍物,如果是就设置为0,如果不是就加上左面和上面的
                if (obstacleGrid[i][j] == 1){
                    dp[i][j] = 0;
                }else {
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
                }
            }
        }
        return dp[obstacleGrid.length-1][obstacleGrid[0].length-1];
    }

221. 最大正方形 - 力扣(LeetCode)

dp(i,j)=min(dp(i−1,j),dp(i−1,j−1),dp(i,j−1))+1

这个东西比较难推出

代码就简单了

public static int maximalSquare(char[][] matrix) {
        //设置dp,dp[i][j]表示到ij点为右下角元素的最大正方形的面积
        int a = matrix.length;
        int b = matrix[0].length;
        int[][] dp = new int[a][b];
        //设置边界值
        for (int i = 0; i < a; i++) {
            dp[i][0] = matrix[i][0] - '0';
        }
        for (int i = 0; i < b; i++) {
            dp[0][i] = matrix[0][i] - '0';
        }
        //开始动态规划
        for (int i = 1; i < a; i++) {
            for (int j = 1; j < b; j++) {
                //判断自身是不是1
                if (matrix[i][j] == '1'){
                    dp[i][j] = Math.min(Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1]) + 1;
                }else dp[i][j] = 0;
            }
        }
        int tmp = 0;
        for (int i = 0; i < a; i++) {
            for (int j = 0; j < b; j++) {
                if (tmp < dp[i][j])
                    tmp = dp[i][j];
            }
        }
        return tmp * tmp;
    }

剑指 Offer II 103. 最少的硬币数目 - 力扣(LeetCode)

class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        //dp,i表示i元的最少硬币,为-1则表示不可
        dp[0] = 0;
        for (int i = 1; i <= amount; i++) {
            int tmp = -1;
            for (int j = 0; j < coins.length; j++) {
                int cnt = -1;
                if (i-coins[j] < 0){
                    //不满足
                    cnt = -1;
                }else {
                    if (dp[i-coins[j]] < 0){
                        //也不满足
                        cnt = -1;
                    }else {
                        cnt = dp[i-coins[j]] + 1;
                    }

                }
                if (tmp == -1){
                    tmp = cnt;
                }else {
                    if (cnt != -1){
                        if (tmp > cnt)
                            tmp = cnt;
                    }
                }
            }
            dp[i] = tmp;
        }

        return dp[amount];
    }
    }

1043. 分隔数组以得到最大和 - 力扣(LeetCode)

用别人的解法

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值