动态规划三丶----典型例题


参考博文,点击这里
在这里插入图片描述

55.跳跃游戏

在这里插入图片描述
这道题采用动态规划,可能会超时,但是任然需要掌握。

class Solution {
    public boolean canJump(int[] nums) {
       boolean[] can = new boolean[nums.length];
        can[0]=true;
        for(int i=0;i<nums.length;i++){
            for(int j=0;j<i;j++){
                if(can[j] &&(j+nums[j]>=i)){
                    can[i]=true;
                    break;
                }
            }
        }
        return can[nums.length-1];
    }
}

使用贪心算法
LeetCode 题解

public class Solution {
    public boolean canJump(int[] nums) {
        int n = nums.length;
        int rightmost = 0;
        for (int i = 0; i < n; ++i) {
            if (i <= rightmost) {
                rightmost = Math.max(rightmost, i + nums[i]);
                if (rightmost >= n - 1) {
                    return true;
                }
            }
        }
        return false;
    }
}

45.跳跃游戏II

在这里插入图片描述
注意:这道题用动态规划可能会超时,但是还是需要掌握。

steps:代表我跳到这个位置最少需要几步
j:是一步能跳到i的位置,也就是i的上一个位置到j需要跳一步,所以是step[j]+1然后在求最小值。
Integer.MAX_VALUE:因为有些位置跳不上去,又因为他是求最小值,所以这里设置一个最大值,这是求解动态规划的一个技巧。

class Solution {
    public int jump(int[] nums) {
     // state,代表我跳到这个位置最少需要几步
        int[] steps = new int[nums.length];

        // initialize
        steps[0] = 0;
        for (int i = 1; i < nums.length; i++) {
            steps[i] = Integer.MAX_VALUE;
        }

        // function
        for (int i = 1; i < nums.length; i++) {
            for (int j = 0; j < i; j++) {
                if (steps[j] != Integer.MAX_VALUE && j + nums[j] >= i) {
                    //j:是一步能跳到i的位置,也就是i的上一个位置到j需要跳一步,所以是step[j]+1然后在求最小值。
                    steps[i] = Math.min(steps[i], steps[j] + 1);
                }
            }
        }
        
        // answer
        return steps[nums.length - 1];
    }
}

132.分割回文串II

注意:这里用了2次动态规划进行求解,判断回文串,用了一个动态规划,分割的时候有用了一次动态规划

思路:动态规划
首先是对回文串的判断,不像LeetCode131——分割回文串中的做法,对一个字符串s用指向首字符和末字符的双指针分别向后和向前遍历字符串s中的字符来判断s是否是回文串。

本题对回文串的判别方法使用动态规划的思路。对于字符串s,如果其首字符和末字符不相同,显然其不是一个回文串。如果其首字符和末字符相同,那么我们去判断字符串s出去首字符和末字符的子串是否是回文串。

状态定义:judge(i, j) -------- 字符串s中[i, j]范围内的子串是否是回文串

状态转移

(1)如果s.charAt(i) != s.charAt(j),judge(i)(j) = false。

(2)如果s.charAt(i) == s.charAt(j),

a:如果此时j - i <= 1,即[i, j]范围内的字符个数小于等于2个,judge(i)(j) = true。

b:否则,judge(i)(j) = judge(i + 1)(j - 1)。

很显然的一点是,如果字符串s本身就是一个回文串,那么其需要分割的次数就是0。

通过上述状态定义和状态转移,我们很容易地得到大小为n * n的二维矩阵judge,用来判断字符串s中[i, j]范围内的子串是否是回文串。

而题目要求的是将s分割成一些子串,使每个子串都是回文串,求解这个最少分割次数,我们还需要一个状态定义和状态转移方程。

状态定义:dp(i) -------- 表示s中第i个字符到第(n-1)个字符所构成的子串的最小分割次数

状态转移:

在[i, n - 1]范围内寻找切点j,使得满足s中[i, j]范围的子串是一个回文串,在所有的切点j中寻找总的最少的切分次数。即1 + dp[j + 1]的最小值,如果j + 1越界,即j已经是s中第n - 1个字符,那么说明s中[i, j]范围内的整个子串就是一个回文串。该最小值就是dp[i]的值。

对于上述两个状态定义和状态转移,其实可以一起进行,因为切分需要满足的条件是s中[i, j]范围内的子串是一个回文串。

时间复杂度和空间复杂度均是O(n ^ 2)。

JAVA代码:

