子序列问题

子序列 是值 一个字串 中 非连续 的 字串 ;字串: 123456789 子序列a: 13579(非连续)

字串b : 12345(连续)

** 子串 问题

** 1.求最大子串 **
class Solution {
public int lengthOfLIS(int[] nums) {

    int get=vector(nums,0,nums.length-1);

    return get;
}
//就最大子串
public int vector(int[] nums,int begin,int end){
    int max=0;//输出最大子串的和
    int curr=0;
    int newBegin=0;

for(int i=0; i <nums.length  ;  i++){
    curr += nums[i];
    if(curr > max)
    {
        max=curr;
        begin =newBegin;
        end=i;
    }
        
    if(curr < 0)
    {
        curr=0;
        newBegin=i+1;
    }
}
return  max;
}

}
// 找 最大子序列的 方法很简单 ,只要前 i 项 的和 还没有 小于 0 那么 子序列 就 一直向后扩展
// 小于 0 就 丢弃之前的 子序列 并且开始 新的 子序列

** 2.最长公共子串 **

**

** 子序列问题 **

首先让们想一下,有一个字串A:1 2 3 4 5 6 7 8 9,我们将它放在一个数组B里面,从B[0]开始放,
有一个数组f[9]用来记录字串从1到9的每个数的递增数;比如说:f[4]里面放的就是到5的最长递增数,f[4]的值就是5;现在我们想想这个数是怎么得到的,是不是依次将f[0]到f[3]的大小比较一遍,选出其中一个最大的数+1赋值给f[4];同时别忘了要满足B[4]>B[3]。现在让我们来实现我所说的这段话的代码。

1.递推法 解决 最长递增子序列问题
(不推荐)
int list(float[] B)
{
int n=B.length;
int[] f=new int[n];
f[0]=1;
for(int i=1; i< n; i++)
{
f[i] =1;
for(int j=0 ; j <i ; j++){
if(B[j] < B[i] && f[j] > f[i]-1 )
//作用是 比大小然后满足递增
f[i]=f[j]+1;
}
}
return f[n-1];
}

一, 最长递增子序列问题的描述

在这里插入图片描述

在这里插入图片描述

dp[i]:[0, i] 的最长上升子序列的长度。

dp[i] : 以 nums【i】 结尾 的 子序列 ,能达到 的 最大长度。

对于 dp【i】 来说,只要 找到 前面 比 nums【i】 小 的 nums【j】 中 最大 的 dp【j】即可。

动态转移方程
dp[i] = max(dp[i], dp[j] + 1); nums[i] > nums[j]
为啥你能得出这个方程?
首先要知道,如果 nums[i] 严格大于在它之前的某个数,那么 nums[i] 就可以接在这个数后面,形成一个更长的上升子序列。
所以我们每次都要在 [0, i) 这个区间内去找。那么定义 j,然后在 [0,i) 区间去找比 num[i] 小的数。区分为两种情况:

当 nums[i] > nums[j] :上升趋势,符合题意,dp[j] + 1;
当 nums[i] <= nums[j] :非上升趋势,不符合题意,跳过。
上述所有 1. 情况 下计算出的 dp[j] + 1 的最大值,为直到 i 的最长上升子序列长度(dp[i])。实现方式为遍历 j 时,每轮执行 dp[i] = max(dp[i], dp[j] + 1)。
初始化:
数组中每个元素都至少可以单独成为子序列,长度最少都为 1,所以初始化全为 1。

class Solution {
public int lengthOfLIS(int[] nums) {

	int len =nums.length;
	int ans=1;
	//dp[i]  : 以 nums【i】 结尾 的 子序列  ,能达到 的 最大长度。
	int dp[] = new int[len];
	
	//边界
	dp[0] =1;

	//复杂度(n的平方)
	for(int i=1 ;i < len  ; i++ )
	{
	  dp[i]=1;
		//找到前面结点里,比  num是【i】 小 的中,dp[] 最长的值
		int maxPre=0;
		for(int j= i-1 ;j>=0 ; j--)
			{
				if ( num[i] > nums[j] {
					//dp【j】最优子结构  例如 : 数组 1 2 3 4  i为3  j为 0 1 2  求出 j中的最大值
					maxPre=Math.max( dp[j], maxPre);
				}
			}
	}
 	//状态转移方程
 	dp[i] = maxPre +1;
 	ans= Math.max(ans,dp[i]);
}
return ans;

}

复杂度分析

时间复杂度:O(n^2)
),其中 nn 为数组 nums 的长度。动态规划的状态数为 n,计算状态 dp[i] 时,需要 O(n) 的时间遍历 dp[0 … i-1] 的所有状态,所以总时间复杂度为 O(n^2)。

