【剑指offer2】 chap14 动态规划

十四、动态规划

分冶:

  • 无重叠,直接递归

  • 有重叠 ,建立 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]);
    }
}

1.n序列问题

两种空间优化方案,一般可以转化成双序列

打家劫舍

剑指 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<=i0——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;
        }
    }
}

2.双序列问题

剑指 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. 子序列的数目

3.矩阵路径问题

  • 路径数量
  • 路径的最大值,最小值
  • 障碍物问题

剑指 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. 三角形中最小路径之和

4.背包问题

问题特点

  • 信息:一组物品,物品重量,物品价格

  • 限定:总重量

  • 目标:总价格最高

问题细分:

  • 0-1背包问题:每种物品只有一个

  • 有界背包问题:每种物品有多个

  • 无限背包问题:每种物品有无穷多个

(1)0-1背包问题

剑指 Offer II 101. 分割等和子集

labuladong模板——经典动态规划:0-1 背包问题

(2)有界背包问题

剑指 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);
    }
}

(3)无限背包问题

求组合数:

剑指 Offer II 103. 最少的硬币数目

剑指 Offer II 104. 排列的数目

377. 组合总和 Ⅳ  注意外层和内层循环的状态,这道题目标状态在外层,选择状态在内层   回溯的区别(39. 组合总和

518. 零钱兑换 II   dp[j] = dp[j] + dp[j - coin];  目标状态在内层,选择状态在外层;

目标值最大的典型问题:转化为一维问题,只进行依次判断,用j做dp传递,dp[j] = Math.max(dp[j], dp[j - w] + v)   目标状态在内层,选择状态在外层(交换也可以)
 

5.其他题型

(1)旅游最短路线

787. K 站中转内最便宜的航班

memo表(hash)+ dp函数递归; 

注意各种边界情况

        labuladong 题解思路

(2) 指针转盘

514. 自由之路
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)

你可能感兴趣的:(剑指offer刷题笔记,算法,动态规划,数据结构,java)