代码随想录|动态规划

斐波那契数列

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

1. 确定dp[i]的含义:第i个斐波那契数

2. 递推公式:dp[i] = dp[i-1] + dp[i-2]

3. dp数组如何初始化:dp[0] = 1, dp[1] = 1

4. 确定遍历顺序:从前往后遍历

5. 打印dp数组:用来debug

爬楼梯

// 常规方式
public int climbStairs(int n) {
    int[] dp = new int[n + 1];
    dp[0] = 1;
    dp[1] = 1;
    for (int i = 2; i <= n; i++) {
        dp[i] = dp[i - 1] + dp[i - 2];
    }
    return dp[n];
}

1. 确定dp[i]的含义:达到第i阶,有dp[i]种方法

2. 递推公式:dp[i] = dp[i-1] + dp[i-2]

3. dp数组如何初始化:dp[1] = 1, dp[2] = 2

4. 确定遍历顺序:从前往后遍历

5. 打印dp数组:用来debug

使用最小花费爬楼梯

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        int len = cost.length;
        int[] dp = new int[len + 1];

        // 从下标为 0 或下标为 1 的台阶开始,因此支付费用为0
        dp[0] = 0;
        dp[1] = 0;

        // 计算到达每一层台阶的最小费用
        for (int i = 2; i <= len; i++) {
            dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        }

        return dp[len];
    }
}

1. 确定dp[i]的含义:达到第i层,需要的最小花费是dp[i]

2. 递推公式:dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])

可以由i-1的位置跳了一步和i-2的位置跳了两步到了i的位置,然后再加上跳i-1或者跳i-2本身的花费就得到了dp[i] 然后再选择最少的花费

3. 初始化dp[0] = 0, dp[1] = 0 最开始站在0或者1都不需要花费体力,所以都是初始化为0

4. 确定遍历顺序:从前往后遍历

不同路径

 class Solution{
    
     public static int uniquePaths(int m, int n) {
        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];
    }
 }

1. 确定dp[i][j]的含义:从(0,0)走到(i,j)有多少条路径

2. 递推公式:

dp[i-1][j] 走一步到dp[i][j] 

dp[i][j-1]走一步到dp[i][j]

所以 dp[i][j] = dp[i-1][j] + dp[i][j-1]

3. 初始化:分别需要在两个方向上进行初始化

dp[i][0] = 1

dp[0][j] = 1

4. 遍历顺序:从左向右,从上往下遍历

 

 

不同路径 II 

 

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

        //如果在起点或终点出现了障碍,直接返回0
        if (obstacleGrid[m - 1][n - 1] == 1 || obstacleGrid[0][0] == 1) {
            return 0;
        }

        for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) {
            dp[i][0] = 1;
        }
        for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) {
            dp[0][j] = 1;
        }

        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = (obstacleGrid[i][j] == 0) ? dp[i - 1][j] + dp[i][j - 1] : 0;
            }
        }
        return dp[m - 1][n - 1];
    }
}

1. 确定dp[i][j]的含义:从(0,0)走到(i,j)有多少种不同的路径

2. 递推公式:

dp[i-1][j] 走一步到dp[i][j] 

dp[i][j-1]走一步到dp[i][j]

所以 if obs[i][j] == 0 (没有障碍物的情况下)

dp[i][j] = dp[i-1][j] + dp[i][j-1]

3. 初始化:分别需要在两个方向上进行初始化

dp[i][0] = 1

dp[0][j] = 1

一旦遇到障碍物,for循环就终止

for (int i = 0; i < m && obs[i][0] == 0; i++) dp[i][0] = 1;
for (int j = 0; j < n && obs[0][j] == 0; j++) dp[0][j] = 1;

4. 遍历顺序:从左向右,从上往下遍历

整数拆分

class Solution {
    public int integerBreak(int n) {
        //dp[i] 为正整数 i 拆分后的结果的最大乘积
        int[] dp = new int[n+1];
        dp[2] = 1;
        for(int i = 3; i <= n; i++) {
            for(int j = 1; j <= i-j; j++) {
                // 这里的 j 其实最大值为 i-j,再大只不过是重复而已,
                //并且,在本题中,我们分析 dp[0], dp[1]都是无意义的,
                //j 最大到 i-j,就不会用到 dp[0]与dp[1]
                dp[i] = Math.max(dp[i], Math.max(j*(i-j), j*dp[i-j]));
                // j * (i - j) 是单纯的把整数 i 拆分为两个数 也就是 i,i-j ,再相乘
                //而j * dp[i - j]是将 i 拆分成两个以及两个以上的个数,再相乘。
            }
        }
        return dp[n];
    }
}

