动态规划
步骤:
- 子问题
- 转移方程
- 初始化
- 遍历顺序
- 返回值
一.背包问题
1.01背包
一般转移方程:int[][] dp = new int[n+1][target+1];
一维写法:
外层循环物品,内层循环重量(倒序)
dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
v表示重量,w表示价值
// 外层循环遍历每个类型的研究材料
for (int i = 0; i < M; ++i) {
// 内层循环从 N 空间逐渐减少到当前研究材料所占空间
for (int j = N; j >= costs[i]; --j) {
// 考虑当前研究材料选择和不选择的情况,选择最大值
dp[j] = max(dp[j], dp[j - costs[i]] + values[i]);
}
}
2.完全背包
外层循环重量(正序),内层循环物品,判断重量是否大于等于物品重量
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
for(int i = 0; i < weight.size(); i++) { // 遍历物品
if (j - weight[i] >= 0) dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
}
}
二.线性DP
1.最长上升子序列
1.1.DP做法
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
Arrays.fill(dp, 1);
int res = 1;
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
res = Math.max(dp[i], res);
}
return res;
}
}
1.2.贪心+二分做法
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
List<Integer> list = new ArrayList<>();
for(int i = 0;i<n;i++){
int l = 0;
int r = list.size();
while(l < r){
int m = (l + r) >> 1;
if(list.get(m) >= nums[i]){
r = m;
}else{
l = m + 1;
}
}
if(l == list.size()){
list.add(nums[i]);
}else{
list.set(l,nums[i]);
}
}
return list.size();
}
}
2.最长公共子序列
通常考虑:dp[i-1][j],dp[i][j-1],dp[i-1][j-1]
LeetCode例题
class Solution {
public int minDistance(String word1, String word2) {
int m = word1.length();
int n = word2.length();
int[][] dp = new int[m+1][n+1];
for(int i = 0;i<=m;i++){
dp[i][0] = i;
}
for(int j = 0;j<=n;j++){
dp[0][j] = j;
}
for(int i = 1;i<=m;i++){
for(int j = 1;j<=n;j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j] = dp[i-1][j-1];
}else{
dp[i][j] = Math.min(Math.min(dp[i-1][j],dp[i][j-1]),dp[i-1][j-1])+1;
}
}
}
return dp[m][n];
}
}
三.区间DP
当你发现子问题是从两侧向内缩小的,就可以往区间 DP 上想了。通常是枚举分割线。
状态表示一般为:dp[l,r],表示一段区间的答案。
四.数位统计DP
数位DP往往都是这样的题型,给定一个闭区间[ l , r ] [l,r][l,r],让你求这个区间中满足某种条件的数的总数。