LeetCode 专题:动态规划

斐波拉契数列

“斐波拉契数列”问题是认识动态规划非常好的例子。

LeetCode 第 70 题:Climbing Stairs

提示:斐波拉契数列,画出树形结构,发现大量重叠子问题,所以用动态规划。

LeetCode 第 120 题:Triangle

提示:状态 + 状态转移方程。

Python 代码:

class Solution:
    def minimumTotal(self, triangle):
        """
        :type triangle: List[List[int]]
        :rtype: int
        """
        l = len(triangle)
        if l == 0:
            return 0
        dp = triangle[-1]
        for i in range(l - 2, -1, -1):
            for j in range(len(triangle[i])):
                dp[j] = min(dp[j], dp[j + 1]) + triangle[i][j]
        return dp[0]

LeetCode 第 64 题 :Minimum Path Sum 最小路径和

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

提示:状态 + 状态转移方程。也可以用组合数来做。

LeetCode 第 343 题: Integer Break

提示:关键在“将其拆分为至少两个正整数的和”,还可以使用贪心算法。

Python 代码:

class Solution:
    def integerBreak(self, n):
        """
        :type n: int
        :rtype: int
        """
        product = [0] * (n + 1)
        product[1] = 1
        for i in range(2, n + 1):
            product_max = 0
            for j in range(1, i):
                product_max = max(j * product[i - j], product_max, j * (i - j))
            product[i] = product_max
        return product[n]

LeetCode 第 279 题:完全平方数(特别重要)

传送门:279. 完全平方数。提示:1、BFS;2、动态规划,状态就是题目中要问的问题。

Python 代码:


LeetCode 第 91 题:解码方法(特别重要)

提示:注意数组下标越界问题。

class Solution:

    def numDecodings(self, s):
        l = len(s)
        if l == 0:
            return 0

        if l == 1:
            return 1 if s[0] != '0' else 0
        dp = [0 for _ in range(l)]
        dp[0] = 1 if s[0] != '0' else 0
        for i in range(1, l):
            if s[i] != '0':
                # 如果不是 '0' ,那么 s[i] 就可以编码,所以 cur 就至少是 dp[i-1]
                dp[i] += dp[i - 1]
            if 9 < int(s[i - 1:i + 1]) < 27:
                # 可以和前面的数字组成一个编码
                if i - 2 < 0:
                    # 12
                    dp[i] += 1
                else:
                    dp[i] += dp[i - 2]
        return dp[l - 1]

不同的路径

LeetCode 第 62 题:不同路径

提示:还可以用组合数公式。

LeetCode 第 63 题:不同路径 II

提示:分类讨论。

打家劫舍系列

LeetCode 第 198 题:House Robber

提示:状态转移,如果使用“滚动数组”,可以把空间复杂度降到 。

Python 代码:掌握“滚动数组”的写法

class Solution:
    def rob(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        
        size = len(nums)
        
        if size == 0:
            return 0

        if size <= 2:
            return max(nums)
        
        pre = nums[0]
        cur = max(nums[0], nums[1])
        
        for i in range(2, size):
            temp = cur # 因为 cur 会被覆盖,所以先把 cur 存一下,最后再赋值给 pre
            cur = max(cur, pre + nums[i])
            pre = temp
        return cur

LeetCode 第 213 题:House Robber II

提示:基于上一个问题。

LeetCode 第 337 题:House Robber III

提示:递归求解,先写递归终止条件。

Java 代码:

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */

class Solution {
    public int rob(TreeNode root) {
        if (root == null) {
            return 0;
        }
        int val = root.val;
        if(root.left != null){
            val+= rob(root.left.left) + rob(root.left.right);
        }

        if(root.right != null){
            val+= rob(root.right.left) + rob(root.right.right);
        }
        return Math.max(val, rob(root.left) + rob(root.right));
    }
}

LeetCode 第 309 题:Best Time to Buy and Sell Stock with Cooldown

(状态机解决)

背包问题—— Knapsack01(6题)

LeetCode 第 416 题:分割等和子集

提示:0-1 背包问题,思路是:物品一个一个加进来。

状态:dp[i][j]:考虑索引是 [0,i] 这个区间的物品是否能够填充容量为 j 的背包。

Python 代码:这是用二维数组的写法,试试看能不能用一维数组写出来

class Solution:

    def canPartition(self, nums):
        """
        :type nums: List[int]
        :rtype: bool
        """

        l = len(nums)
        if l == 0:
            return False
        s = sum(nums)
        half = s // 2
        if s % 2 == 1:
            return False

        dp = [[0 for _ in range(half + 1)] for _ in range(l)]
        # 先填第 1 行,即 nums[0] 这个物品,是不是能够填满 0,1,。。。,half 的背包
        for i in range(half + 1):
            dp[0][i] = False if nums[0] != i else True
        # 再填后面几行
        for i in range(1, l):
            for j in range(half + 1):
                # 不放这个物品 、 放这个物品
                dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i]]

        return dp[-1][-1]