1. dp数组含义:对i进行拆分,最大的乘积为dp[i]

2. 递推公式:

j * (i-j): 是把i拆成两个数

j * dp[i-j]:关于为什么j*dp【i-j】不用考虑拆 j

3. 初始化:dp[0] = 0, dp[1] = 0, dp[2] = 1

 

不同的二叉搜索树

class Solution {
    public int numTrees(int n) {
        //初始化 dp 数组
        int[] dp = new int[n + 1];
        //初始化0个节点和1个节点的情况
        dp[0] = 1;
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            for (int j = 1; j <= i; j++) {
                //对于第i个节点,需要考虑1作为根节点直到i作为根节点的情况,所以需要累加
                //一共i个节点,对于根节点j时,左子树的节点个数为j-1,右子树的节点个数为i-j
                dp[i] += dp[j - 1] * dp[i - j];
            }
        }
        return dp[n];
    }
}

1.dp数组的含义:输入i,输出dp[i]种不同的二叉搜索树

2. 递推公式:dp[i] += dp[j-1] * dp[i-j]

dp[i] = 所有左子树的可能 * 所有右子树的可能

左子树节点数量 + 右子树节点数量 + 头节点数量(1) = i(总数量)

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

因为是二叉搜索树 所以以j来遍历 左边有j-1个节点 右边有i-j个节点 因为左边所有的节点都会比j小 而右边所有的节点都会比j大

dp[3] = dp[0] * dp[2] + dp[1] * dp[1] + dp[2] * dp[0]

3. 初始化:dp[0] = 0, dp[1] = 1

4. 遍历顺序:从小到大

0-1背包问题理论基础

public class BagProblem {
    public static void main(String[] args) {
        int[] weight = {1,3,4};
        int[] value = {15,20,30};
        int bagSize = 4;
        testWeightBagProblem(weight,value,bagSize);
    }

    /**
     * 动态规划获得结果
     * @param weight  物品的重量
     * @param value   物品的价值
     * @param bagSize 背包的容量
     */
    public static void testWeightBagProblem(int[] weight, int[] value, int bagSize){

        // 创建dp数组
        int goods = weight.length;  // 获取物品的数量
        int[][] dp = new int[goods][bagSize + 1];

        // 初始化dp数组
        // 创建数组后,其中默认的值就是0
        for (int j = weight[0]; j <= bagSize; j++) {
            dp[0][j] = value[0];
        }

        // 填充dp数组
        for (int i = 1; i < weight.length; i++) {
            for (int j = 1; j <= bagSize; j++) {
                if (j < weight[i]) {
                    /**
                     * 当前背包的容量都没有当前物品i大的时候,是不放物品i的
                     * 那么前i-1个物品能放下的最大价值就是当前情况的最大价值
                     */
                    dp[i][j] = dp[i-1][j];
                } else {
                    /**
                     * 当前背包的容量可以放下物品i
                     * 那么此时分两种情况:
                     *    1、不放物品i
                     *    2、放物品i
                     * 比较这两种情况下,哪种背包中物品的最大价值最大
                     */
                    dp[i][j] = Math.max(dp[i-1][j] , dp[i-1][j-weight[i]] + value[i]);
                }
            }
        }

        // 打印dp数组
        for (int i = 0; i < goods; i++) {
            for (int j = 0; j <= bagSize; j++) {
                System.out.print(dp[i][j] + "\t");
            }
            System.out.println("\n");
        }
    }
}

1. dp数组的定义:

dp[i][j]: 从[0,i]中任意取物品 放进重量为j的背包 价值总和最大为多少

不放物品i:dp[i-1][j]

放物品i:dp[i-1][j-weight[i]] + value[i]

2. dp公式

dp[i][j] = max(dp[i-1][j], dp[i-1][j-weight[i]] + value[i]]

