120.64.139.494.376.213.337.309.279.91.62.322.377.474

T170Climbing Staris

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

//(1)递归--记忆化搜索
public int climbStairs(int n){
    memo=new int[n+1];
    Arrays.fill(mem,-1);
    return calWays(n);
}
private int calcWays(int n){
	if(n==0||n==1)
        return 1;
    if(memo[n]==-1)
        memo[n]=calcWays(n-1)+calcWays(n-2);
    return memo[n];
}
T343.Integer Break
//递归:记忆化搜索O(n2)
class Solution {
    private int[] memo;
	public int integerBreak(int n) {
		assert(n>0);
		memo=new int[n+1];
		Arrays.fill(memo, -1);
		return breakInteger(n);
    }
	private int breakInteger(int n) {
		if(n==1)
			return 1;
		if(memo[n]!=-1)
			return memo[n];
		int res=-1;
		for(int i=1;i<=n-1;i++) {
			//i+(n-i)
			res=max3(res,i*(n-i),i*breakInteger(n-i));
		}
		memo[n]=res;
		return res;
	}
	private int max3(int a, int b, int c){
        return Math.max(a, Math.max(b, c));
    }
}
//动态规划
//1、确定dp数组以及下标的含义 dp[i]表示分拆数字i可以得到的最大乘积
//2、确定递推公式 dp[i]=max(dp[i],max(i-j)*j,dp[i-j]*j);
//3、初始化 dp[2]=1
//4、确定遍历顺序 从前向后遍历
class Solution {
    public int integerBreak(int n) {
        int[] dp=new int[n+1];
        dp[2]=1;
        for(int i=3;i<=n;i++){
            for(int j=1;j<i;j++){
                dp[i]=max3(dp[i],(i-j)*j,dp[i-j]*j);
            }
        }
        return dp[n];
    }
    private int max3(int a, int b, int c){
        return Math.max(a, Math.max(b, c));
    }
}
T198

两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报装置的情况下 ,一夜之内能够偷窃到的最高金额。

//(1)记忆化搜索
//(2)动态规划
class Solution {
    public int rob(int[] nums) {
        int n=nums.length;
        int[] memo=new int[n];
        memo[n - 1] = nums[n - 1];
        if(n==0)
            return 0;
        for(int i=n-2;i>=0;i--){
            for(int j=i;j<n;j++)
                memo[i]=Math.max(memo[i],nums[j]+(j+2<n?memo[j+2]:0));
        }
        return memo[0];
    }
}
T120

给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

相邻的结点 在这里指的是 下标 与 上一层结点下标 相同或者等于 上一层结点下标 + 1 的两个结点。

思路

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mm8JRvFv-1619870552022)(image/image-20201206165611972.png)]

class Solution {
    public int minimumTotal(List<List<Integer>> triangle) {
        int n = triangle.size();
        int[][] f = new int[n][n];
        f[0][0] = triangle.get(0).get(0);
        for (int i = 1; i < n; ++i) {
            f[i][0] = f[i - 1][0] + triangle.get(i).get(0);
            for (int j = 1; j < i; ++j) {
                f[i][j] = Math.min(f[i - 1][j - 1], f[i - 1][j]) + triangle.get(i).get(j);
            }
            f[i][i] = f[i - 1][i - 1] + triangle.get(i).get(i);
        }
        int minTotal = f[n - 1][0];
        for (int i = 1; i < n; ++i) {
            minTotal = Math.min(minTotal, f[n - 1][i]);
        }
        return minTotal;
    }
}
T64

给定一个包含非负整数的 *m* x *n* 网格 grid ,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

**说明:**每次只能向下或者向右移动一步。

由于路径的方向只能是向下或向右,因此网格的第一行的每个元素只能从左上角元素开始向右移动到达,网格的第一列的每个元素只能从左上角元素开始向下移动到达,此时的路径是唯一的,因此每个元素对应的最小路径和即为对应的路径上的数字总和。

class Solution {
    public int minPathSum(int[][] grid) {
        if (grid == null || grid.length == 0 || grid[0].length == 0) {
            return 0;
        }
        int rows = grid.length, columns = grid[0].length;
        int[][] dp = new int[rows][columns];
        dp[0][0] = grid[0][0];
        for (int i = 1; i < rows; i++) {
            dp[i][0] = dp[i - 1][0] + grid[i][0];
        }
        for (int j = 1; j < columns; j++) {
            dp[0][j] = dp[0][j - 1] + grid[0][j];
        }
        for (int i = 1; i < rows; i++) {
            for (int j = 1; j < columns; j++) {
                dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
            }
        }
        return dp[rows - 1][columns - 1];
    }
}

