【基础算法】动态规划总结

动态规划:Dynamic programming,DP

枚举不理想的情况考虑采用动态规划

①原问题与子问题、②动态规划的状态、③边界状态结值、④状态转移方程。


1、爬楼梯 lc70

  • 题目:向上走1阶楼梯或者2阶楼梯,n阶楼梯有几种爬楼方式。
  • 算法:动态规划,斐波那契数列
  • 分析:每次只能上1或者2步,第 i 阶楼梯的爬法为 i-1 和 i-2的和。

   

  • 代码
 dp[1] = 1;
 dp[2] = 2;
 for(int i = 0; i < m; i++){
    dp[i] = dp[i-1] + dp[i-2];
 }
// 斐波那契数列:1、1、2、3、5、8... 
public int climbStairs(int n) {
        if(n == 1 || n == 2) return n;
        int a = 1, b = 2, num = 0;
        for(int i = 3; i <= n; i++){
            num = a + b;
            a = b;
            b = num;
        }
        return num;
    }

2、打家劫舍 lc198

  • 题目:每个房间都有数量不等的财宝,相邻两个房间盗取则触发警报,不触发警报可以获取多少财宝。
    • 如:5、2、6、3、1、7,最后的结果选5、6、7之和18;
  • 算法:动态规划;
  • 分析:不用管前面是怎样的一个过程,只需要保证dp[i] 是最大的,并且做相邻max比较;

   

  • 代码
    class Solution {
        public int rob(int[] nums) {
            int n = nums.length;
            if (n <= 1) return n == 0 ? 0 : nums[0];
            int[] dp = new int[n];
            dp[0] = nums[0];
            dp[1] = Math.max(nums[0], nums[1]);
            for (int i = 2; i < n; i++) {
                dp[i] = Math.max((dp[i - 2] + nums[i]), dp[i - 1]);
            }
            return dp[n - 1];
        }
    }

3、最大字段和 lc53

  • 题目:给定一个数组,求数组连续数组最大字段和;
  • 算法:暴力枚举、动态规划;
  • 分析:从开始往后加,如果包含nums[i]>dp[i-1],则包含,否则从新开始;

    public int maxSubArray(int[] nums) {
        int n = nums.length;
        if (n <= 1) return n == 0 ? 0 : nums[0];
        int[] dp = new int[n];
        dp[0] = nums[0];
        int result = dp[0];
        for (int i = 1; i < n; i++) {
            dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
            result = Math.max(result, dp[i]);
        }
        return result;
    }

4、找零钱 lc322

  • 题目:不同面值的钞票,采用最少的钞票组成目标金额
  • 思考:贪心不行,如1、2、5、7、10,先选10再选2,再选2,最后是3张,其实2*7就可以;
  • 算法:动态规划
  • 分析:边界值为dp[0] = 0,计算从i = 1 到 i = 14的所需要硬币的最小张数。

 

class Solution {
    public int coinChange(int[] coins, int amount) {
        int key = amount + 1;
        int []dp = new int[amount + 1];
        Arrays.fill(dp, key);
        dp[0] = 0;
        for(int i = 1; i <= amount; i++){
            for(int j = 0; j < coins.length; j++){
                if(i >= coins[j]){
                    dp[i] = Math.min(dp[i], dp[i - coins[j]] + 1);
                }     
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
}

5、三角形最小路径和 lc120

  • 题目:二维数组保存了三角形,求数字三角形从顶端到底端和最小的路径之和,每次可以向下走相邻的两个位置。
  • 思考:自顶向下和自底向上之间有没有区别?自底向上只需要考虑下方、右下方;自顶向下所需要判断的条件更多,计算量更大。采用自底向上的方式代码量会更小。
  • 分析:
    • dp[i][j]表示含第i行第j列元素的最小路径和
    • dp[i][j]=min(dp[i+1][j],dp[i+1][j+1])+triangle[i][j]
    • 二维数组在初始化时默认为全0

    public int minimumTotal(List<List<Integer>> triangle) {
        if (triangle == null || triangle.size() == 0) {
            return 0;
        }
        int[][] dp = new int[triangle.size() + 1][triangle.size() + 1];
        for (int i = triangle.size() - 1; i >= 0; i--) {
            List<Integer> row = triangle.get(i);
            for (int j = row.size() - 1; j >= 0; j--) {
                dp[i][j] = Math.min(dp[i+1][j+1],dp[i+1][j]) + row.get(j);
            }
        }
        return dp[0][0];
    }

拓展:采用一维的dp可否实现?采用自顶向下的代码如何实现?

6、三角形最小路径和 lc 64

  • 题目:已知一个二维数组,其中存储了非负整数,找从左上角到右下角的路径,使得路径的和最小。
  • 思考:只能向右下角走,动态规划的基础形态,从右下角向左上角记性累加,加较小的数字。
  • 分析:①原问题与子问题:到当前单元的最短路径②动态规划状态:状态单一,右下比较相加③边界值:row和col时的判断④状态转义方程 grid[i][j] += Math.min(grid[i][j + 1], grid[i + 1][j]);
  • 代码:
    public static int minPathSum(int[][] grid) {
        int col = grid.length;
        int row = grid[0].length;
        for (int i = col - 1; i >= 0; i--) {
            for (int j = row - 1; j >= 0; j--) {
                if (i == col - 1 && j == row - 1) {
                } else if (i == col - 1)
                    grid[i][j] += grid[i][j + 1];
                else if (j == row - 1)
                    grid[i][j] += grid[i + 1][j];
                else
                    grid[i][j] += Math.min(grid[i][j + 1], grid[i + 1][j]);
            }
        }
        return grid[0][0];
    }

7、地牢游戏 lc174

  • 题目:二维数组,左上角骑士、右下角公主,二维数组存储整数,正数可以增加生命值,负数会减少骑士的生命值,问骑士初始生命值为多少的时候,才可以保证骑士在行走的过程中,生命值至少为1。
  • 思考:为什么只可以从右下向左上推,而不能反过来(思考:因为求的是骑士初始情况时的血量同时走到最后一格的生命值为1,这是一种思考)。
    • ①从右下角向左上推的时候,每一格存储的是骑士到达次处所需要的最小生命值,若所需要生命值<1,则认为是1。
    • ②动态规划过程模式较为单一,比较右方下方,即min{dungeon[i][j+1] - dungeon[i][j] ,dungeon[i+1][j] - dungeon[i][j] },可以化简再比较1,若小于1,则认为是1。
    • ③边界状态:两侧判断便捷。
  • 代码:
public static int calculateMinimumHP(int[][] dungeon) {
        int col = dungeon.length;
        int row = dungeon[0].length;
        for (int i = col - 1; i >= 0; i--) {
            for (int j = row - 1; j >= 0; j--) {
                if (i == col - 1 && j == row - 1) {
                    dungeon[i][j] = Math.max(1, 1 - dungeon[i][j]);
                } else if (i == col - 1) {
                    dungeon[i][j] = Math.max(1, dungeon[i][j + 1] - dungeon[i][j]);
                } else if (j == row - 1) {
                    dungeon[i][j] = Math.max(1, dungeon[i + 1][j] - dungeon[i][j]);
                } else
                    dungeon[i][j] = Math.max(1, Math.min(dungeon[i + 1][j], dungeon[i][j + 1]) - dungeon[i][j]);
            }
        }
        return dungeon[0][0];
    }

猜你喜欢

转载自blog.csdn.net/smartzzg/article/details/106660589