每日刷题总结——动态规划

动态规划

300. 最长递增子序列

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

子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

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

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

  1. 设计一个dp数组,dp[i]表示长度为i+1的所有递增子序列中尾数最小为dp[i]。这个dp数组肯定是严格递增的。
    例如{1,5,2,6,3,7,4,8};我可以看出两个长度为4的严格递增子序列{1,2,3,4}和{5,6,7,8}。那我dp[3]就应该等于4。我们可以发现dp数组肯定是一个递增数组,你要问为啥?因为我dp[3]=4,我敢保证dp[2]肯定小于4,我不用去在nums数组里挨个挨个计算,很简单,我肯定有一个基于子数组{1,2,3,4}的子数组{1,2,3}。所以我的dp[2]<=3。以此类推嘛。
  2. 定义一个int类型的数字max,代表最长子序列的值。他是动态更新的
  3. 用一个for循环遍历整个nums数组,按顺序取出数字num。在dp数组中找到比这个数大的所有数中最小的那个数的位置,很简单,因为我们要保证dp数组中存储的是尾数最小的数字。如果我找不到怎么办,我找到的最大的值dp[max]还要比nums小。那简单啊,max++,然后dp[max]=num;

总结:

  1. 设计一个dp数组记录长度为i+1的所有递增子序列中尾数最小为dp[i]
  2. 更新dp,比尾数小,我就覆盖他,比尾数大我就放到他后面
public int lengthOfLIS(int[] nums) {
    int max = 0;
    int[] dp = new int[nums.length];
    for(int num : nums) {
        // 二分法查找,时间复杂度为logN
        int left = 0, right = max;
        while(left < right) {//寻找dp数组中最小的值,当lo>=hi时说明整个数组遍历完了,可以退出循环了
            int mid = left+(right-left)/2;
            if(dp[mid] < num)//找到一个比拿出来的这个num大的数字中最小的那个。
                left = mid+1;
            else
                right = mid;
        }
        dp[left] = num;//覆盖掉
        if(left == max)//原来是最后一个数字啊,那我+1
            max++;
    }//如此一来总复杂度为NlogN
    return max;
}

673. 最长递增子序列的个数

给定一个未排序的整数数组 nums , 返回最长递增子序列的个数 。

注意 这个数列必须是 严格 递增的。

示例 1:

输入: [1,3,5,4,7]
输出: 2
解释: 有两个最长递增子序列,分别是 [1, 3, 4, 7] 和[1, 3, 5, 7]。

示例 2:

输入: [2,2,2,2,2]
输出: 5
解释: 最长递增子序列的长度是1,并且存在5个子序列的长度为1,因此输出5。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

⭐:这道题和300题很像,但是300题是做了个取巧的动作,我只关注尾数。而这道题目是最长的子序列的情况数,也就是说我们不能光盯着最后一个数字,万一中间也有变化怎么办。所以还是老老实实两个for循环吧。

  1. 创建两个dp数组dp1和dp2
    dp1[i]表示以[0, i]且i必选的最长子序列长度
    dp2[i]表示以[0, i]且i必选的最长子序列个数
  2. 如果外层的数字nums[i] > 内层数字nums[j],说明可以构成递增子序列
  3. 如果可以构成递增子序列,那我要看看加上nums[i]这个数之后的递增子序列的长度是不是跟我记录的这个dp1[i]一样,如果一样说明好家伙,新的组合。如果大于说明,哎我目前记录的不是最长的子序列长度啊,那我更新一下吧。

基于上面的思想

public int findNumberOfLIS(int[] nums) {
    if (nums.length == 0) {
        return 0;
    }

    int[] dp = new int[nums.length];
    int[] combination = new int[nums.length];

    Arrays.fill(dp, 1);
    Arrays.fill(combination, 1);

    int max = 1, res = 0;

    for (int i = 1; i < dp.length; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) {
                if (dp[j] + 1 > dp[i]) { //如果+1长于当前LIS 则组合数不变
                    dp[i] = dp[j] + 1;
                    combination[i] = combination[j];
                } else if (dp[j] + 1 == dp[i]) { //如果+1等于当前LIS 则说明找到了新组合
                    combination[i] += combination[j];//前面以nums[j]作为倒数第二个数的组合数+我已经找到的nums[i]作为结尾的组合数
                }
            }
        }
        max = Math.max(max, dp[i]);
    }

    for (int i = 0; i < nums.length; i++)
        if (dp[i] == max) res += combination[i];

    return res;
}

1143. 最长公共子序列

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

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

  • 例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。

两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。

示例 1:

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

示例 2:

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