https://leetcode-cn.com/problems/combination-sum-iv/solution/xi-wang-yong-yi-chong-gui-lu-gao-ding-bei-bao-wen-/

139.

给定一个非空字符串 s 和一个包含非空单词的列表 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。
将字符串看作背包,List中的单词看作物品,可以重复取,因此是完全背包。

定义dp[i]表示字符串 s前 i个字符组成的字符串 s[0…i-1]是否能被空格拆分成若干个字典中出现的单词。

转移方程:dp[i]=dp[j] && check(s[j…i−1]),其中 check(s[j…i−1]) 表示子串 s[j…i-1]是否出现在字典中。

public class Solution {
    public boolean wordBreak(String s, List<String> wordDict) {
        Set<String> wordDictSet = new HashSet(wordDict);
        boolean[] dp = new boolean[s.length() + 1];
        dp[0] = true;
        for (int i = 1; i <= s.length(); i++) {
            for (int j = 0; j < i; j++) {
                if (dp[j] && wordDictSet.contains(s.substring(j, i))) {
                    dp[i] = true;
                    break;
                }
            }
        }
        return dp[s.length()];
    }
}
494.目标和

使用回溯法会超时

如何转化成01背包问题呢?

假设加法的总和

dp[i][j] 表示用数组中的前 i 个元素,组成和为 j 的方案数。考虑第 i 个数 nums[i],它可以被添加 +-

dp[i][j]定义为从数组nums中 0 - i 的元素进行加减可以得到 j 的方法数量

状态转移方程:

d[i][j]=dp[i-1][j-nums[i]]+dp[i-1][j+nums[i]]
public class Solution {
    public int findTargetSumWays(int[] nums, int S) {
        int[][] dp = new int[nums.length][2001];
        dp[0][nums[0] + 1000] = 1;
        dp[0][-nums[0] + 1000] += 1;
        for (int i = 1; i < nums.length; i++) {
            for (int sum = -1000; sum <= 1000; sum++) {
                if (dp[i - 1][sum + 1000] > 0) {
                    dp[i][sum + nums[i] + 1000] += dp[i - 1][sum + 1000];
                    dp[i][sum - nums[i] + 1000] += dp[i - 1][sum + 1000];
                }
            }
        }
        return S > 1000 ? 0 : dp[nums.length - 1][S + 1000];
    }
}
213.打家劫舍

环状排列意味着第一个房子和最后一个房子中只能选择一个偷窃,因此可以把此环状排列房间问题约化为两个单排排列房间子问题:

在不偷窃第一个房子的情况下(即 nums[1:]),最大金额是 p1
在不偷窃最后一个房子的情况下(即 nums[:n-1]),最大金额是 p2
综合偷窃最大金额: 为以上两种情况的较大值,即 max(p1,p2)max(p1,p2) 。

198:打家劫舍思路

状态定义:dp[i]代表前n个房子在满足条件下的能偷窃到的最高金额。

k>2时:

  • 偷窃第 k 间房屋,那么就不能偷窃第 k-1 间房屋,偷窃总金额为前 k-2 间房屋的最高总金额与第 k间房屋的金额之和。

  • 不偷窃第 k 间房屋,偷窃总金额为前 k-1 间房屋的最高总金额。

转移方程: dp[n+1] = max(dp[n],dp[n-1]+num)

class Solution {
    public int rob(int[] nums) {
        if(nums.length == 0) return 0;
        if(nums.length == 1) return nums[0];
        return Math.max(myRob(Arrays.copyOfRange(nums, 0, nums.length - 1)), 
                        myRob(Arrays.copyOfRange(nums, 1, nums.length)));
    }
    private int myRob(int[] nums) {
        int pre = 0, cur = 0, tmp;
        for(int num : nums) {
            tmp = cur;
            cur = Math.max(pre + num, cur);
            pre = tmp;
        }
        return cur;
    }
}
337.打家劫舍3

