最长连续递增序列
添加链接描述
分析
1、状态只有一个:数组索引
定义dp[i]:表示以 nums[i] 这个数结尾的最长连续
递增子序列的长度
2、选择
对于当前位置nums[i],如果nums[i-1]比我大,那我就接在i-1后面,否则只能自己组队
3、base case:dp[i]=1;
class Solution {
public:
int findLengthOfLCIS(vector<int>& nums) {
int n=nums.size();
vector<int>dp(n,1);
//ans必须初始化为1,因为nums只有1个数的时候不会执行下面的for循环
int ans=1;
for(int i=1;i<n;i++){
if(nums[i-1]<nums[i]){
dp[i]+=dp[i-1];
}
ans=max(ans,dp[i]);
}
return ans;
}
};
最长非连续递增序列
添加链接描述
与上一题不同的是:本题不需要连续。
分析
1、状态只有一个:数组索引
定义dp[i]:表示以 nums[i] 这个数结尾的最长递增子序列的长度
2、选择
上题只有两个选择,
- 自己组队
- 接在前一个元素后面
现在有i个选择
- 自己组队
- 接在前面
i-1
个元素中的任意一个后面
3、base case :dp[i]=1 0<=i<n
c++:
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n=nums.size();
if(n==0){
return 0;
}
vector<int>dp(n,1);
int ans=1;//ans必须初始化为1,因为nums只有1个数的时候不会执行下面的for循环
for(int i=1;i<n;i++){
for(int j=0;j<i;j++){
if(nums[j]<nums[i]){
dp[i]=max(dp[i],dp[j]+1);
}
}
ans=max(ans,dp[i]);
}
return ans;
}
};
java:
class Solution {
public int lengthOfLIS(int[] nums) {
int n=nums.length;
int[]dp=new int[n];
Arrays.fill(dp,1);
int ans=1;
for(int i=1;i<n;i++){
for(int j=0;j<i;j++){
if(nums[j]<nums[i]){
dp[i]=Math.max(dp[i],dp[j]+1);
}
}
ans=Math.max(ans,dp[i]);
}
return ans;
}
}
最长递增子序列的个数
添加链接描述
除了dp数组存储递增序列的长度之外,还需要将使得dp[i]最大的路径的条数cnt
存下来,所以再开一个数组存每个位置的cnt。
dp[i]:以 nums[i] 这个数结尾的最长递增子序列的长度
count[i]:以 nums[i] 这个数结尾的最长递增子序列的条数
c++:
class Solution {
public:
int findNumberOfLIS(vector<int>& nums) {
int n=nums.size();
vector<int>cnt(n,1);
vector<int>dp(n,1);
for(int i=1;i<n;i++){
for(int j=0;j<i;j++){
if(nums[j]<nums[i]){
if(dp[j]+1==dp[i]){
cnt[i]+=cnt[j];
}else if(dp[j]+1>dp[i]){
dp[i]=dp[j]+1;
cnt[i]=cnt[j];
}
}
}
}
int max=0,sum=0;
for(int i=0;i<n;i++){
if(dp[i]>max){
max=dp[i];
sum=cnt[i];
}else if(dp[i]==max){
sum+=cnt[i];
}
}
return sum;
}
};
java:
class Solution {
public int findNumberOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
int[] count = new int[n];
Arrays.fill(dp, 1);
Arrays.fill(count, 1);
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
if (dp[j] + 1 == dp[i]) {
count[i] += count[j];
} else if (dp[j] + 1 > dp[i]) {
count[i] = count[j];
dp[i] = dp[j] + 1;
}
}
}
}
int ans = 0;
int max = -1;
for (int i = 0; i < n; i++) {
if (dp[i] > max) {
ans = count[i];
max = dp[i];
} else if (dp[i] == max) {
ans += count[i];
}
}
return ans;
}
}
最长的斐波那契子序列的长度
添加链接描述
本质还是LIS,对当前下标索引i,遍历其后面所有元素j,然后遍历其前面的所有元素k,如果arr[i]+arr[k]==arr[j],就说明可以接上了。
代码:
class Solution {
public:
int lenLongestFibSubseq(vector<int>& arr) {
int n=arr.size();
vector<vector<int>>dp(n,vector<int>(n));
for(int i=0;i<n-1;i++){
for(int j=i+1;j<n;j++){
dp[i][j]=2;
}
}
int ans=0;
for(int i=1;i<n-1;i++){
for(int j=i+1;j<n;j++){
for(int k=0;k<i;k++){
if(arr[k]+arr[i]==arr[j]){
dp[i][j]=max(dp[i][j],dp[k][i]+1);
}
}
ans=max(ans,dp[i][j]);
}
}
return ans==2?0:ans;
}
};
进一步优化时间复杂度:
c++:
class Solution {
public:
int lenLongestFibSubseq(vector<int>& arr) {
int n=arr.size();
vector<vector<int>>dp(n,vector<int>(n));
for(int i=0;i<n-1;i++){
for(int j=i+1;j<n;j++){
dp[i][j]=2;
}
}
unordered_map<int,int>map;
for(int i=0;i<n;i++){
map[arr[i]]=i;
}
int ans=0;
for(int i=1;i<n-1;i++){
for(int j=i+1;j<n;j++){
int diff=arr[j]-arr[i];
if(map.count(diff)>0){
int k=map[diff];
dp[i][j]=max(dp[i][j],dp[k][i]+1);
}
ans=max(ans,dp[i][j]);
}
}
return ans==2?0:ans;
}
};
java:
class Solution {
public int lenLongestFibSubseq(int[] arr) {
int n=arr.length;
int[][]dp= new int[n][n];
//base case
for (int i = 0; i < n-1; i++) {
for(int j=i+1;j<n;j++){
dp[i][j]=2;
}
}
//map优化
Map<Integer,Integer>map=new HashMap<>();
for (int i = 0; i < arr.length; i++) {
map.put(arr[i],i);
}
int ans=0;
for (int i=0;i<n-1;i++){
for (int j=i+1;j<n;j++){
int diff=arr[j]-arr[i];
if (map.containsKey(diff)){
//在i的前面有一个arr[k](diff)满足arr[k]=arr[j]-arr[i]
int idx=map.get(diff); //取出arr[k]的下标k
dp[i][j]=Math.max(dp[i][j],dp[idx][i]+1);
}
ans=Math.max(ans,dp[i][j]);
}
}
return ans==2?0:ans;
}
}
最长理想子序列
最长理想子序列
和最长非连续递增子序列 本质是一样的
很容易写出下面的基础代码,但是超时
public int longestIdealString(String s, int k) {
int[] dp = new int[s.length()];
int res = 0;
Arrays.fill(dp, 1);
for (int i = 0; i < s.length(); i++) {
for (int j = 0; j < i; j++) {
// 绝对值相差不大于 k
if (Math.abs(s.charAt(i) - s.charAt(j)) <= k) {
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
res = Math.max(res, dp[i]);
}
return res;
}
比如当 k = 3,如果当前字母为 g,那么它只能接在 d e f g h i j 为结尾的子序列后面
按照上面的代码,我们把区间 [0, i-1] 全部遍历了,显然有很多无效分支
如何优化呢?
用 Map 记录以某个字母结尾的最长子序列的长度
这里字母最多 26 个,算上绝对值,最多会遍历 52 次,相比于 10^5 简直少太多了
class Solution {
public:
int longestIdealString(string s, int k) {
unordered_map<char,int>map;
int n=s.size();
int ans=0;
for(int i=0;i<n;i++){
int cur=1;
for(int j=0;j<=k;j++){
char l=(char)(s[i]-j); // 比 cur 少 j 的字母
if(map.count(l)>0){
cur=max(cur,map[l]+1);
}
char r=(char)(s[i]+j); // 比 cur 多 j 的字母
if(map.count(r)>0){
cur=max(cur,map[r]+1);
}
}
map[s[i]]=cur; // 更新以 cur 结尾的最长子序列的长度
ans=max(ans,cur);
}
return ans;
}
};