最长递减子序列 【微软面试100题 第四十七题】

本文介绍了求解最长递减子序列的问题,并提供了两种算法实现:一种是基于动态规划的时间复杂度为O(N^2)的方法;另一种是利用二分搜索进行优化的时间复杂度为O(N*logN)的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目要求:

  求一个数组的最长递减子序列;

  比如{9,4,3,2,5,4,3,2}的最长递减子序列为{9,5,4,3,2}。

代码实现:

代码1:动态规划,时间复杂度O(N^2).

#include <iostream>

using namespace std;
const int N = 8;
int Lis(int a[],int n);
int main(void)
{
    int a[] = {1,-1,2,1,4,-5,6,-7};
    cout << Lis(a,N) << endl;
    return 0;
}

int Lis(int a[],int n)
{
    int *B = new int[n];
    int i,j,max;
    for(i = 0;i<n;i++)
    {
        B[i] = 1;
        for(j = 0;j<i;j++)
        {
            if(a[i]<a[j] && B[j]+1>B[i])
                B[i] = B[j]+1;
        }
    }
    max = B[0];
    for(i = 1;i<n;i++)
    {
        if(max<B[i])
            max = B[i];
    }
    return max;
}

 

代码2:使用二分搜索提速,时间复杂度O(N*logN).

#include <iostream>

using namespace std;
const int N = 8;
int Lis(int a[],int n);
int main(void)
{
    int a[] = {1,-1,2,1,4,-5,6,-7};
    cout << Lis(a,N) << endl;
    return 0;
}

int Lis(int a[],int n)
{
    int *B = new int[n];
    int i,left,right,mid,len = 1;
    B[0] = INT_MAX;
    B[1] = a[0];
    for(i = 1;i<n;i++)
    {
        left = 0;
        right = len;
        while(left<=right)
        {
            mid = (left+right)/2;
            if(B[mid]>a[i])
                left = mid+1;
            else
                right = mid-1;
        }
        B[left] = a[i];
        if(left>len)
            len++;
    }
    return len;
}

 

转载于:https://www.cnblogs.com/tractorman/p/4082028.html