我们可以用 f(o) 表示选择 o节点的情况下,o节点的子树上被选择的节点的最大权值和;g(o) 表示不选择 o 节点的情况下,o 节点的子树上被选择的节点的最大权值和;l 和 r 代表 o 的左右孩子。

  • 当 o 被选中时,o 的左右孩子都不能被选中,故 o被选中情况下子树上被选中点的最大权值和为 l和 r 不被选中的最大权值和相加,即 f(o) = g(l) + g®
  • 当 o不被选中时,o的左右孩子可以被选中,也可以不被选中。对于 o的某个具体的孩子 x,它对 o 的贡献是 x 被选中和不被选中情况下权值和的较大值。故 g(o) = max { f(l) , g(l)}+max{ f® , g® }
309.最佳买卖股票时机含冷冻期

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sC1KRevb-1619870552025)(image/image-20201215112331464.png)]

dp[i][0]表示在i天买入最大利益

dp[i][1]表示在i天卖出最大利益

dp[i][2]表示在经过卖出的后一天冷冻期的最大利益

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xN087RZ2-1619870552027)(image/image-20201215221337091.png)]

class Solution {
    public int maxProfit(int[] prices) {
        if (prices.length == 0) {
            return 0;
        }
        int n = prices.length;
        // f[i][0]: 手上持有股票的最大收益
        // f[i][1]: 手上不持有股票,并且处于冷冻期中的累计最大收益
        // f[i][2]: 手上不持有股票,并且不在冷冻期中的累计最大收益
        int[][] f = new int[n][3];
        f[0][0] = -prices[0];
        for (int i = 1; i < n; ++i) {
            f[i][0] = Math.max(f[i - 1][0], f[i - 1][2] - prices[i]);
            f[i][1] = f[i - 1][0] + prices[i];
            f[i][2] = Math.max(f[i - 1][1], f[i - 1][2]);
        }
        return Math.max(f[n - 1][1], f[n - 1][2]);
    }
}
class Solution {
    public int maxProfit(int[] prices) {
        int n=prices.length;
        if(n==0) return 0;
        int sold=0;
        int rest=0;
        int hold=-prices[0];
        for(int price:prices){
            int pre_sold=sold;
            sold=hold+price;
            hold=Math.max(hold,rest-price);
            rest=Math.max(rest,pre_sold);
        }
        return Math.max(rest,sold);

    }
}
T279.完全平方数

给定正整数 n,找到若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。

class Solution {
    public int numSquares(int n) {
        int[] dp = new int[n + 1]; // 默认初始化值都为0
        for (int i = 1; i <= n; i++) {
            dp[i] = i; // 最坏的情况就是每次+1
            for (int j = 1; i - j * j >= 0; j++) { 
                dp[i] = Math.min(dp[i], dp[i - j * j] + 1); // 动态转移方程
            }
        }
        return dp[n];
    }
}
//O(n^3/2)
T91.解码方法,分割整数问题

dp(i)表示前i个字符可以解码的方法数

image.png
class Solution {
    public int numDecodings(String s) {
        int n = s.length();
        if(n == 0) return 0;
        int[] dp = new int[n + 1];
        dp[0] = 1;
        dp[1] = s.charAt(0) == '0' ? 0 : 1;
        for(int i = 1; i < n; i++){
            
            if(s.charAt(i-1) == '1' || s.charAt(i-1) == '2' && s.charAt(i) <'7'){
                //如果是20、10
                if(s.charAt(i) == '0') dp[i + 1] = dp[i - 1];
                //如果是11-19、21-26
                else dp[i + 1] = dp[i] + dp[i - 1];
            }else if(s.charAt(i) == '0'){
                //如果是0、30、40、50
                return 0;
            }else{
                //i-1和i无法构成一个字母
                dp[i + 1] = dp[i];
            }
        }
        return dp[n];
    }
}
T62.不同路径

m*n的网格,机器人从左上角每次只能向下或者向右移动一步,机器人试图达到网格的右下角,总共有多少天不同的路径?

class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp=new int[m][n];
        dp[0][0]=1;
        for(int j=0;j<n;j++)
            dp[0][j]=1;
        for(int i=0;i<m;i++)
            dp[i][0]=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];
    }
}
322.兑换零钱

定义 F(i)F(i) 为组成金额 ii 所需最少的硬币数量

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JRs6kk8M-1619870552030)(image/image-20201217103354383.png)]

