动态规划之最长上升子序列(贪心+二分法)

本文介绍了一种结合贪心算法与二分查找的高效算法,用于求解给定数组中最长子序列的长度。通过维护一个特殊数组d,确保d[i]始终保存已遍历过的最大值,若遇更小值,则用二分法在d数组中找首个更大值并替换,最终得到排好序的最长子序列长度。

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

      动态规划算法和动态规划算法的区别:当一个问题的最优解包含其子问题的最优解时,称此问题具有最优子结构性质。运用贪心策略在每一次转化时都取得了最优解。问题的最优子结构性质是该问题可用贪心算法或动态规划算法求解的关键特征。贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是。贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。动态规划主要运用于二维或三维问题,而贪心一般是一维问题

      要求一样,就是在给定的数组里面找出最长子序列的长度(一般来说这个长度个数的可能子序列不唯一,但长度是唯一的),这篇文章主要讲解贪心+二分法来解决问题,如果刚接触,我建议先看入门篇-动态规划之最长子序列,里面有较详细的解释,这里主要是学习一个新的思想:我们现在利用一个数组来存放最长子序列 d[i](i 为当前长度),这个数组有这样的规定:不管怎么样,d[i]总的保存已遍历过最大的数,如果遇到比他小的,则在的d[i] 前面的数中,找到第一个比他大的值,然后替换(这里必须明白:替换并不会改变d数组的长度,所以在最后得到的结果是最长子序列长度的排好序的最小序列 ),如果比d[i]大直接接在后面即可,具体算法请看代码

