动态规划问题汇总(一)

基本步骤

在这里插入图片描述

509. 斐波那契数

在这里插入图片描述
递归版本

class Solution {
    public int fib(int n) {
        if(n==0){
            return 0;
        }
        if(n==1){
            return 1;
        }
        return fib(n-1)+fib(n-2);
    }
}

动态规划版本

class Solution {
    public int fib(int n) {
        int[] dp = new int[n+1];
        if(n<=1){
            return n;
        }
        
        dp[0]=0;
        dp[1]=1;
        for(int i=2;i<=n;i++){
            dp[i] = dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
}

优化后,不用存储一整个数组,只需要存储三个数即可

class Solution {
    public int fib(int n) {
        int[] dp = new int[2];
        if(n<=1){
            return n;
        }
        dp[0]=0;
        dp[1]=1;

        int sum=0;
        for(int i=2;i<=n;i++){
            sum = dp[0]+dp[1];
            dp[0]=dp[1];
            dp[1]=sum;

        }
        return sum;
    }
}

这也是动态规划的常见优化方式,优化额外的存储空间。

70. 爬楼梯

在这里插入图片描述

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

    }
}

其实就是斐波那契数列,可自行优化

746. 使用最小花费爬楼梯

在这里插入图片描述

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        // dp[i]到达第i个台阶需要支付的最低花费,注意到达该台阶本身并不需花费,即不管cost[i],只有往上跳了才会产生花费
        int[] dp = new int[cost.length+1];
        if(cost.length==2){
            return Math.min(cost[0],cost[1]);
        }
        dp[0]=0;
        dp[1]=0;
        for(int i=2;i<=cost.length;i++){
            dp[i]=Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
        }
        return dp[cost.length];
    }
}

稀里糊涂也写了另一个版本,到达每个台阶的最低花费中,把自身跳的花费也计算了进去,最后到达楼顶的花费,即是倒数第一阶和倒数第二阶的花费最小值。

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int[] dp = new int[cost.length];
        if(cost.length==2){
            return Math.min(cost[0],cost[1]);
        }
        dp[0]=cost[0];
        dp[1]=cost[1];
        for(int i=2;i<cost.length;i++){
            dp[i]=Math.min(dp[i-1],dp[i-2])+cost[i];
        }
        return Math.min(dp[cost.length-1],dp[cost.length-2]);
    }
}

建议直接看前一个版本,更好理解。

62.不同路径

在这里插入图片描述

class Solution {
    public int uniquePaths(int m, int n) {
    // dp[i][j]表示从(0,0)到(i,j)有多少条不同路径
        int[][] dp = new int[m][n];
        for(int i=0;i<m;i++){
            dp[i][0]=1;
        }
        for(int i=0;i<n;i++){
            dp[0][i]=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];
    }
}

优化成两行数组

class Solution {
    public int uniquePaths(int m, int n) {
        if(m==1 || n==1){
            return 1;
        }
        int[][] dp = new int[2][n];
        dp[1][0]=1;
        for(int i=0;i<n;i++){
            dp[0][i]=1;
        }
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                dp[1][j] = dp[0][j] + dp[1][j-1];
                dp[0][j]=dp[1][j];
                dp[0][j-1] = dp[1][j-1];
            }
        }
        return dp[1][n-1];
    }
}

进一步优化成一行数组

class Solution {
    public int uniquePaths(int m, int n) {
        if(m==1 || n==1){
            return 1;
        }
        int[] dp = new int[n];
        for(int i=0;i<n;i++){
            dp[i]=1;
        }
       
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
               dp[j] = dp[j] + dp[j-1];
            }
        }
        return dp[n-1];
    }
}

dp[j] = dp[j] + dp[j-1];
当你遍历到第j个元素时,当前里面存着的其实是上一次遍历存储的j,即上一行的(i-1,j),由于是从左到右遍历的,而j-1的元素刚刚更新完,即(i,j-1),所以直接dp[j] = dp[j] + dp[j-1]即可

63. 不同路径 II

在这里插入图片描述

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        int[][] dp = new int[m][n];

        // 初始化,若第一行或第一列有障碍物,则后面的路径数皆为0
        for(int i=0;i<m&&obstacleGrid[i][0]==0;i++){
            dp[i][0]=1;
        }
        for(int i=0;i<n&&obstacleGrid[0][i]==0;i++){
            dp[0][i]=1;
        }

        // 从第二行或第二列开始遍历
        for(int i=1;i<m;i++){
            for(int j=1;j<n;j++){
                if(obstacleGrid[i][j] == 0){
                   dp[i][j]=dp[i-1][j] + dp[i][j-1];
                }
 
            }
        }

        return dp[m-1][n-1];


    }
}