image-20201217104411693
class Solution {
    public int coinChange(int[] coins, int amount) {
        // 自底向上的动态规划
        if(coins.length == 0){
            return -1;
        }

        // memo[n]的值: 表示的凑成总金额为n所需的最少的硬币个数
        int[] memo = new int[amount+1];
        // 给memo赋初值,最多的硬币数就是全部使用面值1的硬币进行换
        // amount + 1 是不可能达到的换取数量,于是使用其进行填充
        Arrays.fill(memo,amount+1);
        memo[0] = 0;
        for(int i = 1; i <= amount;i++){
            for(int j = 0;j < coins.length;j++){
                if(i - coins[j] >= 0){
                    // memo[i]有两种实现的方式,
                    // 一种是包含当前的coins[i],那么剩余钱就是 i-coins[i],这种操作要兑换的硬币数是 memo[i-coins[j]] + 1
                    // 另一种就是不包含,要兑换的硬币数是memo[i]
                    memo[i] = Math.min(memo[i],memo[i-coins[j]] + 1);
                }
            }
        }

        return memo[amount] == (amount+1) ? -1 : memo[amount];
    }
}

377.给定一个由正整数组成且不存在重复数字的数组,找出和为给定目标正整数的组合的个数。

定义dp数组含义:dp[i]定义:一个人跳台阶,每次可以选择跳num阶(num in nums),他要跳到第i级台阶总共有多少种跳法。显然,跳到第i级台阶的方法数为跳到 dp[i-num] for num in nums的方法数之和,因为他只要跳到第i-num级,再一步跳num级,就可以到第i级了。

dp[i] 代表目标正整数i有几种组合结果
dp[i - num] 代表选择了num之后的新的目标正整数i - num 有几种组合结果
dp[0] = 1 代表这是一个空集,加上我们选择的数(目标正整数本身)就是我们的目标正整数

class Solution {
    public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target + 1];//默认为0
        dp[0] = 1;
        for (int i = 1; i <= target; i++) {
            for (int num : nums) {
                if (i - num >= 0) {
                    dp[i] += dp[i - num];
                }
            }
        }
        return dp[target];
    }
}

背包问题:https://leetcode-cn.com/problems/combination-sum-iv/solution/xi-wang-yong-yi-chong-gui-lu-gao-ding-bei-bao-wen-/

动态规划问题总结:https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92.md#0-1-%E8%83%8C%E5%8C%85

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-h5BM4Gfk-1619870552031)(image/image-20201217160134350.png)]

474.一和零

