题目要求:
解析:因为dp[i] 只与dp[i-1] 和dp[i-2] 有关:
def climbstairs(len):
dp = 0
dp1 = 1
dp2 = 2
if len <= 2:
return len
for i in range(2, len):
dp = dp1 + dp2
dp1 = dp2
dp2 = dp
return dp
题目要求:
def rob(arr):
length = len(arr)
dp = 0
dp1 = 0
dp2 = 0
if length == 0:
return 0
if length == 1:
return arr[0]
for i in range(0, length):
dp = max(dp2 + arr[i], dp1)
dp2 = dp1
dp1 = dp
return dp
0-1背包问题:

def knapsack(weights, values, N, W):
"""
0-1背包对物品的迭代放在外层,里层的体积或价值逆向遍历
完全背包对物品的迭代放在里层,外层的体积或价值正向遍历
:param weights: 物品的重量数组
:param values: 物品的价值数组
:param N: 物品的个数
:param W: 背包的最大承受重量
:return:
"""
dp = [0] * (W + 1) # dp数组用来存储物品的最大价值,dp[i-1] 前 i-1 个物品的最大价值.
for i in range(1, N + 1): # 遍历前 i 个物品
w = weights[i - 1]
v = values[i - 1]
for j in range(W, w - 1, -1): # 这里,dp[j]需要满足的最大重量 j >= W-w, range()为左开右闭区间,所以需要: w-1
dp[j] = max(dp[j], dp[j - w] + v)
return dp[W]
完全背包问题

def knapsack2(weights, values, com_N, com_W): # 完全背包
"""
用动态规划来解决背包问题:
完全背包问题的状态转移方程:dp[i][j] = max(dp[i-1][j], dp[i][j-w] + v
:param weights: 物品的重量数组
:param values: 物品的价值数组
:param com_N: 物品的个数
:param com_W: 背包的最大承受重量
:return:
"""
dp = [[0] * (com_W + 1)] * (com_N + 1)
for i in range(1, com_N + 1): # 遍历前 i 个物品
w = weights[i - 1]
v = values[i - 1]
for j in range(1, com_W + 1):
if j >= w:
dp[i][j] = max(dp[i - 1][j], dp[i][j - w] + v)
else:
dp[i][j] = dp[i - 1][j]
return dp[com_N][com_W]
def knapsack3(weights, values, com_N, com_W): # 完全背包
"""
:param weights: 物品的重量数组
:param values: 物品的价值数组
:param com_N: 物品的个数
:param com_W: 背包的最大承受重量
:return:
"""
dp = [0] * (com_W + 1)
for i in range(1, com_N + 1): # 遍历前 i 个物品
w = weights[i - 1]
v = values[i - 1]
for j in range(w, com_W + 1):
dp[j] = max(dp[j], dp[j - w] + v)
return dp[com_W]
看下图找出状态转移函数:


