动态规划
动态规划是很重要的一种题型,更是一种很重要的思想!!!!!
把握住两点: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]内满足某些限制条件的个数
这种问题都是与数的组成有关系!!!
浙公网安备 33010602011771号