这道题和经典的背包问题很类似,不同的是在背包问题中,我们只有一种容量,而在这道题中,我们有 0 和 1 两种容量。每个物品(字符串)需要分别占用 0 和 1 的若干容量,并且所有物品的价值均为 1。因此我们可以使用二维的动态规划。

  • dp数组定义dp(i,j):最多有i个0和j个1的strs的最大子集的大小

  • 递推公式,dp[i][j】可以由前一个strs里的字符串推导出来,strs里的字符串有zeroNum个0,oneNum个1。

    dp[i][j] = max(dp[i][j], dp[i - zeroNum][j - oneNum] + 1);
    
  • 初始化:初始为0

  • 确定遍历顺序:

    01背包为什么一定是外层for循环遍历物品,内层for循环遍历背包容量且从后向前遍历!

    那么本题也是,物品就是strs里的字符串,背包容量就是题目描述中的m和n。

class Solution {
    public int findMaxForm(String[] strs, int m, int n) {
        int[][] dp=new int[m+1][n+1];
        for(String s:strs){
            int[] count=countzeroesones(s);
            for(int zeroes=m;zeroes>=count[0];zeroes--)
                for(int ones=n;ones>=count[1];ones--)
                    dp[zeroes][ones]=Math.max(1+dp[zeroes-count[0]][ones-count[1]],dp[zeroes][ones]);
        }
        return dp[m][n];
    }
    public int[] countzeroesones(String s) {
        int[] c = new int[2];
        for (int i = 0; i < s.length(); i++) {
            c[s.charAt(i)-'0']++;
        }
        return c;
    }
}
01背包

https://www.zhihu.com/column/c_1220413174077423616

状态转移方程:

F(n,C)考虑将n个物品放进容量为C的背包,使得价值最大

F(i,C)=max(F(i-1,c),v(i)+F(i-1,c-w(i)))

//递归
public class knapsack01 {
	private int[][] memo;
	public int knapsack(int[] w,int[] v,int C) {
		if(w==null||v==null||w.length!=v.length)
			throw new IllegalArgumentException("Invalid w or v");
		if(C<0)
			throw new IllegalArgumentException("C must be greater or equal to zero.");
		int n=w.length;
		if(n==0||C==0)
			return 0;
		memo=new int[n][C+1];
		for(int i=0;i<n;i++)
			for(int j=0;j<=C;j++)
				memo[i][j]=-1;
		return bestValue(w,v,n-1,C);
	}
    //用 [0...index]的物品,填充容积为c的背包的最大价值
	private int bestValue(int[] w,int[] v,int index,int c) {
		if(c<=0||index<0)
			return 0;
		if(memo[index][c]!=-1)
			return memo[index][c];
		int res=bestValue(w,v,index-1,c);
		if(c>=w[index])
			res=Math.max(res, v[index]+bestValue(w,v,index-1,c-w[index]));
		return memo[index][c]=res;
	}
}
//动态规划
/// 时间复杂度: O(n * C) 其中n为物品个数; C为背包容积
/// 空间复杂度: O(n * C)
//dp[i][j] 表示从下标为[0-i]的物品里任意取,放进容量为j的背包,价值总和最大是多少。
// dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
//初始化:dp[i][0]=0& dp[0][j](存放编号0的物品的时候,各个容量的背包所能存放的最大价值)
public class knapsack01 {
	private int[][] memo;
	public int knapsack(int[] w,int[] v,int C) {
		if(w==null||v==null||w.length!=v.length)
			throw new IllegalArgumentException("Invalid w or v");
		if(C<0)
			throw new IllegalArgumentException("C must be greater or equal to zero.");
		int n=w.length;
		if(n==0||C==0)
			return 0;
		memo=new int[n][C+1];
        //初始化
		for(int j=0;j<=C;j++)
			memo[0][j]=w[0]<=j?v[0]:0;
		for(int i=1;i<n;i++)//遍历物品
			for(int j=0;j<=C;j++){//遍历背包容量
				memo[i][j]=memo[i-1][j];
				if(j>=w[i])
					memo[i][j]=Math.max(memo[i][j], v[i]+memo[i-1][j-w[i]]);
			}
		return memo[n-1][C];
	}
	
}
//优化
//dp[j]表示容量为j的背包,所背物品价值可以最大为dp[j]
//dp[j] = max(dp[j], dp[j - weight[i]] + value[i]);
//初始化为0
//遍历顺序:一维遍历时背包从大到小遍历
public int knapsack01(int[] w, int[] v, int C){
        if(w == null || v == null || w.length != v.length)
            throw new IllegalArgumentException("Invalid w or v");
        if(C < 0)
            throw new IllegalArgumentException("C must be greater or equal to zero.");
        int n = w.length;
        if(n == 0 || C == 0)
            return 0;
        int[] memo=new int[C+1];//只有一行,是一维数组
     	for(int j = 0 ; j <= C ; j ++)
            memo[j] = (j >= w[0] ? v[0] : 0);//初始值
     	for(int i=1;i<n;i++)
            for(j=C;j>=w[i];j--)
                memo[j]=Math.max(memo[j],v[i]+memo[j-w[i]]);
     	return memo[C];
 }
T416.分割等和子集

给定一个只包含正整数非空数组。是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

典型的背包问题,在n个物品中选出一定物品,填满sum/2的背包

F(n,C)考虑将n个物品填满容量为C的背包

F(i,C)=F(i-1,c)||F(i-1,c-w(i))

/// 时间复杂度: O(len(nums) * O(sum(nums)))
/// 空间复杂度: O(len(nums) * O(sum(nums)))
public class Solution1 {
    // memo[i][c] 表示使用索引为[0...i]的这些元素,是否可以完全填充一个容量为c的背包
    // -1 表示为未计算; 0 表示不可以填充; 1 表示可以填充
    private int[][] memo;
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for(int i = 0 ; i < nums.length ; i ++){
            if(nums[i] <= 0)
                throw new IllegalArgumentException("numbers in nums must be greater than zero.");
            sum += nums[i];
        }
        if(sum % 2 == 1)
            return false;
        memo = new int[nums.length][sum / 2 + 1];
        for(int i = 0 ; i < nums.length ; i ++)
            Arrays.fill(memo[i], -1);
        return tryPartition(nums, nums.length - 1, sum / 2);
    }
    // 使用nums[0...index], 是否可以完全填充一个容量为sum的背包
    private boolean tryPartition(int[] nums, int index, int sum){
        if(sum == 0)
            return true;
        if(sum < 0 || index < 0)
            return false;
        if(memo[index][sum] != -1)
            return memo[index][sum] == 1;
        memo[index][sum] = (tryPartition(nums, index - 1, sum) ||
                tryPartition(nums, index - 1, sum - nums[index])) ? 1 : 0;
        return memo[index][sum] == 1;
    }
}
//动态规划+空间优化
public class S416 {
	 public boolean canPartition(int[] nums) {
		 int sum=0;
		 for(int i = 0 ; i < nums.length ; i ++){
	            if(nums[i] <= 0)
	                throw new IllegalArgumentException("numbers in nums must be greater than zero.");
	            sum += nums[i];
	     }
		 if(sum%2==1)
			 return false;
		 int n=nums.length;
		 int C=sum/2;
		 boolean[] memo=new boolean[C+1];
		 for(int i=0;i<=C;i++)
			 memo[i]=(nums[0]==i);
		 for(int i=1;i<n;i++)
			 for(int j=C;j>=nums[i];j--)
				 memo[j]=memo[j]||memo[j-nums[i]];
		 return memo[C];
	 }
}
T300 .给定一个无序的整数数组,找到其中最长上升子序列的长度。