//动态规划,0-1背包问题
public static int knapsack(int[] weight, int[] value, int N, int W){
int[][] dp = new int[N+1][W+1]; //java 统一赋值为0
//Arrays.fill(dp, 0);
System.out.println(dp);
for (int i = 1; i <=N ; i++) {
int w = weight[i-1];
int v = value[i-1];
for (int j = 1; j <= W; j++) {
if (j>=w) {
//0-1完全背包
//dp[i][j] = Math.max(dp[i-1][j], dp[i-1][j-w] + v);
//完全背包公式
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-w] + v);
}
else {
dp[i][j] = dp[i-1][j];
}
}
}
return dp[N][W];
}
public static void main(String[] args) {
int[] weights = {10, 3, 4, 5, 4};
int[] values = {24, 2, 9, 10, 9};
System.out.println(Solution.knapsack(weights, values, 5, 13));
}
完全背包和0-1背包输出的都是:28
字符串匹配(非动态规划),详细解析参考:Implement strStr()
Input: haystack = "hello", needle = "ll"
Output: 2
Input: haystack = "aaaaa", needle = "bba"
Output: -1
def find_substr(string, substring):
fri_index = string.find(substring, 0, len(string))
last_index = string.rfind(substring) # 0, len(string)默认
return fri_index, last_index
if __name__ == '__main__':
s = "this is string example....wow!!!"
subs = "is"
fri, last = find_substr(s, subs)
print("fri = {} last = {}".format(fri, last))
输出
fri = 2 last = 5
public int strStr(String haystack, String needle) {
if (needle.isEmpty()) return 0;
int sizeA = haystack.length(), sizeB = needle.length();
for (int i = 0; i <= sizeA - sizeB; i++) {
if (haystack.substring(i, i + needle.length()).equals(needle))
return i;
}
return -1;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
String_Solution solution = new String_Solution();
String string = "aaaaa";
String string2 = "bba";
int bool = solution.strStr(string, string2);
System.out.println(bool);
}
// indexOf 字符串中查找其子串第一次出现的位置,如果没有找到该子串,则返回-1
public int strStr2(String haystack, String needle) {
int index=haystack.indexOf(needle);
return index;
}
最长公共子序列:(Longest Common Subsequence)
Input: text1 = "abcde", text2 = "ace"
Output: 3
Explanation: The longest common subsequence is "ace" and its length is 3.
解析:
公共子序列不同于公共字串,公共子序列可以是不连续的,但是前后位置不变。
通过上图,我们可看出:两个序列的最长公共子序列的状态转移方程
d
p
[
i
]
[
j
]
=
{
d
p
[
i
−
1
]
[
j
−
1
]
+
1
,
i
f
:
i
=
j
m
a
x
(
d
p
[
i
−
1
]
[
j
]
,
d
p
[
i
]
[
j
−
1
]
)
,
i
f
:
i
≠
j
def longest_common_subsequence(text1, text2):
m = len(text1)
n = len(text2)
dp = [[0] * (n + 1)] * (m + 1)
for i in range(1, m + 1):
for j in range(1, n + 1):
if text1[i - 1] == text2[j - 1]:
dp[i][j] = dp[i - 1][j - 1] + 1
else:
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
return dp[m][n]
if __name__ == '__main__':
text = "acbde"
text0 = "ace"
value = longest_common_subsequence(text, text0)
print(value)
str1 = "abcbdab"
str2 = "bdcaba"
value2 = longest_common_subsequence(str1, str2)
print(value2)
输出:
3
4
public static int lengthOfLIS(String str, String substr) {
int m = str.length();
int n = substr.length();
int dp[][] = new int[m+1][n+1];
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (str.charAt(i-1)==substr.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1] + 1;
}
else {
dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[m][n];
}
public static void main(String[] args) {
String s1 = "abcbdab";
String s2 = "bdcaba";
System.out.println(Solution.lengthOfLIS(s1, s2));
}
完全平方数相加
分割类型题,动态规划的状态转移方程通常并不依赖相邻的位置,而是依赖于满足分割条件的位置。1 + min(dp[i-1], dp[i-4], dp[i-9] ...)。
Python代码
def num_squares(key):
dp = [99] * (key + 1) # 这里,99代指最大个数。
dp[0] = 0
for i in range(1, key + 1):
for j in range(1, i):
if j * j <= i: # 此处,展现的python中for循环的劣势
dp[i] = min(dp[i], dp[i - j * j] + 1)
else:
break
return dp[key]
print(num_squares(5))
java代码
import java.util.Arrays;
public class Solution {
public int numSquares(int n) {
int dp[] = new int[n+1];
/*
* 通过Arrays.fill方法就可以为每个元素赋予同样的值
* Arrays.fill(arr, 0, 2, 10);
* 0-2,不包括2的索引,打印的是10
*/
Arrays.fill(dp, 99);
dp[0] = 0;
for (int i = 1; i <= n; i++) {
for (int j = 1; j*j <= i; j++) {
dp[i] = Math.min(dp[i], dp[i-j*j] + 1);
}
}
return dp[n];
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int n = 5;
Solution solution = new Solution();
System.out.println(solution.numSquares(n));
}
}
输出:
2
股票交易类问题通常可以用动态规划来解决。对于稍微复杂一些的股票交易类问题,比如需要冷却时间或者交易费用,则可以用通过动态规划实现的状态机来解决。
1. Best Time to Buy and Sell Stock
题目描述
题解
def max_profit(prices):
sell = 0
buy = -99 # 表示最小值
for i in range(0, len(prices)):
buy = max(buy, -prices[i])
sell = max(sell, buy + prices[i])
return sell
2. Best Time to Buy and Sell Stock IV
题目描述
可以买卖各 k 次,且每次只能拥有一支股票,求最大的收益。题解
k 大约总天数,那么我们一旦发现可以赚钱就进行买卖。 k 小于总天数,我们可以建立两个动态规划数组 buy 和sell,对于每天的股票价格,buy[j] 表示在第 j 次买入时的最大收益,sell[j] 表示在第 j 次卖出时的最大收益。
def max_profit_unlimited(prices):
"""
辅助函数,
:param prices:每天的股票价格
:return: maxprofit 最大收益
"""
maxprofit = 0
for i in range(1, len(prices)):
if prices[i] > prices[i - 1]:
maxprofit += prices[i] - prices[i - 1]
return maxprofit
def max_profit2(k, prices):
days = len(prices)
if days < 2: # 天数小于2,买入后无法卖出,收益为 0
return 0
if k >= days: # 如果 k 大约总天数,那么我们一旦发现可以赚钱就进行买卖
return max_profit_unlimited(prices)
sell = [0] * (k + 1)
buy = [-99] * (k + 1) # 表示最小值
for i in range(0, days):
for j in range(1, k+1):
buy[j] = max(buy[j], sell[j-1] - prices[i])
sell[j] = max(sell[j], buy[j] + prices[i])
return sell[k]
3. Best Time to Buy and Sell Stock with Cooldown (Medium)
题目描述
题解
状态机来解决这类复杂的状态转移问题,通过建立多个状态以及它们的转移方式,我们可以很容易地推导出各个状态的转移方程。
def max_profit3(prices):
"""
给定一段时间内每天的股票价格,已知每次卖出之后必须冷却一天,且每次只能拥有一支股票,求最大的收益。
:param prices:
:return:
"""
days = len(prices)
if days == 0:
return 0
buy = [0] * days
sell = [0] * days
s1 = [0] * days
s2 = [0] * days
s1[0] = buy[0] = -prices[0]
for i in range(1, days):
buy[i] = s2[i - 1] - prices[i]
s1[i] = max(buy[i - 1], s1[i - 1])
sell[i] = max(buy[i - 1], s1[i - 1]) + prices[i]
s2[i] = max(s2[i - 1], sell[i - 1])
return max(sell[days - 1], s2[days - 1])
本笔记参考:LeetCode 101: A LeetCode Grinding Guide (C++ Version)
特此感谢作者高畅Chang Gao的技术支持