动态规划
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” )。
问总共有多少条不同的路径?
链接: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]
的路径。
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最常重复子数组
给两个整数数组
A
和B
,返回两个数组中公共的、长度最长的子数组的长度。输入: 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
注意:数组和序列的区别在于,数组要是连续的;序列可以不连续。
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];
}
}
类似题型:
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));
}
}