LIS(i)表示以第i个数字为结尾的最长上升子序列的长度。表示[0…i]的范围内,选择数字nums[i]可以获得的最长上升子序列的长度。

状态转移方程:LIS(i)=max(1+LIS(j) if nums[i]>nums[j]) j<i

//O(n^2)
class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums.length==0)
            return 0;
        int n=nums.length;
        int[] memo=new int[n];
        Arrays.fill(memo,1);
        for(int i=1;i<n;i++)
            for(int j=0;j<i;j++)
                if(nums[j]<nums[i])
                    memo[i]=Math.max(memo[i],1+memo[j]);
        int res=memo[0];
        for(int i=1;i<n;i++)
            res=Math.max(res,memo[i]);
        return res;
    }
}
T376最长公共子序列LCS

LCS(m,n) S1[0…m]和S2[0…n]的最长公共子序列的长度,从后往前。

s1[m]==s2[n]—LCS(m,n)=1+LCS(m-1,n-1)

s1[m]!=s2[n]—LCS(m,n)=max(LCS(m-1,n),LCS(m,n-1 ))

public class LCS {
    public String lcs(String s1, String s2){
        int m = s1.length();
        int n = s2.length();
        // memo 是 (m + 1) * (n + 1) 的动态规划表格
        // memo[i][j] 表示s1的前i个字符和s2前j个字符的最长公共子序列的长度
        // 其中memo[0][j] 表示s1取空字符串时, 和s2的前j个字符作比较
        // memo[i][0] 表示s2取空字符串时, 和s1的前i个字符作比较
        // 所以, memo[0][j] 和 memo[i][0] 均取0
        // 我们不需要对memo进行单独的边界条件处理 :-)
        int[][] memo = new int[m + 1][n + 1];

        // 动态规划的过程
        // 注意, 由于动态规划状态的转变, 下面的i和j可以取到m和n
        for(int i = 1 ; i <= m ; i ++)
            for(int j = 1 ; j <= n ; j ++)
                if(s1.charAt(i - 1) == s2.charAt(j - 1))
                    memo[i][j] = 1 + memo[i - 1][j - 1];
                else
                    memo[i][j] = Math.max(memo[i - 1][j], memo[i][j - 1]);

        // 通过memo反向求解s1和s2的最长公共子序列
        m = s1.length();
        n = s2.length();
        StringBuilder res = new StringBuilder("");
        while(m > 0 && n > 0)
            if(s1.charAt(m - 1) == s2.charAt(n - 1)){
                res.insert(0, s1.charAt(m - 1));
                m --;
                n --;
            }
            else if(memo[m - 1][n] > memo[m][n - 1])
                m --;
            else
                n --;

        return res.toString();
    }
}

m ; i ++)
for(int j = 1 ; j <= n ; j ++)
if(s1.charAt(i - 1) == s2.charAt(j - 1))
memo[i][j] = 1 + memo[i - 1][j - 1];
else
memo[i][j] = Math.max(memo[i - 1][j], memo[i][j - 1]);

    // 通过memo反向求解s1和s2的最长公共子序列
    m = s1.length();
    n = s2.length();
    StringBuilder res = new StringBuilder("");
    while(m > 0 && n > 0)
        if(s1.charAt(m - 1) == s2.charAt(n - 1)){
            res.insert(0, s1.charAt(m - 1));
            m --;
            n --;
        }
        else if(memo[m - 1][n] > memo[m][n - 1])
            m --;
        else
            n --;

    return res.toString();
}

}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值