#include<iostream>
using namespace std;
// 使用二分法的前提是 数组是已经排好序的(刚好在这里我们的d数组就是递增数组)
// 查找返回d数组中第一个比x大的值的下标
int er_method(int a[],int len,int x)
{
    int mid,l=1;
    while(l<=len)
    {
        mid = (l+len)/2;
        if(a[mid]<=x)
            l = mid+1;
        else
            len = mid-1;
    }
    return l;// 此时mid等于l
}
int main()
{
    int n;
    while(cin>>n)
    {
        //待测的数组-------    a
        //存放最长子序列---     d
        //记录最长子序列的长度-- len
        int a[101],d[101],len=1;
        for(int i=1;i<=n;i++)
        {
            cin>>a[i];
        }
        d[1]=a[1];   // 第一个待测数字就d的第一个
        if(n<2)  // 如果待测数字只有一个 ,那就这个数字就是最长子序列
        {
            cout<<1<<endl;
            continue;
        }
        for(int i=2;i<=n;i++)
        {
            if(a[i]>d[len]) // 如果第 i个数大于d数组的最大的数,直接接在d数组的后面
                d[++len] = a[i];
            else    // 比d数组的最大数值小的的话,那就在d数组中找到一个比它大的数,替换掉
                d[er_method(a,len,a[i]] = a[i]; // 这一步不改变d 数组的长度
        }
        cout<<len<<endl;
    }
    return 0;
}

算法已经结束。这里必须再次强调,这种方法只能找出长度,但不能求解具体值是什么,它求出来的结果是 最长子序列长度的排好序的最小序列,请注意两个定语

<think>嗯,用户让我介绍一下如何设计一个动态规划算法来解决长递增子序列(LIS)问题。首先,我需要确认自己对这个问题是否足够了解。长递增子序列是一个经典的动态规划题目,应该用一维或二维的DP数组来记录状态。不过,用户可能已经知道基本的解法,所以可能需要详细解释每个步骤,或者有没有更优化的方法? 首先,我应该回忆一下LIS问题的定义。长递增子序列指的是在一个给定的序列中找到一个子序列,使得这个子序列的元素是严格递增的,并且这个子序列的长度尽可能长。例如,序列[10,9,2,5,3,7,101,18]的长递增子序列是[2,3,7,101],长度为4。不过,可能存在多个不同的长递增子序列,但只需要返回长度即可。 接下来,动态规划解法的思路应该是怎样的呢?传统的方法是使用一个数组dp,其中dp[i]表示以第i个元素结尾的长递增子序列的长度。然后,对于每个元素i,需要遍历前面所有的元素j(j < i),如果nums[i] > nums[j],那么dp[i]就可以是dp[j] + 1,否则保持原值。这样,后整个dp数组中的大值就是所求的结果。这种方法的时间复杂度是O(),空间复杂度是O(n)。 不过,用户可能想知道是否有更高效的方法,比如O(n log n)的解法。这时候可能涉及到贪心算法二分查找的结合。例如,维护一个数组tail,其中tail[i]表示长度为i+1的递增子序列小末尾元素。这样,遍历每个元素时,可以用二分查找找到该元素在tail数组中的位置,然后更新tail数组。这种方法可以将时间复杂度降低到O(n log n)。 用户的问题明确要求使用动态规划,所以可能需要重点讲解传统的DP方法,但也可以提到优化方法作为补充。或者,用户可能对两种方法都感兴趣,但根据问题描述,可能只需要动态规划的部分。 另外,我需要考虑用户可能的背景。如果用户是学生或刚开始学习算法,可能需要详细的步骤解释示例。如果用户已经有一定的基础,可能希望更深入的优化分析。所以,在回答时应该结构清晰,分步骤说明,并给出具体的例子来帮助理解。 在写代码示例时,应该用Python或其他常见语言,确保语法正确,并加上注释。同时,要解释状态转移方程初始化的过程,以及如何从DP数组中获取结果。例如,dp数组的每个元素dp[i]初始值为1,因为每个元素本身就是一个长度为1的子序列。然后,对于每个i,遍历j从0到i-1,比较nums[i]nums[j]的大小,从而更新dp[i]。 另外,要考虑到边界情况,例如输入数组为空,或者所有元素都是递减的情况。这时候,长递增子序列的长度应该是1,因为每个元素本身都可以视为一个子序列后,总结动态规划解法的优缺点,比如时间复杂度较高,但思路直观,容易理解。而优化后的贪心+二分法虽然效率更高,但可能稍难理解。根据用户的需求,可能需要强调不同方法的应用场景。 总之,我需要组织一个结构清晰的回答,分步骤解释动态规划的设计思路,给出状态转移方程,代码示例,并分析时间空间复杂度。同时,可以简要提及更优的解法,供用户进一步学习参考。</think>--- ### 长递增子序列(LIS)问题的动态规划解法 #### 1. **问题定义** 给定一个整数序列 $nums$,找到其中**严格递增**的子序列大长度。 例如: 输入:$nums = [10,9,2,5,3,7,101,18]$ 输出:4(长子序列如 $[2,5,7,101]$) --- #### 2. **动态规划设计思路** - **定义状态**: 设 $dp[i]$ 表示以 $nums[i]$ 结尾的长递增子序列的长度。 - **状态转移**: 对于每个 $i$,遍历其之前的所有位置 $j$($j < i$): - 若 $nums[i] > nums[j]$,则 $dp[i] = \max(dp[i], dp[j] + 1)$ - 否则保持 $dp[i]$ 不变 - **初始化**: 每个元素自身至少构成长度为1的子序列,即 $dp[i] = 1$(初始值)。 - **结果**: 终结果为所有 $dp[i]$ 中的大值。 --- #### 3. **状态转移方程** $$ dp[i] = \begin{cases} 1 & \text{初始值} \\ \max\{dp[j] + 1\} & \text{对所有 } j < i \text{ 且 } nums[i] > nums[j] \end{cases} $$ --- #### 4. **代码实现(Python)** ```python def length_of_LIS(nums): if not nums: return 0 n = len(nums) dp = [1] * n # 初始化:每个元素自身长度为1 for i in range(n): for j in range(i): # 遍历i之前的所有j if nums[i] > nums[j]: dp[i] = max(dp[i], dp[j] + 1) return max(dp) # 取大值 # 示例 nums = [10,9,2,5,3,7,101,18] print(length_of_LIS(nums)) # 输出:4 ``` --- #### 5. **复杂度分析** - **时间复杂度**:$O(n^2)$ 双重循环遍历所有元素对 $(i, j)$。 - **空间复杂度**:$O(n)$ 仅需维护长度为 $n$ 的 $dp$ 数组。 --- #### 6. **优化方法(附加)** - **贪心 + 二分查找**: 维护一个数组 $tail$,记录长度为 $k$ 的递增子序列小末尾值。 时间复杂度优化至 $O(n \log n)$。 示例代码: ```python def length_of_LIS_optimized(nums): tail = [] for num in nums: idx = bisect.bisect_left(tail, num) # 二分查找插入位置 if idx == len(tail): tail.append(num) else: tail[idx] = num return len(tail) ``` --- #### 7. **关键对比** | 方法 | 时间复杂度 | 核心思想 | 适用场景 | |----------------|------------|------------------------|----------------| | 基础动态规划 | $O(n^2)$ | 暴力枚举所有可能子序列 | 小规模数据 | | 贪心+二分查找 | $O(n \log n)$ | 维护小末尾值 | 大规模数据优化 | --- 通过动态规划可以直观解决LIS问题,而贪心+二分法则提供了更高效的优化思路。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

memory_cood

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值