空间复杂度:O(n),需要额外使用长度为 nn 的 dpdp 数组。

** 动态规划 + 二分法 + 贪心O(nlogn) **

public class Solution {
public int lengthOfLIS(int[] nums) {
int len = nums.length;
if (len <= 1) {
return len;
}

    // tail 数组的定义:长度为 i + 1 的上升子序列的末尾最小是几
    int[] tail = new int[len];
    // 遍历第 1 个数,直接放在有序数组 tail 的开头
    tail[0] = nums[0];
    // end 表示有序数组 tail 的最后一个已经赋值元素的索引
    int end = 0;

    for (int i = 1; i < len; i++) {
        // 【逻辑 1】比 tail 数组实际有效的末尾的那个元素还大
        if (nums[i] > tail[end]) {
            // 直接添加在那个元素的后面,所以 end 先加 1
            end++;
            tail[end] = nums[i];
        } else {
            // 使用二分查找法,在有序数组 tail 中
            // 找到第 1 个大于等于 nums[i] 的元素,尝试让那个元素更小
            int left = 0;
            int right = end;
            while (left < right) {
                // 选左中位数不是偶然,而是有原因的,原因请见 LeetCode 第 35 题题解
                // int mid = left + (right - left) / 2;
                int mid = left + ((right - left) >>> 1);
                if (tail[mid] < nums[i]) {
                    // 中位数肯定不是要找的数,把它写在分支的前面
                    left = mid + 1;
                } else {
                    right = mid;
                }
            }
            // 走到这里是因为 【逻辑 1】 的反面,因此一定能找到第 1 个大于等于 nums[i] 的元素
            // 因此,无需再单独判断
            tail[left] = nums[i];
        }
        // 调试方法
        // printArray(nums[i], tail);
    }
    // 此时 end 是有序数组 tail 最后一个元素的索引
    // 题目要求返回的是长度,因此 +1 后返回
    end++;
    return end;
}

// 调试方法,以观察是否运行正确
private void printArray(int num, int[] tail) {
    System.out.print("当前数字:" + num);
    System.out.print("\t当前 tail 数组:");
    int len = tail.length;
    for (int i = 0; i < len; i++) {
        if (tail[i] == 0) {
            break;
        }
        System.out.print(tail[i] + ", ");
    }
    System.out.println();
}

public static void main(String[] args) {
    int[] nums = new int[]{3, 5, 6, 2, 5, 4, 19, 5, 6, 7, 12};
    Solution solution = new Solution8();
    int lengthOfLIS = solution8.lengthOfLIS(nums);
    System.out.println("最长上升子序列的长度:" + lengthOfLIS);
}

}

作者:liweiwei1419
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/dong-tai-gui-hua-er-fen-cha-zhao-tan-xin-suan-fa-p/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在这里插入图片描述

  1. 确定dp数组(dp table)以及下标的含义
    这道题目我们要一起维护两个数组。

dp[i]:i之前(包括i)最长递增子序列的长度为dp[i]

count[i]:以nums[i]为结尾的字符串,最长递增子序列的个数为count[i]

  1. 确定递推公式
    在300.最长上升子序列 中,我们给出的状态转移是:

if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);

即:位置i的最长递增子序列长度 等于j从0到i-1各个位置的最长升序子序列 + 1的最大值。

本题就没那么简单了,我们要考虑两个维度,一个是dp[i]的更新,一个是count[i]的更新。

那么如何更新count[i]呢?

以nums[i]为结尾的字符串,最长递增子序列的个数为count[i]。

那么在nums[i] > nums[j]前提下,如果在[0, i-1]的范围内,找到了j,使得dp[j] + 1 > dp[i],说明找到了一个更长的递增子序列。

那么以j为结尾的子串的最长递增子序列的个数,就是最新的以i为结尾的子串的最长递增子序列的个数,即:count[i] = count[j]。

在nums[i] > nums[j]前提下,如果在[0, i-1]的范围内,找到了j,使得dp[j] + 1 == dp[i],说明找到了两个相同长度的递增子序列。

那么以i为结尾的子串的最长递增子序列的个数 就应该加上以j为结尾的子串的最长递增子序列的个数,即:count[i] += count[j];