class Solution {
    public int minCut(String s) {
        int n = s.length();
		boolean[][] judge = new boolean[n][n];
		int[] dp = new int[n]; // dp[i]表示s中第i个字符到第(n-1)个字符所构成的子串的最小分割次数
		for (int i = n - 1; i >= 0; i--) {
			dp[i] = Integer.MAX_VALUE;
			for (int j = i; j < n; j++) {
			//表示最后
				if (s.charAt(i) == s.charAt(j) && (j - i <= 1 || judge[i + 1][j - 1])) {
					judge[i][j] = true;
					if (j + 1 < n) {
						dp[i] = Math.min(dp[i], 1 + dp[j + 1]);
					}else{
						dp[i] = 0;
					}
				}
			}
		}
		return dp[0];
	}

}

139.单词切分

思路:
用dp[i]记录到i为止的字符串能够由1个或多个单词拼接而成,遍历字符串嵌套遍历字典 -> O(n*k)。

需要每次遍历给定字典中的字符串,查看当前字符串的指定长度子串是否与字典中的某字符串相同,若相同,则置dp[i+dict[j].length()] = true

最后返回dp[s.length()]即可。
注意看下面的注释。

class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        boolean[] dp = new boolean[s.length()+1];  //状态:表示到i为止的字符串能够由1个或多个单词拼接而成。
        //这里的+1:因为最大的坐标是s.length();  所以长度还需要加1
        //并且这里由于新建后,boolean的默认值是false。
            dp[0] = true;
        
            for(int i=0 ;i<s.length();i++){
            
                for(String word:wordDict){
                    //i+word.length()<=s.length():表示单词没有越界,取等于:是因为subString(前闭后开的原则)
                    if(dp[i]&&i+word.length()<=s.length()&&s.substring(i,i+word.length()).equals(word)){
                        dp[i+word.length()]=true;
                    }
                }
            }
            return dp[s.length()];
        }
    
}

1143. 最长公共子序列

在这里插入图片描述
在这里插入图片描述

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
     int m = text1.length();
     int n = text2.length();
     int[][] f = new int[m+1][n+1];  //这里因为下面会取到f[m][] f[][n],所以需要+1
        
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                f[i][j] = Math.max(f[i-1][j],f[i][j-1]);
                if(text1.charAt(i-1)==text2.charAt(j-1)){
                    f[i][j] = f[i-1][j-1]+1;
                }
            }
        }
        return f[m][n];
    }
}

1062.最长重复子串

注意:和上面的最长重复字序列,区分开来。
在这里插入图片描述
在这里插入图片描述

