【力扣刷题】(三)动态规划专题

动态规划

1,动态规划适合什么问题?

求最值,而且核心是穷举思想。

首先,穷举可能会非常复杂;所以你需要发现重叠的子问题,然后使用备忘录模式dp数组的形式优化。也就是你需要找到最优子结构,通过子问题才能拆解出复杂问题。要想正确的穷举,你还需要找出我们的状态转移方程。

动态回归的优化:可以只用dp存储转态转移方程即可。(状态压缩)

自顶向下:从大问题开始,向下分解,如递归树;

自底向上:从基本问题开始,向上推导,如动态规划。

动规五部曲:

  • 明确dp数组的下标及对应的值的意义
  • 找到子问题得到递推公式
  • 明确dp数组如何初始化
  • 确定遍历顺序
  • 举例推导dp数组
for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 择优(选择1,选择2...)

2,力扣题型

2.1,746最小花费爬楼梯

输入:cost = [10, 15, 20]
输出:15
解释:最低花费是从 cost[1] 开始,然后走两步即可到阶梯顶,一共花费 15 。
输入:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1]
输出:6
解释:最低花费方式是从 cost[0] 开始,逐个经过那些 1 ,跳过 cost[3] ,一共花费 6 。

链接:https://leetcode-cn.com/problems/min-cost-climbing-stairs

class Solution {
    
    
    public int minCostClimbingStairs(int[] cost) {
    
    
        int next = 0;
        int size = cost.length;
        // 当有0层阶梯和1层阶梯时直接跨过去
        int prev = 0;
        int curr = 0;
        
        for(int i =2;i<=size;i++){
    
    
            // 下一步如何走于前两步有关
            // 下一步可以从前两步走两步或者是前一步走一步到达
            next = Math.min(curr+cost[i-1],prev+cost[i-2]);
            prev = curr;
            curr = next;
        }
        return next;
            
    }
}

2.2,62不同路径

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。

问总共有多少条不同的路径?

img
链接:https://leetcode-cn.com/problems/unique-paths

问题分析:

dp[i][j]的路径等于dp[i-1][j]dp[i][j-1]的路径方法之和。这就是我们的递推公式。

然后如何初始化数组:当m或n为1,则只有向右或向左走的一种方法。

为什么要先初始化数组?因为我们的结果就是最后的dp数组元素值,而且整个值是通过递推公式由初始值推导出来的。其中dp[i][j]表示从[0][0][i][j]的路径。

image-20210901160624982

class Solution {
    
    
    public int uniquePaths(int m, int n) {
    
    
        int[][] dp = new int[m][n];
        // 如何初始化数组
        for(int i = 0;i<m;i++) dp[i][0] = 1;
        for(int j = 0;j<n;j++) dp[0][j] = 1;
        for(int i=1;i<m;i++){
    
    
            for(int j=1;j<n;j++){
    
    
                // 递推公式
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
            }
        }
        return dp[m-1][n-1];
    }
}

此外,还有不同路径的变种,带障碍的不同路径。这里的思想是跳过障碍,障碍默认为0。

2.3,343整数拆分

给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

输入: 2
输出: 1
解释: 2 = 1 + 1, 1 × 1 = 1。

链接:https://leetcode-cn.com/problems/integer-break/

思路:怎么把一个数n拆成2个?并计算乘积最大的?从j从1开始遍历到n-1;计算Max(j*(i-j))

如何把一个数拆成n个并取最大?继续对刚刚的i-j拆分。j*(dp[i-j])。这里我们的dp[i]表示拆分i的最大乘积。进一步的,我们得到dp公式:dp[i]=max(dp[i],max(j*(i-j),dp[i-j]*j))

class Solution {
    
    
    public int integerBreak(int n) {
    
    
        // 初始化为0,其他不考虑
        int[] dp = new int[n+1];
        for(int i =1;i<=n;i++){
    
    
            for(int j=1;j<=i/2;j++){
    
    
                dp[i] = Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));
            }
        }
        return dp[n];
    }
}

2.4,300最长递增子序列

给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/

考虑动态规划:一个元素和两个元素的序列最大长度的关系就是加一,只要满足递增的情况下。

class Solution {
    
    
    public int lengthOfLIS(int[] nums) {
    
    
        int[] dp = new int[nums.length];
        //初始化全部为1;dp[i]表示有i个元素的长度
        Arrays.fill(dp,1);

        int size = nums.length;
        for(int i=1;i<size;i++){
    
    
            for(int j=0;j<i;j++){
    
    
                if(nums[j]<nums[i]){
    
     //满足升序条件
                    dp[i] = Math.max(dp[i],dp[j]+1);
                }
            }
        }
        //取dp数组中最大的为结果
        // 1,3,6,7,9 =>5
	//1,3,6,7,9,4 =>3(会把4取到)
        int result = 1;
        for(int i=0;i<size;i++){
    
    
            result = Math.max(result,dp[i]);
        }
        return result;
    }
}

2.5,718最常重复子数组

给两个整数数组 AB ,返回两个数组中公共的、长度最长的子数组的长度。

输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出:3

链接:https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray/

定义dp[i][j]表示A[i:]和B[j:]的最长公共子数组;则两个数组满足dp[i][j]=dp[i-1][j-1]+1

class Solution {
    
    
    public int findLength(int[] nums1, int[] nums2) {
    
    
        int s1 = nums1.length,s2 = nums2.length;
        int res = 0;
        //dp[i][j]表示下标为i-1和j-1的数组AB的最长公共子数组
        //因此鉴于ij的定义,他们要从1开始
        int[][] dp = new int[s1+1][s2+1];
      
        for(int i=1;i<s1;i++){
    
    
            for(int j=1;j<s2;j++){
    
    
                dp[i][j] = nums1[i-1]==nums2[j-1]?dp[i-1][j-1]+1:0;
                res = Math.max(dp[i][j],res);
            }
        }
        return res;
    }
}

2.6,1143最长公共子序列

给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。

例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

输入:text1 = "abcde", text2 = "ace" 
输出:3  
解释:最长公共子序列是 "ace" ,它的长度为 3 。

链接:https://leetcode-cn.com/problems/longest-common-subsequence

注意:数组和序列的区别在于,数组要是连续的;序列可以不连续。

image.png
class Solution {
    
    
    public int longestCommonSubsequence(String text1, String text2) {
    
    
        int s1 = text1.length();
        int s2 = text2.length();
        int dp[][] = new int[s1+1][s2+1];
        for(int i=1;i<=s1;i++){
    
    
            for(int j=1;j<=s2;j++){
    
    
                //两层for循环都是轮训填充的
                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]);
                }
            }
        }
        return dp[s1][s2];
    }
}

类似题型:

[583. 两个字符串的删除操作]

2.7,72编辑距离

https://leetcode-cn.com/problems/edit-distance/

class Solution {
    
    
    public int minDistance(String word1, String word2) {
    
    
        // 思路:dp
        int m = word1.length();
        int n = word2.length();
        int[][] dp = new int[m+1][n+1];
        // 二维数组初始化
        // s1为null,s2需要编辑个数为自己长度
        for(int i=1;i<=m;i++){
    
    
            dp[i][0] = i;
        }
         // s2为null,s1需要编辑个数为自己长度
        for(int j=1;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] = min(dp[i][j-1]+1,dp[i-1][j]+1,dp[i-1][j-1]+1);
                }
            }
        }
        return dp[m][n];
    }
    public int min(int a,int b,int c){
    
    
        return Math.min(a,Math.min(b,c));
    }
}

猜你喜欢

转载自blog.csdn.net/qq_40589204/article/details/120988558
今日推荐