LeetCode1143. 最长公共子序列/300. 最长递增子序列//1713. 得到子序列的最少操作次数(好题!!!!!)
Posted Zephyr丶J
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode1143. 最长公共子序列/300. 最长递增子序列//1713. 得到子序列的最少操作次数(好题!!!!!)相关的知识,希望对你有一定的参考价值。
2021.7.26 每日一题非常好,所以单独发一个,前两个题是前置问题
1143. 最长公共子序列
题目描述
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。
两个字符串的 公共子序列 是这两个字符串所共同拥有的子序列。
示例 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, text2.length <= 1000
text1 和 text2 仅由小写英文字符组成。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-common-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
动态规划,f[i][j]表示两个字符串分别以i和j结尾的最长公共子序列长度
如果text1[i]和text[j]相同,那么长度就是f[i - 1][j - 1]+1
如果不相同,那么长度就是max(f[i-1][j], f[i][j-1])
我这里相同的情况写的更加严谨了一点,其实不这样写也是正确的,之前也简单分析过
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
//和昨天那个差不多,动态规划,但是需要考虑之前每一个状态了
int l1 = text1.length();
int l2 = text2.length();
//dp表示在两个字符串中以i、j位置结尾的最长公共子序列最大长度
int[][] dp = new int[l1+ 1][l2 + 1];
for(int i = 1; i <= l1; i++){
for(int j = 1; j <= l2; j++){
if(text1.charAt(i - 1) == text2.charAt(j - 1)){
dp[i][j] = Math.max(dp[i - 1][j - 1] + 1,Math.max(dp[i][j - 1], dp[i - 1][j]));
}
else{
dp[i][j] = Math.max(dp[i][j - 1], dp[i - 1][j]);
}
}
}
return dp[l1][l2];
}
}
300. 最长递增子序列
题目描述
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
1 <= nums.length <= 2500
-104 <= nums[i] <= 104
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
常规思路动规:
class Solution {
public int lengthOfLIS(int[] nums) {
//是动态规划吗,以每个数字为结尾的最大长度?
//那么状态转移方程呢,就是dp[i] = 1 + dp[比i小的所有数字]中的最大值
int l = nums.length;
if(l == 0)
return 0;
int maxlen = 1;
int[] dp = new int[l];
dp[0] = 1;
for(int i = 1; i < l; i++){
int maxpart = 0;
for(int j = 0; j < i; j++){
//如果这个数比当前数小,那么就比较它的dp
if(nums[j] < nums[i])
maxpart = Math.max(dp[j], maxpart);
}
dp[i] = 1 + maxpart;
maxlen = Math.max(maxlen, dp[i]);
}
return maxlen;
}
}
要求复杂度为O(nlogn),所以优化
考虑改变状态的定义:dp[i]定义为长度为 i 的最小的末尾元素
这样,dp这个数组就变的有序起来了,而思路就是尽可能的使递增的元素变小
那么,如何去维护这个数组呢,遍历原数组,如果大于末尾元素,说明能使递增序列继续加1
如果比末尾元素小,那么这个元素必然不可能使有序序列长度增加了,但是能使长度为 i 的序列末尾元素变小,
用二分法找到这个元素进行替换
这样,长度为 i 的最小末尾元素就确定下来了,而dp的长度就是最长递增序列的长度
class Solution {
public int lengthOfLIS(int[] nums) {
int l = nums.length;
//数组 d[i] ,表示长度为 i 的最长上升子序列的末尾元素的最小值
int[] d = new int[l + 1];
int len = 1; //目前长度
d[1] = nums[0];
for(int i = 1; i < l; i++){
//如果当前值大于末尾值,表示可以扩展长度
if(nums[i] > d[len]){
d[++len] = nums[i];
}else{
//如果小于等于末尾值,那么说明可以替换当前d[]数组中的元素,使得一个位置最长上升子序列的末尾值减小
//找第一个大于等于nums[i]的下标
int left = 1;
int right = len;
while(left < right){
int mid = (right - left) / 2 + left;
if(d[mid] < nums[i]){
left = mid + 1;
}else{
right = mid;
}
}
d[left] = nums[i];
//表示left长度的递增子序列,末尾值可以更小
}
}
return len;
}
}
1713. 得到子序列的最少操作次数
2021.7.26 每日一题
题目描述
给你一个数组 target ,包含若干 互不相同 的整数,以及另一个整数数组 arr ,arr 可能 包含重复元素。
每一次操作中,你可以在 arr 的任意位置插入任一整数。比方说,如果 arr = [1,4,1,2] ,那么你可以在中间添加 3 得到 [1,4,3,1,2] 。你可以在数组最开始或最后面添加整数。
请你返回 最少 操作次数,使得 target 成为 arr 的一个子序列。
一个数组的 子序列 指的是删除原数组的某些元素(可能一个元素都不删除),同时不改变其余元素的相对顺序得到的数组。比方说,[2,7,4] 是 [4,2,3,7,2,1,4] 的子序列(加粗元素),但 [2,4,2] 不是子序列。
示例 1:
输入:target = [5,1,3], arr = [9,4,2,3,4]
输出:2
解释:你可以添加 5 和 1 ,使得 arr 变为 [5,9,4,1,2,3,4] ,target 为 arr 的子序列。
示例 2:
输入:target = [6,4,8,1,3,2], arr = [4,7,6,2,3,8,6,1]
输出:3
提示:
1 <= target.length, arr.length <= 105
1 <= target[i], arr[i] <= 109
target 不包含任何重复元素。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-operations-to-make-a-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路
第一反应,求最长公共子序列,动态规划,然后长度减去最长的公共子序列长度就是答案。然后去复习最长公共子序列,见上一题
但是这样写的话很明显超时了,那么怎么做呢
那么,根据提示target是互不相同的整数,可以将target中的整数存储在一个哈希表中
这样可以快速查找arr中是否有没有这个元素
然后如果这个元素在arr中存在的话,就把这个元素替换成它target中出现的下标,不存在的元素就去掉
因为需要按顺序出现,所以求替换后arr数组的最长上升子序列就可以了
然后再去复习最长上升子序列,因为我第一时间想到的求最长上升子序列的方法是动规,状态转移方程,就是dp[i] = 1 + dp[比i小的所有数字]中的最大值
但是动规的复杂度是n方,所以还是不行
那么继续优化,怎么操作才能使复杂度降下来
看到对数复杂度,首先想到二分,然后想到二分需要的条件,是排序的
那么怎么定义状态才能使其排序呢,看上面的题300. 最长递增子序列
定义d[i]为长度为 i 的递增子序列末尾元素的最小值
class Solution {
public int minOperations(int[] target, int[] arr) {
//首先,最长子序列不行
//那么,根据提示target是互不相同的整数,可以将target中的整数存储在一个哈希表中
//这样可以快速查找arr中是否有没有这个元素
//然后如果这个元素在arr中存在的话,就把这个元素替换成它target中出现的下标,不存在的元素就去掉
//因为需要按顺序出现,所以求替换后arr数组的最长上升子序列就可以了
//最后结果就是m - len
Map<Integer, Integer> map = new HashMap<>();
int m = target.length;
for(int i = 0; i < m; i++){
map.put(target[i], i);
}
List<Integer> list = new ArrayList<>();
int n = arr.length;
for(int i = 0; i < n; i++){
if(map.containsKey(arr[i])){
list.add(map.get(arr[i]));
}
}
//然后问题转换为求最长上升子序列
//d[i]定义为长度为i的递增子序列末尾长度的最小值
int size = list.size();
if(size == 0)
return m;
int[] d = new int[size + 1];
int len = 1;
d[1] = list.get(0);
for(int i = 1; i < size; i++){
int num = list.get(i);
//System.out.println(num);
if(num > d[len]){
d[++len] = num;
}else{
int left = 1;
int right = len;
while(left < right){
int mid = (right - left) / 2 + left;
if(num > d[mid]){
left = mid + 1;
}else{
right = mid;
}
}
d[left] = num;
}
}
return m - len;
}
}
总结
这个题非常值得收藏,第一点,意识到这是一个求最长公共子序列的问题
第二点,看到target中互不相同四个字,想到能将arr数组中的对应元素变成下标
第三点,变成下标以后,意识到因为target中下标肯定是递增的,所以在arr中要找的就变成了了最长递增子序列
第四点,最长递增子序列常规做法动规,复杂度n方,要通过需要写nlogn的方法,然后再想到怎么构造一个可以递增的数组,用二分查找
环环相扣,层层递进,秒啊秒啊
以上是关于LeetCode1143. 最长公共子序列/300. 最长递增子序列//1713. 得到子序列的最少操作次数(好题!!!!!)的主要内容,如果未能解决你的问题,请参考以下文章