DP ——线性dp之LIS

这篇博客介绍了如何使用线性动态规划(DP)解决最长递增子序列(LIS)问题,包括两种算法:一种是平方时间复杂度的直接DP方法,另一种是更高效的nlogn解决方案,利用隐式DP和单调队列优化。

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

POJ 1631

LIS解析:

LIS 是 longest Increasing Sequence
是一道典型的线性DP问题,有两种算法可以求
一种是n^2 的算法,
设a: 1-n 的序列
for i 1->n
for j 1->i dp[i] = 1
d[i] = max{dp[i],dp[j]+1(a[j] < a[i])}

另一种nlogn即可(隐形DP)
设a: 1-n 的序列
设b 记录长度为i子序列的最小尾数的序列//次序列单调
for i 1 -> n
k++ 或 binary;

AC code:

#include <iostream>
#include <string>
#include <cstdio>
#include <algorithm>
#include <cmath>
#include <cstring>
using namespace std;
const int maxn = 4e4 + 100;

int a[maxn];
int b[maxn];


int binary(int st, int ed,int x)
{
    int mid;
    while(ed - st > 1)
    {
        mid = st + (ed-st)/2;
        if(b[mid] == x)  return mid;
        if(b[mid] < x)   st = mid;
        else             ed = mid;
    }
    return ed;
}
int LIS(int p)
{
   int k = 1;
   memset(b,0,sizeof(0));
   b[1] = a[0];
   for(int i = 1; i < p; i++)
   {
      int x = a[i];
      if(x > b[k])  b[++k] = x;
      else if(x < b[1]) b[1] = x;
      else
      {
      int j = binary(1,k,x);
      b[j] = min(b[j],x);
      }
   }
   return k;
}

int main()
{
    int n,p;
    scanf("%d",&n);
    while(n--)
    {
        scanf("%d",&p);
        for(int i = 0; i < p; i++)
        {
            scanf("%d",&a[i]);
        }
        int ans = LIS(p);
        printf("%d\n",ans);
    }
    return 0;
}



<think>嗯,用户想要了解Java中动态规划的线性DP,我需要详细讲解。首先,得确定用户对动态规划的基本概念是否熟悉。可能他们知道一些,但作为讲解,还是应该从基础开始,再逐步深入。 线性DP,应该指的是问题模型是线性的,比如一维数组或者序列上的问题。常见的例子有斐波那契数列、最长递增子序列(LIS)、最长公共子序列(LCS)这些。这些例子比较典型,用户可能听说过,可以用来举例说明。 接下来,我需要明确线性DP的核心步骤:定义状态、状态转移方程、初始化和计算顺序。这部分是关键,得详细解释每个步骤的作用。比如状态定义,要说明为什么用一维数组,或者二维但有一个维度是线性变化的。 然后,可能需要举几个具体的例子。比如斐波那契数列作为入门例子,虽然简单,但能展示状态转移。然后是LIS问题,这里可以用O(n²)的解法,说明状态转移的过程。可能还要提到更优化的O(n log n)解法,但用户可能主要关注线性DP的基础,所以先讲基本的。 再考虑LCS问题,虽然是二维DP,但可以归为线性结构,因为两个序列都是线性遍历的。需要解释状态的定义和转移方程如何推导出来。 在Java代码实现部分,要给出具体的例子,比如斐波那契的迭代版本,避免递归带来的重复计算。LIS问题则需要双重循环,展示如何填充dp数组。代码要简洁,注释清晰,方便用户理解。 可能还需要总结线性DP的特点,比如状态定义通常与问题规模相关,状态转移依赖于之前的状态等。此外,注意事项部分应该包括空间优化,比如滚动数组,或者状态压缩的技巧,这对实际应用有帮助。 用户可能有疑问,如何识别问题是否适用线性DP,这里需要给出一些判断方法,比如问题是否涉及序列或数组,子问题是否可以线性分解等。同时,错误排查方面,比如初始化是否正确,边界条件是否处理妥当,这些也是常见问题。 最后,确保整个结构清晰,分点讲解,由浅入深,让用户能够逐步理解和应用。需要检查是否覆盖了线性DP的主要知识点,并且示例代码正确无误,符合Java语法规范。</think>好的!我将分步骤为您详细讲解Java中动态规划的线性DP实现,结合核心原理、经典模型和代码示例。 --- ### **一、线性DP核心理解** **定义**:在一维或伪一维(如双序列)结构上,按线性顺序推导状态的动态规划。 **特点**:状态转移方程仅依赖前序有限个状态,时间复杂度通常为 $O(n)$ 或 $O(n^2)$。 --- ### **二、实现四步骤** #### **1. 定义状态** - 使用一维数组 `dp[i]`,表示以第 `i` 个元素结尾的子问题最优解 - 例:最长递增子序列中,`dp[i]` 表示以 `nums[i]` 结尾的LIS长度 #### **2. 状态转移方程** - 关键:找到 `dp[i]` 与 `dp[j] (j < i)` 的关系 - 例(LIS): $$dp[i] = \max_{0 \le j < i}(dp[j] + 1) \quad \text{当} \ nums[j] < nums[i]$$ #### **3. 初始化** - 初始状态通常为边界值,如 `dp[0] = 1` #### **4. 计算顺序** - 按线性顺序(如从左到右)填充 `dp` 数组 --- ### **三、经典模型与Java代码** #### **模型1:斐波那契数列** ```java int fib(int n) { if (n <= 1) return n; int[] dp = new int[n+1]; dp[0] = 0; dp[1] = 1; for (int i = 2; i <= n; i++) { dp[i] = dp[i-1] + dp[i-2]; // 状态转移 } return dp[n]; } ``` #### **模型2:最长递增子序列(LIS)** ```java int lengthOfLIS(int[] nums) { int[] dp = new int[nums.length]; Arrays.fill(dp, 1); int maxLen = 1; for (int i = 1; i < nums.length; i++) { for (int j = 0; j < i; j++) { if (nums[j] < nums[i]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } maxLen = Math.max(maxLen, dp[i]); } return maxLen; } ``` #### **模型3:最长公共子序列(LCS)** ```java int longestCommonSubsequence(String text1, String text2) { int m = text1.length(), n = text2.length(); int[][] dp = new int[m+1][n+1]; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (text1.charAt(i-1) == text2.charAt(j-1)) { dp[i][j] = dp[i-1][j-1] + 1; } else { dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]); } } } return dp[m][n]; } ``` --- ### **四、优化技巧** 1. **空间压缩**:若状态仅依赖前几个值,可用滚动数组 (例:斐波那契数列只需保存前两个状态) 2. **贪心+二分优化LIS**:时间复杂度降至 $O(n \log n)$ --- ### **五、常见问题排查** 1. **初始化错误**:检查 `dp[0]` 是否符合场景 2. **边界越界**:注意数组索引与字符串位置的偏移 3. **转移方程遗漏条件**:如LCS中字符不相等的情况 --- ### **六、适用场景判断** 当问题满足以下特征时,优先考虑线性DP: 1. 输入为序列或可线性展开的结构 2. 问题的最优解可分解为子问题的最优解 3. 子问题之间存在明显的递推关系 通过以上步骤拆解和代码实践,可以系统掌握线性DP的核心设计方法!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值