public class solution{
 public int longestCommonSubstring(String A,String B){
 		int m = A.length();
 		int n = B.length();
 		int[][] f = new int[m+1][n+1];
 		for(int i =1;i<=m;i++){
 		  for(int j =1;j<=n;j++){
 		  	if(A.charAt(i-1) == B.charAt(j-1)){
 		  		f[i][j] = f[i-1][j-1] + 1;
 		  		}else{
 		  		f[i][j] = 0;
 		  		   }
 		  	}
 		 }
 		 int max = 0;
 		for(int i = 1;i <= m;i++){
 		  for(int j =1;j <= n;j++){
 		     max = Math.max(max,f[i][j]);
 		     }
 		    }
 		 return max;

72.编辑距离

注意:Math.min 和max只能比较两个值之间的大小。如果是多个数,需要分次进行比较。
在这里插入图片描述
在这里插入图片描述

class Solution {
    public int minDistance(String word1, String word2) {
        int m = word1.length();
        int n = word2.length();
        int[][] f = new int[m+1][n+1];
        
        for(int i = 0;i<=m;i++){
            for(int j = 0;j<=n;j++){
                if(i == 0){
                    f[i][j] = j;
                    continue;
                }
                if( j ==0){
                    f[i][j] =i;
                    continue;
                }
              //  f[i][j] = Math.min(f[i][j-1]+1,f[i-1][j]+1,f[i-1][j-1]+1);
                  f[i][j] = Math.min(Math.min(f[i - 1][j], f[i][j - 1]), f[i - 1][j - 1]) + 1;
                if(word1.charAt(i-1) == word2.charAt(j-1)){
                    f[i][j] = Math.min(f[i][j],f[i-1][j-1]);
                }
            }
        }
        return f[m][n];
    }
}

115.不同的子序列

注意:在相等的情况下的取值。

class Solution {
    public int numDistinct(String s, String t) {
        int m =  s.length();
        int n =  t.length();
        if(s ==null|| t == null|| n>m){
            return 0;
        }
        
     
        int[][] f = new int[m+1][n+1]; //f[i][j]:s的前i个字符串中挑出T的前j个字符有多少种方案。
        
        for(int i=0;i<=s.length();i++){
            f[i][0]=1;
        }
        
        for(int i=1;i<=s.length();i++){
            for(int j=1;j<=t.length();j++){
                f[i][j] = f[i-1][j];  //最后一个不相等,将s的最后一个删除。
                
                if(s.charAt(i-1) == t.charAt(j-1)){
                    f[i][j] = f[i-1][j] + f[i-1][j-1]; //f[i-1][j]:表示不要最后一个,因为前i-1个已经解决问题了,后者是表示要。
                }
            }
        }
        return f[m][n];
        
    }
}

97.交错字符串

状态定义:

f(x, y) -------- s1中[0, x - 1]区间的子串和s2中[0, y - 1]区间的子串能否构成s3中[0, x + y - 1]区间的子串。

状态转移:

(1)当x == 0时,说明此时s1中[0, x - 1]区间的子串是一个空串。只需根据s2中[0, y - 1]区间的子串和s3中[0, y - 1]区间的子串判断即可。

(2)当y == 0时,说明此时s2中[0, y - 1]区间的子串是一个空串。只需根据s1中[0, x - 1]区间的子串和s3中[0, x - 1]区间的子串判断即可。

(3)当x != 0且y != 0时,分为两种情况

a:如果f(x - 1, y) == true,即s1中[0, x - 2]区间的子串能够和s2中[0, y - 1]区间的子串构成s3中[0, x + y - 2]区间的子串且s1.charAt(x - 1) == s3.charAt(x + y - 1),显然f(x, y)值为true。

b:如果f(x, y - 1) == true,即s1中[0, x - 1]区间的子串能够和s2中[0, y - 2]区间的子串构成s3中[0, x + y - 2]区间的子串且s2.charAt(y - 1) == s3.charAt(x + y - 1),显然f(x, y)值为true。

时间复杂度和空间复杂度均是O(n * m),其中n为字符串s1的长度,m为字符串s2的长度。

public class Solution {
 
    public boolean isInterleave(String s1, String s2, String s3) {
        int n1 = s1.length();
        int n2 = s2.length();
        int n3 = s3.length();
        if(n1 + n2 != n3){
            return false;
        }
        boolean[][] f = new boolean[n1 + 1][n2 + 1];
        f[0][0] = true;
        
        for (int i = 1; i < n1 + 1; i++) { //当y==0的时候,判断s1[0, x - 1]区间和s3中[0, x - 1]区间的
            if(!f[i - 1][0] || s1.charAt(i - 1) != s3.charAt(i - 1)){ //因为只剩他两了。只要一个不等就退出。
                break;
            }
            f[i][0] = true;
        }
        for(int i = 1; i < n2 + 1; i++){
            if(!f[0][i - 1] || s2.charAt(i - 1) != s3.charAt(i - 1)){
                break;
            }
            f[0][i] = true;
        }
        
        for (int i = 1; i < n1 + 1; i++) {
            for (int j = 1; j < n2 + 1; j++) {
                
                if(f[i - 1][j] && s1.charAt(i - 1) == s3.charAt(i + j - 1)){ //s1的最后一个等于s3的最后一个。
                    f[i][j] = true;
                    continue;  //这个Continue:很关键,而下面的不用加。是下面的会自动挑出本次循环。
                }
                if(f[i][j - 1] && s2.charAt(j - 1) == s3.charAt(i + j - 1)){
                    f[i][j] = true;
                }
            }
        }
        return f[n1][n2];
    }
}

4.例题:

120. 三角形最小路径和(动态规划的典型例题,需要注意)

在这里插入图片描述
在这里插入图片描述

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        
      if (triangle == null) return 0;
        if (triangle.size() == 1) return triangle.get(0).get(0); //边界条件
        
        int minTotal = Integer.MAX_VALUE;
        int[] aux = new int[triangle.size()];//声明一个辅助数组用于存储f[i][j]的数据,只有行数。
        
        for (int i=0; i<aux.length; i++){
        
            aux[i] = Integer.MAX_VALUE;
        }
        
        aux[0] = triangle.get(0).get(0);
        
        for (int i = 1; i < triangle.size(); i++) {
            
            for (int j = triangle.get(i).size()-1; j >= 0; j--) {
                if(j != 0){
                    //Math.min(aux[j], aux[j-1]):上一层中的,能到达此数的左右两个值中取最小的那个。
                    //triangle.get(i).get(j):表示每一行,中的元素,从后往前开始取。  
                    aux[j] = Math.min(aux[j], aux[j-1]) + triangle.get(i).get(j);
                }else{ 
                    //因为列数为0的时候,上一层相邻的元素只有第一个。
                    aux[j] = aux[j] + triangle.get(i).get(j);
                }
            }
        }
        for (int i = 0; i < aux.length; i++) {  //遍历:找出最后一行的最小值。
            minTotal = Math.min(aux[i], minTotal);
        }
        return minTotal;
    }
}