<think>好的,用户想了解最长递减子序列算法实现和解决方法。首先,我需要回忆相关知识。最长递减子序列(Longest Decreasing Subsequence,LDS)最长递增子序列(LIS)类似,只是元素的顺序是递减的。所以解决方法应该可以借鉴LIS的算法,稍作调整即可。 用户提供的引用中有提到,引用[2]直接讨论了最长递减子序列的问,并提到了简单解决方案和优化方案。而引用[3]则详细讲解了LIS的O(n log n)算法,这可能对优化LDS有帮助。 首先,应该先介绍基本解法,即动态规划的O(n²)方法。动态规划的思路是,对于每个元素,检查之前所有比它大的元素,找到最长子序列长度加一。这一步需要双重循环,时间复杂度较高。 接下来是优化方法。对于LIS,通常使用贪心加二分查找来优化到O(n log n)。同样,LDS可以通过将原数组元素取负数,转化为LIS问,或者直接调整比较逻辑。例如,维护一个数组d,其中d[i]表示长度为i的递减子序列的最小末尾元素(这里可能需要调整,因为递减的话,可能需要最大的末尾元素)。不过根据引用[3]中的方法,维护末尾元素的最小值,但方向相反的话,可能需要维护最大值,或者调整比较条件。 这里可能需要更仔细的推导。例如,在LIS的O(n log n)算法中,数组d维护的是递增序列的最小末尾,以便后续元素可以更容易接在后面。对于递减的情况,可能需要维护一个递减序列,此时每个新元素需要找到第一个比它小的元素的位置,替换或追加。或者,可以将原数组反转,求LIS,再反转回来,或者将每个元素取负数,再求LIS,这样就能利用现有的LIS算法来得到LDS的结果。这种方法可能更简便。 用户的问是关于算法实现,所以需要给出具体的步骤和代码示例。例如,先介绍动态规划的方法,然后介绍优化的O(n log n)方法,并给出代码实现。 在代码部分,用户可能需要不同语言的示例,比如Python或Java。根据引用中的例子,用户提到Java中的实现,但用户现在的问可能希望看到Python的实现,或者通用的伪代码。 同时,需要考虑如何处理元素相等的情况。例如,最长不下降子序列允许相等,而严格递减可能需要不同处理。用户的问中提到的是最长递减子序列,可能需要明确是否允许相等。根据引用[3],最长不下降子序列允许相等,但这里的问最长递减,可能指的是严格递减还是不降。需要明确这一点,或者在算法中说明如何处理。 另外,引用[2]提到了如何打印最长递减子序列,这也是用户可能关心的部分。因此,在回答中可以包括两种方法:求长度和打印序列本身。 总结回答的结构应该是: 1. 问定义 2. 动态规划解法(O(n²)) 3. 优化解法(O(n log n)) 4. 代码示例(可能包括两种方法的实现) 5. 相关扩展问 需要确保LaTeX格式正确,如动态规划的状态转移方程使用$$...$$,行内变量用$...$。例如,状态转移方程可以写成: $$ dp[i] = \max(dp[j] + 1) \quad \text{其中} \quad j < i \quad \text{且} \quad a[j] > a[i] $$ 此外,引用中的内容需要正确标注。例如,在介绍动态规划时,可以引用[2]提到的简单解决方案,优化方法可以参考[3]中的nlogn算法思路,但应用到递减的情况。 在代码部分,Python的实现可能更简洁。例如,对于动态规划方法,可以初始化一个dp数组,每个元素初始为1,然后双重循环更新。对于优化方法,维护一个数组d,遍历每个元素,使用bisect模块来找到插入位置,但需要注意递减顺序可能需要调整bisect的比较逻辑。 可能遇到的错误点:在优化算法中,由于是递减序列,需要将bisect的查找方向调整,或者对数组取负数,然后使用bisect的升序处理。例如,将每个元素取负数,然后使用bisect_left找LIS的位置,这样可以间接得到LDS的长度。 最后,生成相关问需要考虑用户可能的后续疑问,如时间复杂度的比较、如何打印序列、处理有重复元素的情况等。</think>### 最长递减子序列算法实现解决方案 #### 1. 问定义 最长递减子序列(Longest Decreasing Subsequence, LDS)指在给定序列中找到一个子序列,使得元素严格递减(或不严格递减),且长度最长。例如序列$[9,4,3,5,2]$的最长递减子序列为$[9,5,2]$(严格递减)或$[9,4,3,2]$(非严格递减)。 #### 2. 动态规划解法($O(n^2)$) **核心思路**: 定义$dp[i]$为以第$i$个元素结尾的最长递减子序列长度。状态转移方程为: $$ dp[i] = \max(dp[j] + 1) \quad \text{其中} \quad j < i \quad \text{且} \quad a[j] > a[i] $$ 初始时所有$dp[i]=1$,最终结果为$\max(dp)$[^2]。 **Python实现**: ```python def lds_dp(arr): n = len(arr) dp = [1] * n for i in range(n): for j in range(i): if arr[j] > arr[i]: # 严格递减条件 dp[i] = max(dp[i], dp[j] + 1) return max(dp) ``` #### 3. 优化解法($O(n \log n)$) **核心思路**: 利用贪心+二分查找维护一个递减序列的“潜在最优结构”。定义数组$d$,其中$d[i]$表示长度为$i+1$的递减子序列的最小末尾值(允许非严格递减时)或最大末尾值(严格递减时)。遍历元素时,通过二分查找找到插入位置并更新$d$[^3]。 **Python实现**: ```python import bisect def lds_nlogn(arr, strict=True): d = [] for num in arr: # 根据条件调整比较逻辑 if strict: idx = bisect.bisect_left(d, -num) # 转换为递增问(取负数) else: idx = bisect.bisect_right(d, num, key=lambda x: -x) # 非严格递减 if idx == len(d): d.append(-num if strict else num) else: d[idx] = -num if strict else num return len(d) ``` #### 4. 算法对比 | 方法 | 时间复杂度 | 空间复杂度 | 适用场景 | |---------------|------------|------------|------------------------| | 动态规划 | $O(n^2)$ | $O(n)$ | 小规模数据或需输出路径 | | 贪心+二分查找 | $O(n \log n)$ | $O(n)$ | 大规模数据仅需长度 | #### 5. 扩展:如何输出具体序列? 若需输出最长递减子序列的具体元素,需额外记录路径信息。以动态规划为例: ```python def lds_with_path(arr): n = len(arr) dp = [1] * n prev = [-1] * n # 记录前驱索引 max_len, end_idx = 1, 0 for i in range(n): for j in range(i): if arr[j] > arr[i] and dp[j] + 1 > dp[i]: dp[i] = dp[j] + 1 prev[i] = j if dp[i] > max_len: max_len = dp[i] end_idx = i # 回溯路径 path = [] while end_idx != -1: path.append(arr[end_idx]) end_idx = prev[end_idx] return path[::-1] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值