分冶:
无重叠,直接递归
有重叠 ,建立 dp表
剑指 Offer II 088. 爬楼梯的最少成本
class Solution {
public int minCostClimbingStairs(int[] cost) {
int[] dp = new int[]{cost[0], cost[1]};
for (int i = 2; i < cost.length; i++) {
dp[i % 2] = Math.min(dp[(i - 1) % 2], dp[(i - 2) % 2]) + cost[i];
}
return Math.min(dp[0], dp[1]);
}
}
两种空间优化方案,一般可以转化成双序列
剑指 Offer II 089. 房屋偷盗 = 198. 打家劫舍
单序列
class Solution {
public int rob(int[] nums) {
if(nums.length == 0) return 0;
int[] dp = new int[nums.length];
dp[0] = nums[0];
if(nums.length > 1){
dp[1] = Math.max(nums[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];
}
}
转化成双序列
class Solution {
public int rob(int[] nums) {
if (nums.length == 0) return 0;
int[][] dp = new int[2][2];
dp[0][0] = 0;
dp[1][0] = nums[0];
for (int i = 1; i < nums.length; i++) {
dp[0][i % 2] = Math.max(dp[0][(i - 1) % 2], dp[1][(i - 1) % 2]);
dp[1][i % 2] = nums[i] + dp[0][(i - 1) % 2];
}
return Math.max(dp[0][(nums.length - 1) % 2], dp[1][(nums.length - 1) % 2]);
}
}
剑指 Offer II 090. 环形房屋偷盗 = 213. 打家劫舍 II
把环拆成两个队列,一个是从0到n-1,另一个是从1到n,然后返回两个结果最大的。
class Solution {
public int rob(int[] nums) {
//长度为0
if (nums.length == 0) return 0;
//长度为1
if (nums.length == 1) return nums[0];
//长度>=2
int res1 = helper(nums, 0, nums.length - 2);
int res2 = helper(nums, 1, nums.length - 1);
return Math.max(res1, res2);
}
private int helper(int[] nums, int start, int end) {
int[] dp = new int[2];
dp[0] = nums[start];
if (start < end) {
dp[1] = Math.max(nums[start], nums[start + 1]);
}
for (int i = start + 2; i <= end; i++) {
int j = i - start;
dp[j % 2] = Math.max(dp[(j - 2) % 2] + nums[i], dp[(j - 1) % 2]);
}
return dp[(end - start) % 2];
}
}
337. 打家劫舍 III
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int rob(TreeNode root) {
int[] result = robInternal(root);
return Math.max(result[0], result[1]);
}
public int[] robInternal(TreeNode root) {
if (root == null) return new int[2];
int[] result = new int[2];
int[] left = robInternal(root.left);
int[] right = robInternal(root.right);
//当前不偷 = 左边(偷或不偷) + 右边(偷或不偷)
result[0] = Math.max(left[0], left[1]) + Math.max(right[0], right[1]);
//当前偷 = 左边不偷 + 右边不偷 + 当前节点值
result[1] = left[0] + right[0] + root.val;
return result;
}
}
剑指 Offer II 091. 粉刷房子 = 256. 粉刷房子
class Solution {
//三序列dp
public int minCost(int[][] costs) {
int n = costs.length;
for (int i = 1; i < n; i++) {
costs[i][0] += Math.min(costs[i - 1][1], costs[i - 1][2]);
costs[i][1] += Math.min(costs[i - 1][0], costs[i - 1][2]);
costs[i][2] += Math.min(costs[i - 1][0], costs[i - 1][1]);
}
return Math.min(Math.min(costs[n - 1][0], costs[n - 1][1]), costs[n - 1][2]);
}
public int minCost2(int[][] costs) {
int n = costs.length;
int[][] dp = new int[2][3];
dp[0] = costs[0];
for (int i = 1; i < n; i++) {
dp[i % 2][0] = Math.min(dp[(i - 1) % 2][1], dp[(i - 1) % 2][2]) + costs[i][0];
dp[i % 2][1] = Math.min(dp[(i - 1) % 2][0], dp[(i - 1) % 2][2]) + costs[i][1];
dp[i % 2][2] = Math.min(dp[(i - 1) % 2][0], dp[(i - 1) % 2][1]) + costs[i][2];
}
return Math.min(Math.min(dp[(n - 1) % 2][0], dp[(n - 1) % 2][1]), dp[(n - 1) % 2][2]);
}
}
265. 粉刷房子 II (上一道题的通用版本)
class Solution {
public int minCostII(int[][] costs) {
int n = costs.length;
int k = costs[0].length;
int res = Integer.MAX_VALUE;
for (int i = 1; i < n; i++) {
for (int j = 0; j < k; j++) {
int minTemp = Integer.MAX_VALUE;
for (int p = j + 1; p < j + k; p++) {
minTemp = Math.min(minTemp, costs[i - 1][p % k]);
}
costs[i][j] += minTemp;
}
}
for (int j = 0; j < k; j++) {
res = Math.min(res, costs[n - 1][j]);
}
return res;
}
}
276. 栅栏涂色
class Solution {
public int numWays(int n, int k) {
int[][] dp = new int[n][2];
dp[0][1] = k;//第一个栅栏有k种涂法(包含0和1两种情况)
for (int i = 1; i < n; i++) {
dp[i][0] = dp[i - 1][1];
dp[i][1] = (dp[i - 1][0] + dp[i - 1][1]) * (k - 1);
}
return dp[n - 1][0] + dp[n - 1][1];
}
}
不含连续1的非负整数
力扣题解
class Solution {
public static int findIntegers(int n) {
/*
dp[i] = dp[i - 1] + dp[i - 2]
*/
int[] dp = new int[32];
dp[0] = 1;
dp[1] = 2;
for (int i = 2; i < 32; i++)
dp[i] = dp[i - 1] + dp[i - 2];
String numStr = getBinary(n);
int res = 0;
for (int i = 0; i < numStr.length(); i++) {
if (numStr.charAt(i) == '0') {
continue;
}
res += dp[numStr.length() - i - 1];
if (i != 0 && numStr.charAt(i - 1) == '1') {
return res;
}
}
return res + 1;
}
//get the binary form of number
//15 -> 1111
private static String getBinary(int num) {
StringBuilder sb = new StringBuilder();
while (num > 0) {
sb.insert(0, num & 1);
num >>= 1;
}
return sb.toString();
}
}
656. 金币路径 **
从最后一个位置回退
0<= i <=length-2
dp[i] = min(A[i] + dp[j]) i+1<=j<=min(i+B,length) && A[j] >=0
next[i] = j of min_cost
输出路径
0<=i
0——i+1 i == lenth -1 && coins[i] >= 0——length
else——null
class Solution {
public List cheapestJump(int[] coins, int maxJump) {
List res = new ArrayList<>();
int[] dp = new int[coins.length];
int[] next = new int[coins.length];
Arrays.fill(next, -1);
for(int i = coins.length - 2; i >= 0; i--){
int min_cost = Integer.MAX_VALUE;
for(int j = i + 1; j < coins.length && j <= i + maxJump ; j++){
if(coins[j] >= 0){
int cost = coins[i] + dp[j];
if(cost < min_cost){
min_cost = cost;
next[i] = j;
}
}
//如果j均不可达,则next[i]=-1;
}
dp[i] = min_cost;
}
int i;
for(i = 0; i < coins.length && next[i] > 0; i = next[i]){
res.add(i + 1);
}
if(i == coins.length - 1 && coins[i] >= 0){
res.add(coins.length);
}else{
return new ArrayList<>();
}
return res;
}
}
152. 乘积最大子数组
maxP[i] = max (cur*maxP[i-1], cur*minP[i-1], cur)
minP[i] = min (cur*maxP[i-1], cur*minP[i-1], cur)
class Solution {
public int maxProduct(int[] nums) {
int len = nums.length;
int maxP = nums[0];
int minP = nums[0];
int res = nums[0];
for (int i = 1; i < len; i++) {
int prevMaxP = maxP;
int prevMinP = minP;
maxP = Math.max(nums[i], Math.max(nums[i] * prevMaxP, nums[i] * prevMinP));
minP = Math.min(nums[i], Math.min(nums[i] * prevMaxP, nums[i] * prevMinP));
res = Math.max(res, maxP);
}
return res;
}
}
53. 最大子数组和 (线段树)
法一:动态规划 dp O(n):对于每一个i,dp[i]表示包含n[i]的最大子数组和,边迭代边找最大值
class Solution {
public int maxSubArray(int[] nums) {
int len = nums.length;
int[] dp = new int[len];
dp[0] = nums[0];
int res = nums[0];
for(int i = 1; i < len; i++){
dp[i] = Math.max(nums[i], nums[i]+dp[i-1]);
res = Math.max(dp[i], res);
}
return res;
}
}
法二:前缀和
public class Solution {
public int maxSubArray(int[] nums) {
int ans = Integer.MIN_VALUE, sum = 0, min = 0;
for (int num : nums) {
sum += num;
ans = Math.max(ans, sum - min);
min = Math.min(sum, min);
}
return ans;
}
}
法三:分冶
740. 删除并获得点数
978. 最长湍流子数组
697. 数组的度
https://labuladong.github.io/algo/3/26/96/
base case:
dp[-1][...][0] = dp[...][0][0] = 0
dp[-1][...][1] = dp[...][0][1] = -infinity
状态转移方程:
dp[i][k][0] = max(dp[i-1][k][0], dp[i-1][k][1] + prices[i])
dp[i][k][1] = max(dp[i-1][k][1], dp[i-1][k-1][0] - prices[i])
121. 买卖股票的最佳时机 |
暴力解法、动态规划(Java) |
122. 买卖股票的最佳时机 II |
暴力搜索、贪心算法、动态规划(Java) |
123. 买卖股票的最佳时机 III |
动态规划(Java) |
188. 买卖股票的最佳时机 IV |
动态规划(「力扣」更新过用例,只有优化空间的版本可以 AC) |
309. 最佳买卖股票时机含冷冻期 |
动态规划(Java) |
714. 买卖股票的最佳时机含手续费 |
动态规划(Java) |
剑指 Offer II 092. 翻转字符 (双序列递推)
末尾为0
末尾为1
剑指 Offer II 093. 最长斐波那契数列
剑指 Offer II 094. 最少回文分割
int len = s.length();
//isPalindrome
boolean[][] isPal = new boolean[len][len];
for(int i = 0; i < len; i++){
for(int j = 0; j <= i; j++){
if(s.charAt(i) == s.charAt(j) && (i <= j + 1 || isPal[j+1][i-1])){
isPal[j][i] = true;
}
}
}
剑指 Offer II 095. 最长公共子序列
if(text1.charAt(i-1) == text2.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]);
}
583. 两个字符串的删除操作 (转化一下)
return len1 + len2 - 2 * dp[len1][len2];
712. 两个字符串的最小ASCII删除和
注意 0 和另一字符串的 初始化 其他结构类似
剑指 Offer II 096. 字符串交织
注意行列边界初始化
dp[i + 1][j + 1] = (ch1 == ch3 && dp[i][j + 1]) || (ch2 == ch3 && dp[i + 1][j]);
剑指 Offer II 097. 子序列的数目
剑指 Offer II 098. 路径的数目 无障碍物,求路径数
62. 不同路径
两种写法:二维数组,一维数组;第一行和第一列初始化为1
class Solution {
//动态规划,时间mn,空间n,用一行数组储存
public int uniquePaths(int m, int n) {
int[] dp = new int[n];
Arrays.fill(dp, 1);
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[j] += dp[j - 1];
}
}
return dp[n - 1];
}
}
63. 不同路径 II (障碍物,求路径数)
两种写法:二维数组,一维数组;第一行和第一列初始化,碰到障碍物就变成0
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] dp = new int[m][n];
dp[0][0] = obstacleGrid[0][0] == 1 ? 0 : 1;
for (int i = 1; i < m; i++) {
dp[i][0] = obstacleGrid[i][0] == 1 ? 0 : dp[i - 1][0];
}
for (int j = 1; j < n; j++) {
dp[0][j] = obstacleGrid[0][j] == 1 ? 0 : dp[0][j - 1];
}
for (int i = 1; i < m; i++) {
for (int j = 1; j < n; j++) {
dp[i][j] = obstacleGrid[i][j] == 1 ? 0 : dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
}
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[] dp = new int[n];
dp[0] = obstacleGrid[0][0] == 0 ? 1 : 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (obstacleGrid[i][j] == 1) {
dp[j] = 0;
} else if (j >= 1){
dp[j] += dp[j - 1];
}
}
}
return dp[n - 1];
}
}
剑指 Offer II 099. 最小路径之和(无障碍物)
两种写法:二维数组,一维数组
思路都是让第一列和第一行持续累加,其余用min函数比较
class Solution {
public int minPathSum(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int[] dp = new int[n];
dp[0] = grid[0][0];
for (int j = 1; j < n; j++) {
dp[j] = grid[0][j] + dp[j - 1];
}
for (int i = 1; i < m; i++) {
dp[0] += grid[i][0];
for (int j = 1; j < n; j++) {
dp[j] = Math.min(dp[j], dp[j - 1]) + grid[i][j];
}
}
return dp[n - 1];
}
}
174. 地下城游戏(障碍物,路径和)
逆向思考,从最后一个位置倒推满足条件的生命值,同样地,先初始化最后一列和最后一行
// 状态转移方程
for(int i = m - 2; i >= 0; i--) {
for(int j = n - 2; j >= 0; j--) {
dungeon[i][j] = Math.max(Math.min(dungeon[i][j+1], dungeon[i+1][j]) - dungeon[i][j], 1);
}
}
剑指 Offer II 100. 三角形中最小路径之和
问题特点
信息:一组物品,物品重量,物品价格
限定:总重量
目标:总价格最高
问题细分:
0-1背包问题:每种物品只有一个
有界背包问题:每种物品有多个
无限背包问题:每种物品有无穷多个
剑指 Offer II 101. 分割等和子集
labuladong模板——经典动态规划:0-1 背包问题
剑指 Offer II 102. 加减的目标值
多重背包问题:在0-1背包的基础上增加k个累加状态
for (int k = 0; k <= si; k++) {
if (j - k * wi >= 0) {
dp[i][j] = Math.max(dp[i][j], dp[i - 1][j - k * wi] + k * vi);
}
}
求组合数:
剑指 Offer II 103. 最少的硬币数目
剑指 Offer II 104. 排列的数目
377. 组合总和 Ⅳ 注意外层和内层循环的状态,这道题目标状态在外层,选择状态在内层 和回溯的区别(39. 组合总和)
518. 零钱兑换 II dp[j] = dp[j] + dp[j - coin]; 目标状态在内层,选择状态在外层;
(1)旅游最短路线
787. K 站中转内最便宜的航班
memo表(hash)+ dp函数递归;
注意各种边界情况
labuladong 题解思路
(2) 指针转盘
memo表 (hash) + dp函数递归
// 字符 -> 索引列表
HashMap> charToIndex = new HashMap<>();
// 备忘录
int[][] memo;
/* 主函数 */
int findRotateSteps(String ring, String key) {
int m = ring.length();
int n = key.length();
// 备忘录全部初始化为 0
memo = new int[m][n];
// 记录圆环上字符到索引的映射
for (int i = 0; i < ring.length(); i++) {
char c = ring.charAt(i);
if (!charToIndex.containsKey(c)) {
charToIndex.put(c, new LinkedList<>());
}
charToIndex.get(c).add(i);
}
// 圆盘指针最初指向 12 点钟方向,
// 从第一个字符开始输入 key
return dp(ring, 0, key, 0);
}
// 计算圆盘指针在 ring[i],输入 key[j..] 的最少操作数
int dp(String ring, int i, String key, int j) {
// base case 完成输入
if (j == key.length()) return 0;
// 查找备忘录,避免重叠子问题
if (memo[i][j] != 0) return memo[i][j];
int n = ring.length();
// 做选择
int res = Integer.MAX_VALUE;
// ring 上可能有多个字符 key[j]
for (int k : charToIndex.get(key.charAt(j))) {
// 拨动指针的次数
int delta = Math.abs(k - i);
// 选择顺时针还是逆时针
delta = Math.min(delta, n - delta);
// 将指针拨到 ring[k],继续输入 key[j+1..]
int subProblem = dp(ring, k, key, j + 1);
// 选择「整体」操作次数最少的
// 加一是因为按动按钮也是一次操作
res = Math.min(res, 1 + delta + subProblem);
}
// 将结果存入备忘录
memo[i][j] = res;
return res;
}
(3)