LeetCode基础算法-动态规划

LeetCode基础算法-动态规划

LeetCode 动态规划


动态规划的核心步骤:

  1. 查看大问题的最优解能否使用小问题的最优解lai来解决。
  2. 如果能解决,那么总结出大问题的最优解和小问题最优解之间的关系。
  3. 将关系应用,解决问题。

1. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

分析:

  1. 假设我们到达楼顶需要n步,那么我们到达第n阶楼梯时,我们有那些方法呢?
  2. 我们有2种方法,就是从第n-2阶直接跨2阶到达第n阶;我们也可以从第n-1阶跨1阶到达n阶。
  3. 这就是典型的动态规划,dp[i] = dp[i-1] + dp[i-2].
  4. 我们首先需要求出1~n-1的方法,才能求出到达第n阶的方法。
    public int climbStairs(int n) {
        if (n == 1) return 1;
        int[] dp = new int[n];
        dp[0] = 1;
        dp[1] = 2;
        for (int i = 2; i < n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n - 1];

    }

2. 最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

输入: [-2,1,-3,4,-1,2,1,-5,4],
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:

如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。

解题思路:
下面介绍动态规划的做法,复杂度为 O(n)。
步骤 1:令状态 dp[i] 表示以 A[i] 作为末尾的连续序列的最大和(这里是说 A[i]必须作为连续序列的末尾)。
步骤 2:做如下考虑:因为 dp[i] 要求是必须以 A[i] 结尾的连续序列,那么只有两种情况:
这个最大和的连续序列只有一个元素,即以 A[i] 开始,以 A[i] 结尾。
这个最大和的连续序列有多个元素,即从前面某处 A[p] 开始 (p

对第一种情况,最大和就是 A[i] 本身。
对第二种情况,最大和是 dp[i-1]+A[i]。

于是得到状态转移方程:
dp[i] = max{A[i], dp[i-1]+A[i]}

这个式子只和 i 与 i 之前的元素有关,且边界为 dp[0] = A[0],由此从小到大枚举 i,即可得到整个 dp 数组。接着输出 dp[0],dp[1],...,dp[n-1] 中的最大子即为最大连续子序列的和。

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

        }
        return result; 
    }

3. 打家劫舍

你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

解题思路:
下面介绍动态规划的做法,复杂度为 O(n)。
步骤 1:令状态 dp[i] 表示以 A[i]作为被劫持房间的最大值。
步骤 2:做如下考虑:因为 dp[i]代表劫持到第i个房间的最打值,那么只有两种情况:
劫持该房间。
不劫持到该房间

对第一种情况,最大值就是 dp[i-2]+A[i] 本身。
对第二种情况,最大和是 dp[i-1]。

于是得到状态转移方程:
dp[i] = max{dp[i-2]+A[i], dp[i-1]}

这个式子只和 i 与 i 之前的元素有关,且边界为 dp[0] = A[0],dp[1]=max(dp[0],dp[1])由此从小到大枚举 i,即可得到整个 dp 数组。

    public int rob(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        if (nums.length == 1) {
            return nums[0];
        }
        int[] dp = new int[nums.length];
        dp[0] = nums[0];
        dp[1] = Math.max(dp[0], nums[1]);
        for (int i = 2; i < nums.length; i++) {
            dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i]);
        }
        return dp[nums.length - 1];
    }


4. 买卖股票的最佳时机

给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来算你所能获取的最大利润。
注意你不能在买入股票前卖出股票。

示例 1:
输入: [7,1,5,3,6,4]
输出: 5
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格。

示例 2:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

解题思路:

    public int maxProfit(int[] prices) {
        if (prices == null || prices.length <= 1) {
            return 0;
        }
        int[] profits = new int[prices.length];
        profits[0] = 0;
        for (int i = 1; i < profits.length; i++) {
            profits[i] = profits[i] - profits[i - 1];
        }
        int max = 0;
        int temp = 0;
        for (int i = 1; i < profits.length; i++) {
            if (temp + profits[i] > 0) {
                temp += profits[i];
            } else {
                temp = 0;
            }
            max = Math.max(temp, profits[i]);
        }
        return max;
    }

5. 零钱兑换

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:

输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。

解题思路:

  1. 使用动态规划的思想来解决问题.
  2. 11 = min{1+f(11-1),1+f(11-2),1+f(6)}
  3. 大问题的最优解可以根据小问题的最优解解答。
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        dp[0] = 0;
        // 注意此处赋值的目的是为了后面的判断。
        for (int i = 1; i <= amount; i++) {
            dp[i] = amount + 1;
        }
        for (int i = 1; i <= amount; i++) {
            for (int coin : coins) {
                if (coin <= i) {
                    int v = 1 + dp[i - coin];
                    if (dp[i] > v) {
                        dp[i] = v;
                    }
                }
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }

6. 零钱兑换II

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。

注意: 你可以假设

0 <= amount (总金额) <= 5000
1 <= coin (硬币面额) <= 5000
硬币种类不超过500种
结果符合32位符号整数
示例 1:

输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

解题思路:

  1. 动态规划。

    public int change(int amount, int[] coins) {
        if (amount == 0) return 1;
        if (coins == null || coins.length == 0 || amount < 0) return 0;
        int[] dp = new int[amount + 1];
        // 此处的赋值保证了dp[1] dp[2] dp[5] =1;
        dp[0] = 1;
        for (int i = coins.length - 1; i >= 0; i--) {
            for (int j = 0; j <= amount; j++) {
                if (j - coins[i] >= 0) {
                    dp[j] += dp[j - coins[i]];
                } else {
                    dp[j] = dp[j];
                }
            }
        }
        return dp[amount];
    }


你可能感兴趣的:(LeetCode基础算法-动态规划)