j - weight[i]可以理解为要为物品i留出空间来放i

3. 初始化:

从公式可以看出,i是由i-1推倒出来的

第一列初始化为0,因为背包容量为0。

第一行初始化为value[0],因为只放物品0,所以是物品0的value[0]

4.遍历顺序:

无论是先遍历背包还是先遍历物品,dp[i][j]都是由它正上方的值和左上方的值推导出来的,所以两种遍历顺序都可以

背包问题理论基础2

 public static void main(String[] args) {
        int[] weight = {1, 3, 4};
        int[] value = {15, 20, 30};
        int bagWight = 4;
        testWeightBagProblem(weight, value, bagWight);
    }

    public static void testWeightBagProblem(int[] weight, int[] value, int bagWeight){
        int wLen = weight.length;
        //定义dp数组:dp[j]表示背包容量为j时,能获得的最大价值
        int[] dp = new int[bagWeight + 1];
        //遍历顺序:先遍历物品,再遍历背包容量
        for (int i = 0; i < wLen; i++){
            for (int j = bagWeight; j >= weight[i]; j--){
                dp[j] = Math.max(dp[j], dp[j - weight[i]] + value[i]);
            }
        }
        //打印dp数组
        for (int j = 0; j <= bagWeight; j++){
            System.out.print(dp[j] + " ");
        }
    }

1. dp数组含义:

dp[j]:容量为j的背包的最大价值为dp[j]

2. 递推公式:

dp[j] = max(dp[j], dp[j - weight[i] + value[i]])

3.初始化:

dp[0] = 0;

4.遍历顺序:只能是先遍历物品再遍历背包

分割等和子集

class Solution {
    public boolean canPartition(int[] nums) {
        if(nums == null || nums.length == 0) return false;
        int n = nums.length;
        int sum = 0;
        for(int num : nums) {
            sum += num;
        }
        //总和为奇数,不能平分
        if(sum % 2 != 0) return false;
        int target = sum / 2;
        int[] dp = new int[target + 1];
        for(int i = 0; i < n; i++) {
            for(int j = target; j >= nums[i]; j--) {
                //物品 i 的重量是 nums[i],其价值也是 nums[i]
                dp[j] = Math.max(dp[j], dp[j - nums[i]] + nums[i]);
            }
           
            //剪枝一下,每一次完成內層的for-loop,立即檢查是否dp[target] == target,優化時間複雜度(26ms -> 20ms)
            if(dp[target] == target)
                return true;
        }
        return dp[target] == target;
    }
}

1.dp数组含义:容量为j的背包 最大价值为dp[j]

target = sum/2

dp[target] == target

2. 递推公式:

dp[j] = max(dp[j], dp[j - weight[i]] + value[i])

因为这道题的重量和价值都是相同的,所以公式为dp[j] = max(dp[j], dp[j - nums[i] ]+ nums[i])

3. 初始化

dp[0] = 0

4. 遍历顺序:先遍历物品再遍历背包

最后一块石头的重量II

class Solution {
    public int lastStoneWeightII(int[] stones) {
        int sum = 0;
        for (int i : stones) {
            sum += i;
        }
        int target = sum >> 1;
        //初始化dp数组
        int[] dp = new int[target + 1];
        for (int i = 0; i < stones.length; i++) {
            //采用倒序
            for (int j = target; j >= stones[i]; j--) {
                //两种情况,要么放,要么不放
                dp[j] = Math.max(dp[j], dp[j - stones[i]] + stones[i]);
            }
        }
        return sum - 2 * dp[target];
    }
}

1.dp数组含义:容量为j的背包 最大价值为dp[j]

2. 递推公式:因为这道题的重量和价值都是相同的,所以公式为dp[j] = max(dp[j], dp[j - stone[i] ]+ stone[i]) 

max(不放石头i,放石头i)

3. 初始化

dp[0] = 0

尽量初始化为最小的非负数,以防止覆盖

dp[3000/2 + 1] = dp[1501] =0

4. 先遍历物品再遍历背包,遍历背包时要从大到小遍历,因为每个物品只能使用一次

(sum - dp[target]) - dp[target]

目标和

1.dp数组的含义:满容为j,有dp[j]种方法

2. 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值