LeetCode 第 322 题 :Coin Change

提示:抓住“你可以认为每种硬币的数量是无限的”。

Python 代码:掌握 BFS 的写法,关键是画图,还有里面的入队逻辑不能错,同类问题还有完全平方数

class Solution:
    def coinChange(self, coins, amount):
        """
        :type coins: List[int]
        :type amount: int
        :rtype: int
        """
        # 特判 [2147483647]
        # 2
        if amount <= 0:
            return 0
        coins.sort()
        if coins[0] > amount:
            return -1
        # 不要用数组,用哈希表
        # marked = [False for _ in range(amount)]
        marked = set()

        queue = [(0, amount)]
        while queue:
            # print(queue)
            level, top = queue.pop(0)
            level += 1
            for coin in coins:
                residue = top - coin
                if residue == 0:
                    return level
                if residue > 0 and residue not in marked:
                    queue.append((level, residue))
                    marked.add(residue)
                if residue < 0:
                    # 有了前面排序做保证
                    break
        return -1

Java 代码:动态规划

public class Solution {

    public int coinChange(int[] coins, int amount) {
        int len = coins.length;
        if (amount == 0) {
            return 0;
        }
        if (coins == null || len == 0) {
            return -1;
        }

        // 要把 0 算进去,这是基本情况,所以开辟 coins 这么多空间的
        // 组成当前的总价值需要的最少硬币数
        int[] dp = new int[amount + 1];
        // dp 问题的套路,把初值先算出来,因为递归到底的时候,可能到这个地方
        dp[0] = 0;

        for (int i = 1; i <= amount; i++) {
            int minCount = Integer.MAX_VALUE;
            for (int j = 0; j < coins.length; j++) {
                if (i - coins[j] >= 0 && dp[i - coins[j]] != -1) {
                    minCount = Integer.min(dp[i - coins[j]] + 1, minCount);
                }
            }
            dp[i] = minCount == Integer.MAX_VALUE ? -1 : minCount;
        }
        return dp[amount];
    }

    public static void main(String[] args) {
        Solution solution = new Solution();

        int[] coins = {1};
        int amount = 0;
        int coinChange = solution.coinChange(coins, amount);
        System.out.println(coinChange);
    }
}

LeetCode 第 377 题 :Combination Sum IV

给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

Java 代码:

class Solution {
    public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target + 1];
        // 这一步很关键,想想为什么 dp[0] 是 1
        // 因为 0 表示空集,空集和它"前面"的元素凑成一种解法,所以是 1
        // 这一步要加深体会
        dp[0] = 1;
        for (int i = 1; i < target + 1; i++) {
            for (int num : nums) {
                if (i >= num) {
                    dp[i] = dp[i] + dp[i - num];
                }
            }
        }
        return dp[target];
    }
}

Java 代码:

public int combinationSum4(int[] nums, int target) {
    int[] memo = new int[target + 1];
    memo[0] = 1;
    for (int i = 0; i < target; i++) {
        for (int num : nums) {
            if (i + num <= target) {
                memo[i + num] += memo[i];
            }
        }
    }
    return memo[target];
}

LeetCode 第 474 题:Ones and Zeroes

提示:二维背包问题,数组有三维,可以降到一维。

LeetCode 第 139 题:Word Break(重点)

提示:用“记忆化递归”和“动态规划”都做一下。先把单词列表放到哈希表里,然后先判处。

LeetCode 第 494 题:Target Sum

传送门:494. 目标和。

给定一个非负整数数组,a1, a2, ..., an, 和一个目标数,S。现在你有两个符号 +-。对于数组中的任意一个整数,你都可以从 +-中选择一个符号添加在前面。

返回可以使最终数组和为目标数 S 的所有添加符号的方法数。

示例 1:

输入: nums: [1, 1, 1, 1, 1], S: 3
输出: 5
解释: 

-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3

一共有5种方法让最终目标和为3。

注意:

  1. 数组的长度不会超过20,并且数组中的值全为正数。
  2. 初始的数组的和不会超过1000。
  3. 保证返回的最终结果为32位整数。

提示:1、回溯;2、0-1 背包问题。要处理一些细节。

例题1:LeetCode 第 300 题:Longest Increasing Subsequence

提示:1、动态规划;2、使用二分法。

练习1:LeetCode 第 376 题:Wiggle Subsequence