背包问题

问题:在n个物品中挑选若干物品装入背包,最多能装多满?假设背包的大小为m,每个物品的大小为A[i]。

样例
如果有4个物品[2, 3, 5, 7]
如果背包的大小为11,可以选择[2, 3, 5]装入背包,最多可以装满10的空间。
如果背包的大小为12,可以选择[2, 3, 7]装入背包,最多可以装满12的空间。
函数需要返回最多能装满的空间大小。
在这里插入图片描述
dp[i][j]为当背包总重量为j且有前i个物品时,背包最多装满dp[i][j]的空间。
状态转移方程为:dp[i][j] = Math.max(dp[i - 1][j - A[i]] + A[i], dp[i-1][j]);

dp[i - 1][j - A[i]] + A[i]为加入第i个物品后背包的总装满空间。
a.为了把第i个物品放进背包,背包当然要先腾出至少A[i]的空间,腾出后空间的最多装满空间为dp[ i - 1][j - A[i]],再加上第i个物品的空间A[i],即为当背包总空间为j时,装入第i个物品背包的总装满空间。
b.当然第i个物品所占的空间可能比此时背包的总空间j要大(j < A[i]),此时装不进第i个物品,因此此时背包的总装满空间为dp[i-1][j]。
c.还有一种可能的情形是,虽然第i个物品能够装入包中,但为了把第i个物品装入而拿出了其他物品,使此时的总装入空间dp[i-1][j-A[i]] + A[i] < dp[i-1][j]

其他情形:
当j = 0时,dp[i][0] = 0

原题答案即为dp[3][11] = 10,背包的总空间为11时,四个物品能够装入的最大空间为多大。

public class Solution {
    /**
     * @param m: An integer m denotes the size of a backpack
     * @param A: Given n items with size A[i]
     * @return: The maximum size
     */
    public int backPack(int m, int[] A) {
        int[][] dp = new int[A.length][m + 1];//动态规划矩阵
        for(int i = 0; i < A.length; i ++) {//背包空间为0时,不管要放第几个物品,可装满的背包空间为0.
            dp[i][0] = 0;
        }
        for(int j = 1; j < m + 1; j++) {
            if(A[0] <= j) {//当第0个物品的空间小于等于当前背包空间j时
                dp[0][j] = A[0];//背包可装满的最大空间是第0个物品的体积
            }else {//当第0个物品的空间大于当前背包空间j时
                dp[0][j] = 0;//背包可装满的最大空间是0
            }
            for(int i = 1; i < A.length; i++) {//当放第1个到第A.length-1个物品时
                if(A[i] > j) {//若该物品所占空间大于背包总空间(无论怎样腾背包空间,该物品无法放入背包
                    dp[i][j] = dp[i - 1][j];//背包可装满的最大空间不变
                }else {//若该物品所占空间小于等于背包总空间,则需将背包空间腾出至少A[i]后,将该物品放入。放入新物品后背包最大可装满空间可能更大,也可能变小大,取大值作为背包空间为j且放第i个物品时可以有的最大可装满空间。
                    dp[i][j] = Math.max(dp[i-1][j - A[i]] + A[i], dp[i - 1][j]);
                }
            }
        }
        return dp[A.length - 1][m];
    }
}


背包2

给出n个物品的体积A[i]和其价值V[i],将他们装入一个大小为m的背包,最多能装入的总价值有多大?

样例
对于物品体积[2, 3, 5, 7]和对应的价值[1, 5, 2, 4], 假设背包大小为10的话,最大能够装入的价值为9。

注意
A[i], V[i], n, m均为整数。你不能将物品进行切分。你所挑选的物品总体积需要小于等于给定的m。
在这里插入图片描述