示例 3:

输入:text1 = “abc”, text2 = “def”
输出:0
解释:两个字符串没有公共子序列,返回 0 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-common-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

  1. 首先想到的就是dp数组,因为是两个字符串,所以考虑二维dp数组
  2. dp[i][j]代表的是字符串1以下标为 i-1 结尾的子序列和字符串2以下标为 j-1 结尾的子序列有最长公共子序列长度为dp[i][j]
  3. dp数组的初始化:dp[0][j] = 0;dp[i][0] = 0;因为长度为0肯定就没有公共子串嘛
  4. 公式推导:
    1. 如果字符串1的第i-1个字符 == 字符串2的第j-1个字符,则dp[i][j] = dp[i-1][j-1]+1;
    2. 如果不相等,那就选dp[i-1][j]和dp[i][j-1]中大的那个
  5. 返回dp数组右下角那个值
public int longestCommonSubsequence(String text1, String text2) {
    int length1 = text1.length();
    int length2 = text2.length();
    
    int[][] dp = new int[length1+1][length2+1];//dp[i][j]代表A中以i结尾的A和以j为结尾的B有最长公共子序列长度dp[i][j]
    for(int i = 1; i <= length1; i++){
        for(int j = 1; j <= length2; j++){
            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[length1][length2];
}

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

给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 相同所需的最小步数

每步 可以删除任意一个字符串中的一个字符。

示例 1:

输入: word1 = “sea”, word2 = “eat”
输出: 2
解释: 第一步将 “sea” 变为 “ea” ,第二步将 "eat "变为 “ea”

示例 2:

输入:word1 = “leetcode”, word2 = “etco”
输出:4

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/delete-operation-for-two-strings
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:
⭐:这题就和583是一样的,583题找出最长公共子串,那我把剩下的删掉不就两个变成一样了吗

public int minDistance(String word1, String word2) {
    int length1 = word1.length();
    int length2 = word2.length();

    int[][] dp = new int[length1+1][length2+1];
    for(int i = 1; i <= length1; i++){
        for(int j=1; j <= length2; j++){
            if(word1.charAt(i-1) == word2.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 length1-dp[length1][length2]+length2-dp[length1][length2];
}

72. 编辑距离

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例 1:

输入:word1 = “horse”, word2 = “ros”
输出:3
解释:
horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)

示例 2:

输入:word1 = “intention”, word2 = “execution”
输出:5
解释:
intention -> inention (删除 ‘t’)
inention -> enention (将 ‘i’ 替换为 ‘e’)
enention -> exention (将 ‘n’ 替换为 ‘x’)
exention -> exection (将 ‘n’ 替换为 ‘c’)
exection -> execution (插入 ‘u’)

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/edit-distance
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:
⭐:第一眼看到这个题目:

  1. 两个字符串————>二位dp数组;
  2. 三种操作————>三个对应公式;

鉴于这两个操作,整体的思路为:

  1. 建一个二维dp数组:word1前i-1个字符编程word2前j-1个字符要进行dp[i][j]个操作
  2. 初始化:dp[i][0] = i;直接删除 dp[0][j]=j;直接删除
  3. 遍历方向:从前往后两个嵌套遍历就行
  4. 公式:如果word1的第i-1个字符和word2的第j-1个字符相同,那dp[i][j] = dp[i-1][j-1];如果不等:那就分别取删除一个字符情况、插入一个字符、替换一个字符情况中最少的操作步数即可。
public int minDistance(String word1, String word2) {
    int length1 = word1.length();
    int length2 = word2.length();

    int[][] dp = new int[length1+1][length2+1];//word1前i-1个字符变成word2前j-1个字符要进行dp[i][j]个操作
    for(int i = 0; i <= length1; i++){
        dp[i][0] = i;
    }
    for(int j = 0; j <= length2; j++){
        dp[0][j] = j;
    }
    for(int i = 1; i <= length1; i++){
        for(int j = 1; j <= length2; j++){
            if(word1.charAt(i-1) != word2.charAt(j-1)){
                dp[i][j] = Math.min(dp[i-1][j]+1, Math.min(dp[i][j-1]+1,dp[i-1][j-1]+1));
            }else{
                dp[i][j] = dp[i-1][j-1];
            }
        }
    }
    return dp[length1][length2];
}

322. 零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。

你可以认为每种硬币的数量是无限的。

示例 1:

输入:coins = [1, 2, 5], amount = 11
输出:3
解释:11 = 5 + 5 + 1

示例 2:

输入:coins = [2], amount = 3
输出:-1

示例 3:

输入:coins = [1], amount = 0
输出:0

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:
动规四部曲

  1. 建立dp数组:很简单,一维的就行,dp[i]表示凑齐金额为 i 至少需要dp[i]个硬币
  2. 初始化:dp[i]都为Integer.MAX_VALUE
  3. 更新公式:dp[i] = dp[i]和dp[i-coins[j]]中小的那个
  4. 遍历方向:外层是dp的遍历,内层是硬币coins 的遍历

整道题和爬楼梯很像

public int coinChange(int[] coins, int amount) {
    int length = coins.length;
    if(length == 0){
        return -1;
    }
    if(amount == 0){
        return 0;
    }
    int[] dp = new int[amount+1];
    dp[0] = 0;
    Arrays.fill(dp,1,amount+1,Integer.MAX_VALUE);

    for(int i = 0; i < length; i++){//需要一个双循环,一个遍历dp数组,一个遍历coins数组
        for(int j = coins[i]; j < amount+1; j++){
            //这里要特别说明,如果dp[j-coins[i]]都没有改变,那说明当前这个值用该硬币他就是凑不齐的,那就没必要修改了呀,依旧保持Integer.MAX_VALUE即可
            if(dp[j-coins[i]] != Integer.MAX_VALUE){
                dp[j] = dp[j] < dp[j-coins[i]]+1 ? dp[j] : dp[j-coins[i]] + 1;
            }
        }
    }
    if(dp[amount] != Integer.MAX_VALUE){
        return dp[amount];
    }
    return -1;
}

343. 整数拆分

给定一个正整数 n ,将其拆分为 k 个正整的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积 。

示例 1:

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

示例 2:

输入: n = 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/integer-break
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:
动规四部曲:

  1. 确定dp数组的定义:数字i被拆分后乘积最大值为dp[i]
  2. dp初始化:dp[0]和dp[1]不用管,因为拆不了,dp[2] = 1,因为拆分成1和1嘛
  3. 递推公式:dp[i] = j*dp[i-j];循环遍历找最大
  4. 遍历方向:肯定是嵌套遍历,最外层遍历i,内层遍历j,j从1遍历到i,表示被拆分出一个j的情况下能达到的最大值
public int integerBreak(int n) 
   int[] dp = new int[n+1]
   dp[2] = 1;
  
   for(int i = 3; i < n+1; i++){
       for(int j = 1; j < i; j++){
           dp[i] = Math.max(dp[i],Math.max(j*(i-j),j*dp[i-j]));
           //之所以要把j*(i-j)加进去,是因为dp[i-j]种拆分的肯定大于1,这是拆分成3个及以上个数字的情况
           //如果没有j*(i-j)我们的所有结果里就少了一种拆分成两个数的情况
       }
   }
   return dp[n];
}

241. 为运算表达式设计优先级

给你一个由数字和运算符组成的字符串 expression ,按不同优先级组合数字和运算符,计算并返回所有可能组合的结果。你可以 按任意顺序 返回答案。

示例 1:

输入:expression = “2-1-1”
输出:[0,2]
解释:
((2-1)-1) = 0
(2-(1-1)) = 2

示例 2:

输入:expression = “23-45”
输出:[-34,-14,-10,-10,10]
解释:
(2*(3-(4*5))) = -34
((2*3)-(4*5)) = -14
((2*(3-4))*5) = -10
(2*((3-4)*5)) = -10
(((2*3)-4)*5) = 10

提示:

  • 1 <= expression.length <= 20
  • expression 由数字和算符 ‘+’、‘-’ 和 ‘*’ 组成。
  • 输入表达式中的所有整数值在范围 [0, 99]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/different-ways-to-add-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:
这一道题实际上就要用到分治的思想了

  1. 我们按照以“+”、“-”或者“*”为界,将整个运算表达式分成两个部分,我们分别求着两个部分各自的运算值的情况,然后将他们排列组合进行运算不就行了?
  2. 步骤1说着容易,分别求出来然后排列组合,但是我怎么分别求出来呢?————>这不简单,用递归的思想,每一层递归都是一个新生
  • 综上:我们可以知道,先用for循环遍历符号,再用递归求各自的运算值的情况。
  • 细节:何时递归可以返回?遇到只有数字没有符号的情况;何时for循环终止?从0开始表里到整个字符串最后一个即可。
  • 实现思路:以 expression=“2*3-4*5”为例
    在这里插入图片描述
    代码实现:
public List<Integer> diffWaysToCompute(String input) {
    if(map.containsKey(input)) return map.get(input);
    List<Integer> list = new ArrayList<>();
    int len = input.length();
    for(int i = 0; i < len; i++) {
        char c = input.charAt(i);
        if(c == '+' || c == '-' || c == '*') {  // 出现运算符号,递归求解前半段和后半段。
            List<Integer> left = diffWaysToCompute(input.substring(0, i));
            List<Integer> right = diffWaysToCompute(input.substring(i+1, input.length()));
            for(int l : left) {
                for(int r : right) {
                    switch(c) {
                        case '+':
                            list.add(l + r);
                            break;
                        case '-':
                            list.add(l - r);
                            break;
                        case '*':
                            list.add(l * r);
                            break;
                    }
                }
            }
        }
    }
    if(list.size() == 0) list.add(Integer.valueOf(input));//递归结束情况:出现单个数字
    return list;
}

97. 交错字符串

给定三个字符串 s1s2s3,请你帮忙验证 s3是否是由 s1s2交错 组成的。

两个字符串 st 交错 的定义与过程如下,其中每个字符串都会被分割成若干 非空 子字符串:

  • s = s1 + s2 + ... + sn
  • t = t1 + t2 + ... + tm
  • |n - m| <= 1
  • 交错 是 s1 + t1 + s2 + t2 + s3 + t3 + ... 或者 t1 + s1 + t2 + s2 + t3 + s3 + ...

注意:a + b意味着字符串 ab连接。
示例 1:

在这里插入图片描述
输入:s1 = “aabcc”, s2 = “dbbca”, s3 = “aadbbcbcac”
输出:true

示例 2:

输入:s1 = “aabcc”, s2 = “dbbca”, s3 = “aadbbbaccc”
输出:false

示例 3:

输入:s1 = “”, s2 = “”, s3 = “”
输出:true

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/interleaving-string
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:
动规四部曲:

  1. 确定dp数组的定义:boolean[][] dp = new boolean[str1.length + 1][str2.length + 1];dp[i][j]表示str1前i个字符和str2前j个字符串能组成str3前i+j个字符串的可能性为dp[i][j]
  2. dp初始化:dp[0][0] = true;俩空字符串肯定相同啊,dp[i][0]则要比对str1.charAt(i) == str3.charAt(i),如果返回为true,继续往后遍历,如果返回为false,则后面不用比对了,肯定不等了。dp[0][j]同理。
  3. 递推公式:比对str1中的字符:str[i-1] == str3[i+j-1] && dp[i-1][j];比对str2中的字符:str2[j - 1] == str3[i + j - 1] && dp[i][j - 1]
  4. 遍历方向:肯定是嵌套遍历,最外层遍历i,内层遍历j。从前往后遍历。
public boolean isInterleave(String s1, String s2, String s3) {
    if (s1.length() + s2.length() != s3.length()) {
        return false;
    }
    char[] str1 = s1.toCharArray();
    char[] str2 = s2.toCharArray();
    char[] str3 = s3.toCharArray();
   
    boolean[][] dp = new boolean[str1.length + 1][str2.length + 1];
    dp[0][0] = true;
    for (int i = 1; i <= str1.length; i++) {
        // 如果有一个不满足,那么后续的串就都不能拼出str3
        if (str1[i - 1] != str3[i - 1]) {
            break;
        }
        dp[i][0] = true;
    }
    // 填第一行,也就是只选用str2中的字符
    for (int j = 1; j <= str2.length; j++) {
        // 如果有一个不满足,那么后续的串就都不能拼出str3
        if (str2[j - 1] != str3[j - 1]) {
            break;
        }
        dp[0][j] = true;
    }
    // 填中间的表
    for (int i = 1; i <= str1.length; i++) {
        for (int j = 1; j <= str2.length; j++) {
            // 两种情况:
            // 1、str3中[i+j-1]位置的字符是来自str1的
            // 这时必须要求str1中[i-1]位置的字符和str3中[i+j-1]位置的字符相同
            // 同时dp表中的[i-1,j]是满足条件的
            // 2、str3中[i+j-1]位置的字符是来自str2的
            // 这时必须要求str2中[j-1]位置的字符和str3中[i+j-1]位置的字符相同
            // 同时dp表中的[i,j-1]是满足条件的
            // 两种情况只要有一种满足,那么str1中前i个字符和str2中前j个字符就可以拼出str3中这前i+j个字符
            if ((str1[i - 1] == str3[i + j - 1]) && dp[i - 1][j] || (str2[j - 1] == str3[i + j - 1]) && dp[i][j - 1]) {
                dp[i][j] = true;
            }
        }
    }
    return dp[str1.length][str2.length];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

走出半生仍是少年

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值