动态规划

动态规划是很重要的一种题型,更是一种很重要的思想!!!!!

  把握住两点:1)dp数组表示的是什么状态    2)状态转移方程

单个字符串的动态规划问题!!!!!

300. 最长上升子序列

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:

可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?

 

dp[ i ] 表示已 nums[ i ] 结尾的子序列的最大长度。

class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums==null || nums.length==0)
            return 0;
        int[] dp=new int[nums.length];
        for(int i=0;i<dp.length;i++){
            dp[i]=1;
        }
        int res=1;
        for(int i=0;i<nums.length;i++){
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j])
                    dp[i]=Math.max(dp[i],dp[j]+1);
                if(dp[i]>res)
                    res=dp[i];
            }
        }
        return res;
    }
}

优化: dp[ i ] 表示 长度为  i+1 (序号从0开始) 的子序列的序列末尾元素的最小值.   

class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums==null || nums.length==0)
            return 0;
        int[] dp=new int[nums.length];
        dp[0]=nums[0];
        int end=0;
        for(int i=1;i<nums.length;i++){
            if(nums[i]>dp[end]){      // 增大子序列的长度
                end++;
                dp[end]=nums[i]; 
            }else{                       // 修改子序列的值 让其变小
                int left=0,right=end;
                while(left<right){
                    int mid=left+(right-left)/2;
                    if(dp[mid]<nums[i])
                        left=mid+1;
                    else
                        right=mid;
                }
                dp[right]=nums[i];
            }
        }
        return end+1;
    }
}

 

两个字符串之间的动态规划问题!!!!!

1143. 最长公共子序列

难度中等

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

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

 

示例 1:

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

示例 2:

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

示例 3:

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


思考: 求公共子串好像很容易 判断 dp[i+1][j+1]的时候只需要判断 str1.charAt(i+1)和str2.charAt(j+1)是否相等即可。但是公共子序列没那么简单!!!
因为不只是跟最后的元素有关系。
class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        if(text1==null||text2==null||text1.length()==0||text2.length()==0)
            return 0;
        int[][]dp=new int[text1.length()+1][text2.length()+1];
        for(int i=1;i<=text1.length();i++){
            for(int j=1;j<=text2.length();j++){
                if(text1.charAt(i-1)==text2.charAt(j-1))
                    dp[i][j]=dp[i-1][j-1]+1;
                else        // 与公共子串的不同就是多了一个 else 如果不想等,要赋予前面得到的最大值  因为子序列不要求连续
                    dp[i][j]=Math.max(Math.max(dp[i-1][j-1],dp[i-1][j]),dp[i][j-1]);
          //
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1]); // 可以写成这样 dp[i-1][j-1]就被包含在内了!!!!
            }
        }
        return dp[text1.length()][text2.length()];
    }
}

 

背包DP

322. 零钱兑换

难度中等

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

 

示例 1:

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

示例 2:

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

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

 

  这种背包问题,就是用两个循环,第二个循环用来遍历所有的可能,第一个循环用来让dp的下标不断增大,直至目标值。

class Solution {
    public int coinChange(int[] coins, int amount) {
        if(coins==null||coins.length==0||amount==0)
            return 0;
        int[] dp=new int[amount+1];
        for(int i=0;i<coins.length;i++){
            if(coins[i]<=amount)
                dp[coins[i]]=1;
        }
        for(int i=1;i<=amount;i++){
            for(int j=0;j<coins.length;j++){
                if(i-coins[j]>0 && dp[i-coins[j]]>0){
                    if(dp[i]>0)
                        dp[i]=Math.min(dp[i],dp[i-coins[j]]+1);
                    else
                        dp[i]=dp[i-coins[j]]+1;
                }
            }
        }
        return dp[amount]==0?-1:dp[amount];
    }
}

518. 零钱兑换 II

难度中等

给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。 

 

示例 1:

输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:
5=5
5=2+2+1
5=2+1+1+1
5=1+1+1+1+1

示例 2:

输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。

示例 3:

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

 

注意:

你可以假设:

  • 0 <= amount (总金额) <= 5000
  • 1 <= coin (硬币面额) <= 5000
  • 硬币种类不超过 500 种
  • 结果符合 32 位符号整数

思考:上面的那个背包问题,考虑的最优的结果。这个问题是求所有的可能结果的数量!!!!
    上面那个背包问题的 dp[i] 表示的就是达到i值所需的最少金币的数量; 这个问题的 dp[i]表示的应该是达到i值所有的可能组合的数量!!!

  但是,这里的 dp[i]是一个组合的问题,也就是跟顺序没有关系,所有不能有解决跳台阶的那种方式来直接用在这里,会出现重复元素!!
(我。。。 用金币作为第一层循环!!!!! 要达到的金额作为第二层循环。 这就解决了重复的问题! 秒啊!! 同样的时间复杂度,不一样的思维!!!!)
class Solution {
    public int change(int amount, int[] coins) {
        if(amount==0)          /// 总金额为0 ,用一种方案,感觉有点坑啊
            return 1;
        if(coins==null||coins.length==0)
            return 0;
        int[] dp=new int[amount+1];
        dp[0]=1;
        for(int i=0;i<coins.length;i++){
            for(int j=coins[i];j<=amount;j++){
                dp[j]=dp[j]+dp[j-coins[i]];
            }
        }
        return dp[amount];
    }
}

 

数位DP:
用来统计一个区间 [L,R]内满足某些限制条件的个数
这种问题都是与数的组成有关系!!!







posted @ 2020-08-11 18:36  你眼里的星辰  阅读(72)  评论(0)    收藏  举报