直接优化成一维数组

class Solution {
    public int uniquePathsWithObstacles(int[][] obstacleGrid) {
        int m = obstacleGrid.length;
        int n = obstacleGrid[0].length;
        int[] dp = new int[n];

        // 如果终点有障碍物,直接返回0
        if(obstacleGrid[m-1][n-1] == 1 || obstacleGrid[0][0] == 1){
            return 0;
        }

        // 初始化,有障碍物,则后面的路径数皆为0
        for(int i=0;i<n&&obstacleGrid[0][i]==0;i++){
            dp[i]=1;
        }

        // dp[0]只需要处理一次,如果第一行第一列无障碍物,则dp[0]为1
        // 后续遍历时,如果其他行第一列有障碍物,则dp[0]=0,而之后的遍历中,dp[0]一直都是0
        // 如果后续第一列无障碍物,则dp[0]=1也一直不会改变
       
        for(int i=1;i<m;i++){
            for(int j=0;j<n;j++){
                if(obstacleGrid[i][j] == 1){
                    // 如果其他行第一列有障碍物,则dp[0]=0
                   dp[j]=0;
                }else if(j>0){
                     dp[j]=dp[j-1] + dp[j];
                }
            }
        }
        return dp[n-1];
    }
}

这里对dp[0]的处理有些巧妙。

343. 整数拆分

在这里插入图片描述

class Solution {
    public int integerBreak(int n) {
        // dp[i]是正整数i可拆分出的最大乘积
        // dp[i] = max{dp[j]*dp[i-j]} j>0且j<i
        // dp[2]=1 dp[3]=2
        int[] dp = new int[n+1];
        dp[2]=1;
        for(int i=3;i<=n;i++){
            int sum=0;
            // 后半部分就开始重复了 1x4 2x3 3x2 4x1
            for(int j=1;j<=i/2;j++){                
                sum = Math.max(sum,Math.max(j,dp[j])*Math.max(dp[i-j],i-j));
            }
            dp[i] = sum;
        }
        return dp[n];
    }
}

其中的Math.max(j,dp[j]) 其实直接用j即可,但是我无法理解,所以就没改

96.不同的二叉搜索树

在这里插入图片描述

在这里插入图片描述

class Solution {
    public int numTrees(int n) {
        // gn = g0 * gn-1 + g1 * gn-2
        int[] dp = new int[n+1];
        if(n<=2){
            return n;
        }
        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];
    }
}

376. 摆动序列(特殊的自定义二维dp)

在这里插入图片描述
强行用动态规划做了出来,将升序和降序分成两行,拼凑出了dp[i][j]

class Solution {
    public int wiggleMaxLength(int[] nums) {
        // dp[0][i] 到达i元素,以降序为结尾的 摆动序列 的最大长度
        // dp[1][i] 到达i元素,以升序为结尾的 摆动序列 的最大长度

        // 到达i元素后,若是升序,可拼凑到降序结尾的摆动序列,即之前降序为结尾的摆动序列最大长度+1,则 dp[1][i] = dp[0][i-1] + 1; dp[0][i]则不变
        // 同理 若是降序 dp[0][i] = dp[1][i-1]+1;dp[1][i]不变
        // 若是相等,全不变

        // 初始化,dp[0][0]=1, dp[0][1]=1;

        int[][] dp = new int[2][nums.length];
        dp[0][0]=1;
        dp[1][0]=1;

        for(int i=1;i<nums.length;i++){

            // 若是升序
            if(nums[i]>nums[i-1]){
                dp[1][i] = dp[0][i-1] + 1;
                dp[0][i] = dp[0][i-1];
            }else if(nums[i]<nums[i-1]){
                dp[0][i] = dp[1][i-1]+1;
                dp[1][i] = dp[1][i-1];
            }else{
                dp[0][i] = dp[0][i-1];
                dp[1][i] = dp[1][i-1];
            }
        }

        return Math.max(dp[0][nums.length-1],dp[1][nums.length-1]);

    }
}

基于此,直接将空间优化,因为只使用到了两个数,定义两个变量即可

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int up=1;
        int down=1;
        for(int i=1;i<nums.length;i++){
            // 若是升序
            if(nums[i]>nums[i-1]){
                up = down + 1;
            }else if(nums[i]<nums[i-1]){
                down=up+1;
            }
        }
        return Math.max(down,up);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

范大

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值