代码如下:

if (nums[i] > nums[j]) {
if (dp[j] + 1 > dp[i]) {
count[i] = count[j];
} else if (dp[j] + 1 == dp[i]) {
count[i] += count[j];
}
dp[i] = max(dp[i], dp[j] + 1);
}
当然也可以这么写:

if (nums[i] > nums[j]) {
if (dp[j] + 1 > dp[i]) {
dp[i] = dp[j] + 1; // 更新dp[i]放在这里,就不用max了
count[i] = count[j];
} else if (dp[j] + 1 == dp[i]) {
count[i] += count[j];
}
}
这里count[i]记录了以nums[i]为结尾的字符串,最长递增子序列的个数。dp[i]记录了i之前(包括i)最长递增序列的长度。

题目要求最长递增序列的长度的个数,我们应该把最长长度记录下来。

代码如下:

for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
if (dp[j] + 1 > dp[i]) {
count[i] = count[j];
} else if (dp[j] + 1 == dp[i]) {
count[i] += count[j];
}
dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > maxCount) maxCount = dp[i]; // 记录最长长度
}
}
3. dp数组如何初始化
再回顾一下dp[i]和count[i]的定义

count[i]记录了以nums[i]为结尾的字符串,最长递增子序列的个数。

那么最少也就是1个,所以count[i]初始为1。

dp[i]记录了i之前(包括i)最长递增序列的长度。

最小的长度也是1,所以dp[i]初始为1。

代码如下:

vector dp(nums.size(), 1);
vector count(nums.size(), 1);
其实动规的题目中,初始化很有讲究,也很考察对dp数组定义的理解。

  1. 确定遍历顺序
    dp[i] 是由0到i-1各个位置的最长升序子序列 推导而来,那么遍历i一定是从前向后遍历。

j其实就是0到i-1,遍历i的循环里外层,遍历j则在内层,代码如下:

for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
if (dp[j] + 1 > dp[i]) {
count[i] = count[j];
} else if (dp[j] + 1 == dp[i]) {
count[i] += count[j];
}
dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > maxCount) maxCount = dp[i];
}
}
最后还有再遍历一遍dp[i],把最长递增序列长度对应的count[i]累计下来就是结果了。

代码如下:

for (int i = 1; i < nums.size(); i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
if (dp[j] + 1 > dp[i]) {
count[i] = count[j];
} else if (dp[j] + 1 == dp[i]) {
count[i] += count[j];
}
dp[i] = max(dp[i], dp[j] + 1);
}
if (dp[i] > maxCount) maxCount = dp[i];
}
}
int result = 0; // 统计结果
for (int i = 0; i < nums.size(); i++) {
if (maxCount == dp[i]) result += count[i];
}
统计结果,可能有的同学又有点看懵了,那么就再回顾一下dp[i]和count[i]的定义。

  1. 举例推导dp数组
    输入:[1,3,5,4,7]

如果代码写出来了,怎么改都通过不了,那么把dp和count打印出来看看对不对!

时间复杂度O(n^2)
空间复杂度O(n)
还有O(nlogn)的解法,使用树状数组,今天有点忙就先不写了,感兴趣的同学可以自行学习一下,这里有我之前写的树状数组系列博客 (十年前的陈年老文了)

class Solution {
public int findNumberOfLIS(int[] nums) {
if (nums.length <= 1) return nums.length;
int[] dp = new int[nums.length];
for(int i = 0; i < dp.length; i++) dp[i] = 1;
int[] count = new int[nums.length];
for(int i = 0; i < count.length; i++) count[i] = 1;

    int maxCount = 0;
    for (int i = 1; i < nums.length; i++) {
        for (int j = 0; j < i; j++) {
            if (nums[i] > nums[j]) {
                if (dp[j] + 1 > dp[i]) {
                    dp[i] = dp[j] + 1;
                    count[i] = count[j];
                } else if (dp[j] + 1 == dp[i]) {
                    count[i] += count[j];
                }
            }
            if (dp[i] > maxCount) maxCount = dp[i];
        }
    }
    int result = 0;
    for (int i = 0; i < nums.length; i++) {
        if (maxCount == dp[i]) result += count[i];
    }
    return result;
}

}

作者:carlsun-2
链接:https://leetcode-cn.com/problems/number-of-longest-increasing-subsequence/solution/dai-ma-sui-xiang-lu-dai-ni-xue-tou-dp673-9txt/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值