LeetCode DP篇-求子序列问题(1143、300、53、72)
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。
提示:
1 <= text1.length <= 1000
1 <= text2.length <= 1000
输入的字符串只含有小写英文字符。
思路
//思路1:暴力,找出其中一个string的所有子序列,然后拿去第二个进行匹配,匹配到即为公告子序列
//思路2:动态规划,将两个String当初二维数组的行列,从两个String的第一个字符关系进行比较,逐渐增加字符个数,递推进行
solution1 dp
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int m = text1.length();
int n = text2.length();
int[][] dp = new int[m+1][n+1];
for (int i=1; i<m+1; i++){
for (int j=1; j<n+1; j++){
if (text1.charAt(i-1) == text2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1] + 1; //(i,j)=(i-1,j-1),因此从1开始遍历到m+1
}else{
dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
}
}
}
return dp[m][n];
}
}
dp2
//通过将字符串转化为char数组提升性能
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
char[] chars1 = text1.toCharArray();
char[] chars2 = text2.toCharArray();
int m = text1.length();
int n = text2.length();
int[][] dp = new int[m+1][n+1];
for(int i = 1; i < m+1; i++){
for(int j = 1; j < n+1; j++){
if (chars1[i-1] == chars2[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[m][n];
}
}
最长公共子串
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
char[] chars1 = text1.toCharArray();
char[] chars2 = text2.toCharArray();
int m = text1.length();
int n = text2.length();
int[][] dp = new int[m+1][n+1];
int max = 0;
for(int i = 1; i < m+1; i++){
for(int j = 1; j < n+1; j++){
if (chars1[i-1] == chars2[j-1]){
dp[i][j] = dp[i-1][j-1] + 1;
max = Math.max(max,dp[i][j]);
}
}
}
return max;
}
}
300. 最长上升子序列
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
进阶: 你能将算法的时间复杂度降低到 O(n log n) 吗?
solution1 普通动态规划
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp,1);
for (int i = 0; i < nums.length; i++){
for (int j = 0; j < i; j++){
if(nums[j]<nums[i]) dp[i] = Math.max(dp[i],dp[j]+1);
}
}
int res = 0;
for(int i = 0; i < nums.length; i++){
res = Math.max(res,dp[i]);
}
return res;
}
}
//普通dp
//1.dp数组
//2.求base case
//2.数学归纳法求dp[i],即确定状态转移方程
solution2 优化dp
class Solution {
public int lengthOfLIS(int[] nums) {
int[] top = new int[nums.length];
int piles = 0;
for (int i = 0; i < nums.length; i++){
int left = 0, right = piles;
int curr = nums[i];
//二分查找,查找小于当前且最大的数
while(left < right){
int mid = (left + right) >> 1;
if (top[mid] > curr){
right = mid;
}else if(top[mid] < curr){
left = mid + 1;
}else{
right = mid;
}
}
//牌堆最大值小于当前,新建堆
if (left == piles) piles ++;
top[left] = curr;
}
return piles;
}
}
//优化dp
//在普通dp状态方程的求解上,利用二分查找,将时间复杂度缩小到 O(log N)
//参考资料:https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/dong-tai-gui-hua-she-ji-fang-fa-zhi-pai-you-xi-jia/
53. 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
进阶:
如果你已经实现复杂度为 O(n) 的解法,尝试使用更为精妙的分治法求解。
class Solution {
public int maxSubArray(int[] nums) {
int[] dp = new int[nums.length];
dp[0] = nums[0];
for(int i = 1; i < nums.length; i++){
dp[i] = Math.max(nums[i],nums[i]+dp[i-1]);
}
int res = Integer.MIN_VALUE;
for(int j = 0; j < nums.length; j++){
res = Math.max(dp[j],res);
}
return res;
}
}
//!!!考虑每一数加不加入之前还是另起数组
//1、dp数组,每一状态保存有当前数加入的最大和子串
//2、dp[0] = nums[0]
//3、状态转移:max(nums[i],nums[i]+dp[i-1])
72. 编辑距离
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
示例 1:
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
示例 2:
输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
solution1 暴力递归
class Solution {
public int minDistance(String word1, String word2) {
char[] c1 = word1.toCharArray();
char[] c2 = word2.toCharArray();
return helper(c1,c2,c1.length-1,c2.length-1);
}
public int helper(char[] c1, char[] c2, int n, int m){
if (n == -1) return m+1;
if (m == -1) return n+1;
if (c1[n] == c2[m]){
return helper(c1,c2,n-1,m-1); //相同不用改变
}else{ //删除、插入、交换改变最小的那一个
return Math.min(helper(c1,c2,n-1,m)+1,
Math.min(helper(c1,c2,n,m-1)+1,
helper(c1,c2,n-1,m-1)+1)
);
}
}
}
// String st = String.valueOf(c);
// char[] c = st.toCharArray();
//类似最长公共子序列
//思路1:暴力递归 找出每个操作中删除、插入、交换改变最小的那一个
solution2 带备忘录的递归(自顶向下)
class Solution {
public int minDistance(String word1, String word2) {
char[] c1 = word1.toCharArray();
char[] c2 = word2.toCharArray();
int[][] menu = new int[c1.length][c2.length];
return helper(c1,c2,c1.length-1,c2.length-1,menu);
}
public int helper(char[] c1, char[] c2, int n, int m, int[][] menu){
if (n == -1) return m+1;
if (m == -1) return n+1;
if (menu[n][m] != 0) return menu[n][m];
if (c1[n] == c2[m]){
return menu[n][m] = helper(c1,c2,n-1,m-1,menu); //相同不用改变
}else{ //删除、插入、交换改变最小的那一个
return menu[n][m] = Math.min(helper(c1,c2,n-1,m,menu)+1,
Math.min(helper(c1,c2,n,m-1,menu)+1,
helper(c1,c2,n-1,m-1,menu)+1)
);
}
}
}
// String st = String.valueOf(c);
// char[] c = st.toCharArray();
//类似最长公共子序列
//思路1:暴力递归 找出每个操作中删除、插入、交换改变最小的那一个
//思路2:加个备忘录进行记录
solution3 动态规划(自底向上)
class Solution {
public int minDistance(String word1, String word2) {
char[] c1 = word1.toCharArray();
char[] c2 = word2.toCharArray();
int[][] dp = new int[c1.length+1][c2.length+1];
//base case
for (int i = 0; i < c1.length+1; i++) dp[i][0] = i;
for (int j = 0; j < c2.length+1; j++) dp[0][j] = j;
//状态转移
for (int i = 1; i < c1.length+1; i++){
for (int j = 1; j < c2.length+1; j++){
if(c1[i-1] == c2[j-1]) dp[i][j] = dp[i-1][j-1];
else dp[i][j] = Math.min(dp[i-1][j]+1,Math.min(dp[i][j-1]+1,dp[i-1][j-1]+1));
}
}
return dp[c1.length][c2.length];
}
}
// String st = String.valueOf(c);
// char[] c = st.toCharArray();
//类似最长公共子序列
//思路1:暴力递归 找出每个操作中删除、插入、交换改变最小的那一个
//思路2:加个备忘录进行记录
//思路3:1.dp数组 2.base case 3.根据实际情况列动态递归方程