文章目录
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个字符可以解码的方法数
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)]
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();
}
}