提示:1、状态机;2、贪心;

最长公共子序列问题(Longest Common Sequence)

地下城游戏(困难)

最长连续子数组的和

《编程之法》 P10 字典排序

看看哪里用到了“滚动数组”的技巧,典型的一维动态规划问题

分割回文串问题

LeetCode 第 131 题:分割回文串1:dfs 或者动态规划

LeetCode 第 132 题:分割回文串2(困难):动态规划

LeetCode 第 62 题:最短的唯一路径(1)

思路:典型的动态规划问题。用一维的空间复杂度就可以写出来。

class Solution(object):
    def uniquePaths(self, m, n):
        """
        :type m: int
        :type n: int
        :rtype: int
        """
        if m == 0:
            return 0
        dp = [1] * n
        for row in range(m - 1):
            for col in range(1, n):
                dp[col] += dp[col-1]
        return dp[-1]

LeetCode 第 63 题:最短的唯一路径(2)

思路:典型的动态规划问题。用一维的空间复杂度就可以写出来。

class Solution(object):
    def uniquePathsWithObstacles(self, obstacleGrid):
        """
        :type obstacleGrid: List[List[int]]
        :rtype: int
        """

        m = len(obstacleGrid)
        if m == 0:
            return 0
        n = len(obstacleGrid[0])

        if obstacleGrid[0][0] == 1:
            return 0

        dp = [0] * n
        # 这一步不要忘记了
        dp[0] = 1
        # 再写后面几行
        for row in range(m):
            for col in range(n):
                # 【就分下面这两种情况就可以了】
                if obstacleGrid[row][col] == 1:
                    dp[col] = 0
                elif col > 0:
                    dp[col] += dp[col - 1]
                else:
                    # 第 0 列不是 0 就是 1
                    # 0 的情况首先判断了
                    # 什么都不做
                    pass
        return dp[-1]

LeetCode 第 64 题:路径和最小

class Solution(object):
    def minPathSum(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        m = len(grid)
        if m == 0:
            return 0
        n = len(grid[0])

        for col in range(1, n):
            # 第 0 行特殊处理,不要忘记了
            grid[0][col] += grid[0][col - 1]
        for row in range(1, m):
            grid[row][0] += grid[row - 1][0]
            for col in range(1, n):
                grid[row][col] += min(grid[row - 1][col], grid[row][col - 1])
        return grid[-1][-1]

LeetCode 第 115 题:不同的子序列(困难)

传送门:115. 不同的子序列。

要求:给定一个字符串 S 和一个字符串 T,计算在 S 的子序列中 T 出现的个数。

一个字符串的一个子序列是指,通过删除一些(也可以不删除)字符且不干扰剩余字符相对位置所组成的新字符串。(例如,"ACE""ABCDE" 的一个子序列,而 "AEC" 不是)

分析:首先,空串就是 1 种情况。

使用二维动态规划。状态:到目前为止匹配的种数。

  • 这是一道典型的动态规划问题,并且还是二维的动态规划问题
  • 而且这个状态还比较难想,要结合具体的例子才能找到状态转移方程
  • 首先要注意到最特殊的情况:当 t == "" 为 true 的时候,不论 s 是什么,s 都有一个子串(那就是空字符串)能和此时的 t 匹配。
  • 因此 dp[i][0] = 1,所以 0 这个位置就被占据了,这就是为什么 dp 要设置成 dp[slen + 1][tlen + 1] 的原因。

参考资料:
https://leetcode.com/problems/distinct-subsequences/discuss/37320/Java-.
https://www.cnblogs.com/higerzhang/p/4133793.html
https://www.jianshu.com/p/510906b900ec

public class Solution2 {

    public int numDistinct(String s, String t) {
        int slen = s.length();
        int tlen = t.length();
        if (slen == 0 || tlen == 0) {
            return 0;
        }
        int[][] dp = new int[slen + 1][tlen + 1];
        for (int i = 0; i < slen; i++) {
            dp[i][0] = 1;
        }
        for (int i = 1; i < slen + 1; i++) {
            for (int j = 1; j < tlen + 1; j++) {
                dp[i][j] = dp[i - 1][j];
                if (s.charAt(i - 1) == t.charAt(j - 1)) {
                    dp[i][j] += dp[i - 1][j - 1];
                }
            }
        }
        return dp[slen][tlen];
    }

    public static void main(String[] args) {
        String s = "babgbag";
        String t = "bag";
        Solution2 solution = new Solution2();
        int numDistinct = solution.numDistinct(s, t);
        System.out.println(numDistinct);
    }
}

(本节完)

你可能感兴趣的:(LeetCode 专题:动态规划)