线段树求LIS并统计最长子序列个数

博客介绍了如何利用线段树解决求解数列中最长先增后减子序列长度及个数的问题。首先,通过线段树计算每个位置的最长单增和单减子序列长度,接着遍历数据统计最长先增后减子序列的个数,优化后的算法将时间复杂度降低到O(n)。

以下面的题目为例(题目和代码在最后面),给定一个数列(长度最大为10000),求出最长的先增后减子序列长度及个数。做法是先求出以每一个位置结尾的最长单增子序列长度以及以该位置开头的最长单减子序列长度,然后遍历所有位置找出最大先增后减子序列长度。

以最长单增序列(LIS)为例,由于不仅需要整个序列LIS的长度,还要保存以每个位置为结尾位置的LIS长度。记以a[i]结尾的LIS长度为dp[i],则

dp[i] = max{dp[j] | a[j] < a[i]} + 1

这就是一个RMQ问题,涉及单点更新和单点查询,所有选择用线段树求解。

后面统计子序列个数时,记以a[i]结尾的最长子序列个数为num[i],则

num[i] = sum{num[j] | j < i && dp[j] + 1 == dp[i]}

最朴素的做法是遍历1到i - 1,但这样会超时,由于dp[i] <= 10000,故提前保存每个dp[i]的位置,用coll[i]保存所有dp[j] = i的j,那么对于num[i],只需遍历集合coll[dp[j] - 1],统计子序列个数的时间复杂度可以降为O(n)。

下面是原题和代码:


### 长递增子序列LIS)算法 长递增子序列(Longest Increasing Subsequence,LIS)问题旨在从一个给定序列中找到一个长的递增子序列。该子序列中的元素不必连续,但必须保持递增顺序。 #### 实现方法 1. **动态规划法(时间复杂度 O(n²))** 动态规划是一种直观的方法,其核心思想是使用一个数组 `dp`,其中 `dp[i]` 表示以第 `i` 个元素结尾的长递增子序列的长度。通过遍历数组,对于每个元素,检查其前面所有比它小的元素,更新其对应的 `dp[i]` 值。 ```csharp public static int LengthOfLIS(int[] nums) { if (nums.Length == 0) return 0; int[] dp = new int[nums.Length]; dp[0] = 1; int maxans = 1; for (int i = 1; i < dp.Length; i++) { int maxval = 0; for (int j = 0; j < i; j++) { if (nums[i] > nums[j]) { maxval = Math.Max(maxval, dp[j]); } } dp[i] = maxval + 1; maxans = Math.Max(maxans, dp[i]); } return maxans; } ``` 该方法的时间复杂度为 O(n²),适用于小规模数据集[^2]。 2. **优化的动态规划法(时间复杂度 O(n log n))** 该方法通过维护一个数组 `B`,用于记录长度为 `len` 的递增子序列的末尾元素。当遇到比当前序列末尾元素大的值时,直接扩展序列;否则,使用二分查找更新 `B` 中的合适位置。 ```python def length_of_lis(nums): if not nums: return 0 tails = [0] * len(nums) length = 0 for num in nums: low, high = 0, length while low < high: mid = (low + high) // 2 if tails[mid] < num: low = mid + 1 else: high = mid tails[low] = num if low == length: length += 1 return length ``` 该方法的时间复杂度为 O(n log n),适用于大规模数据集[^5]。 #### 时间复杂度分析 - **O(n²) 方法** 动态规划法需要两层嵌套循环,外层遍历每个元素,内层遍历其前面的所有元素,因此时间复杂度为 O(n²)[^2]。 - **O(n log n) 方法** 优化的动态规划法通过二分查找减少了内层循环的次数,使得查找操作的时间复杂度降低到 O(log n),整体时间复杂度为 O(n log n)[^5]。 #### 适用场景 - **O(n²) 方法**:适用于小规模数据集或对时间复杂度要不高的场景。 - **O(n log n) 方法**:适用于大规模数据集或对时间效率有较高要的场景。 ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值