斐波拉契數列
“斐波拉契數列”問題是認識動態規劃非常好的例子。
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?,F在你有兩個符號
+
和-
。對于數組中的任意一個整數,你都可以從+
或-
中選擇一個符號添加在前面。返回可以使最終數組和為目標數 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。
注意:
- 數組的長度不會超過20,并且數組中的值全為正數。
- 初始的數組的和不會超過1000。
- 保證返回的最終結果為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
http://www.lxweimin.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);
}
}
(本節完)