public class Solution {
    /**
     * @param m: An integer m denotes the size of a backpack
     * @param A & V: Given n items with size A[i] and value V[i]
     * @return: The maximum value
     */
    public int backPackII(int m, int[] A, int V[]) {
        if(A == null || null == V || A.length == 0 || V.length == 0 || A.length != V.length) return 0;
        int[][] dp = new int[A.length][m + 1];//动态规划矩阵
        for(int i = 0; i < A.length; i++) {//背包空间为0时,不管要放第几个物品,可装的物品价值均为0.
            dp[i][0] = 0;
        }
        for(int i = 0; i < A.length; i++) {
            for(int j = 1; j < m + 1; j++) {
                if(i == 0) {//不管背包空间多大,放第0个物品时
                    if(A[i] <= j) {//若物品所占空间小于等于背包空间
                        dp[i][j] = V[i];//则此时背包所装物品价值为该物品价值
                    }else {//若该物品所占空间大于背包空间
                        dp[i][j] = 0;//则此时背包所装物品价值为0
                    }
                }else {//当放第1个到第A.length-1个物品时
                    if(A[i] > j) {//若该物品所占空间大于背包总空间(无论怎样腾背包空间,该物品无法放入背包)
                        dp[i][j] = dp[i - 1][j];//背包内价值不变
                    }else{//若该物品所占空间小于等于背包总空间,则需将背包空间腾出至少A[i]后,将该物品放入。放入新物品后背包价值可能更大,也可能还不如原来大,取大值作为背包空间为j且放第i个物品时可以有的最大价值。
                        dp[i][j] = Math.max(dp[i - 1][j - A[i]] + V[i], dp[i-1][j]);//
                    }
                }
            }
        }
        return dp[A.length - 1][m];
    }
}


K Sum

给定 n 个不同的正整数,整数 k(k <= n)以及一个目标数字 target。 
在这 n 个数里面找出 k 个数,使得这 k 个数的和等于目标数字,求问有多少种方案?
在这里插入图片描述

public class Solution {
    /**
     * @param A: an integer array.
     * @param k: a positive integer (k <= length(A))
     * @param target: a integer
     * @return an integer
     */
    public int  kSum(int A[], int k, int target) {
        int n = A.length;
        int[][][] f = new int[n + 1][k + 1][target + 1];
        for (int i = 0; i < n + 1; i++) {
            f[i][0][0] = 1;
        }
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= k && j <= i; j++) {
                for (int t = 1; t <= target; t++) {
                    f[i][j][t] = 0;
                    if (t >= A[i - 1]) {
                        f[i][j][t] = f[i - 1][j - 1][t - A[i - 1]];
                    }
                    f[i][j][t] += f[i - 1][j][t];
                } // for t
            } // for j
        } // for i
        return f[n][k][target];
    }
}

剑指offer 剪绳子

参考题解
这边还要跟自己相比是因为每次剪的长度都要跟之前的最大值比一下

class Solution {
    public int integerBreak(int n) {
        int[] dp = new int[n+1];
        if (n < 2) {
            return 0;
        }
        dp[2] =1;
        for(int i = 3; i <= n;i++){
            for(int j = 1;j<i;j++)
            dp[i] = Math.max(Math.max(j * dp[i - j], j * (i - j)), dp[i]);
        }
        return dp[n];
    }
}

343.整数拆分

class Solution {
  
    public int integerBreak(int n) {
       int[] dp = new int[n+1];
       dp[0]= dp[1] =0;
       
       for(int i = 2;i<=n;i++){
           int max = Integer.MIN_VALUE;
           for(int j =1;j <i;j++){
             max = Math.max(max,Math.max(j*(i-j),j*dp[i-j]));
           }
           dp[i] = max;
           
       }
       return dp[n];
    }
}

LeetCode 152

解题思路
代码思路

public class Solution {

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

        int preMax = nums[0];
        int preMin = nums[0];

        // 滚动变量
        int curMax;
        int curMin;

        int res = nums[0];
        for (int i = 1; i < len; i++) {
            if (nums[i] >= 0) {
                curMax = Math.max(preMax * nums[i], nums[i]);
                curMin = Math.min(preMin * nums[i], nums[i]);
            } else {
                curMax = Math.max(preMin * nums[i], nums[i]);
                curMin = Math.min(preMax * nums[i], nums[i]);
            }
            res = Math.max(res, curMax);

            // 赋值滚动变量
            preMax = curMax;
            preMin = curMin;
        }
        return res;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值