最长递增子序列的变种——最长递增子序列的长度与具体序列(允许重复元素)
字数 1525 2025-11-30 02:32:31

最长递增子序列的变种——最长递增子序列的长度与具体序列(允许重复元素)

题目描述:
给定一个整数数组nums,找到最长递增子序列(LIS)的长度,并返回其中一个具体的LIS序列。注意:这个变种问题允许子序列中的元素可以重复出现(即非严格递增),但要求返回的序列必须是原数组中的实际元素按原顺序组成的。

解题过程:

  1. 问题理解:
    我们需要找到数组中最长的递增子序列(允许相等元素),并返回这个序列本身。这与标准LIS问题不同,标准问题通常只要求长度或任意一个序列,而这里需要构造出具体的序列。

  2. 动态规划状态定义:
    定义dp[i]为以第i个元素结尾的最长递增子序列的长度。

  3. 状态转移方程:
    对于每个位置i,我们需要检查所有j < i的元素:
    如果nums[j] ≤ nums[i](允许相等),那么:
    dp[i] = max(dp[i], dp[j] + 1)

  4. 记录路径信息:
    为了能够回溯构造具体的序列,我们需要记录每个位置的前驱位置。定义prev[i]表示在以nums[i]结尾的最长递增子序列中,nums[i]的前一个元素的位置。

  5. 算法步骤:

  • 初始化dp数组全为1(每个元素本身构成长度为1的子序列)
  • 初始化prev数组全为-1(表示没有前驱)
  • 遍历数组,对于每个i:
    • 遍历所有j < i
    • 如果nums[j] ≤ nums[i]且dp[j] + 1 > dp[i]:
      • 更新dp[i] = dp[j] + 1
      • 记录prev[i] = j
  • 找到dp数组中的最大值及其位置
  • 通过prev数组回溯构造具体序列
  1. 示例演示:
    考虑数组nums = [3, 1, 2, 2, 4, 3, 5]

初始化:
dp = [1, 1, 1, 1, 1, 1, 1]
prev = [-1, -1, -1, -1, -1, -1, -1]

处理过程:

  • i=0: 第一个元素,保持初始状态
  • i=1: nums[1]=1 < nums[0]=3,但dp[0]+1=2 > dp[1]=1,更新dp[1]=2, prev[1]=0
  • i=2: 比较j=0,1
    • j=0: nums[0]=3 > nums[2]=2,跳过
    • j=1: nums[1]=1 ≤ nums[2]=2,dp[1]+1=3 > dp[2]=1,更新dp[2]=3, prev[2]=1
  • i=3: 比较j=0,1,2
    • j=2: nums[2]=2 ≤ nums[3]=2,dp[2]+1=4 > dp[3]=1,更新dp[3]=4, prev[3]=2
  • i=4: 比较所有j<4
    • j=3: nums[3]=2 ≤ nums[4]=4,dp[3]+1=5 > dp[4]=1,更新dp[4]=5, prev[4]=3
  • i=5: 比较所有j<5
    • 找到满足条件的最优j=1: dp[1]+1=3
  • i=6: 比较所有j<6
    • j=4: nums[4]=4 ≤ nums[6]=5,dp[4]+1=6 > dp[6]=1,更新dp[6]=6, prev[6]=4

最终dp = [1, 2, 3, 4, 5, 3, 6]
最大值6出现在位置6,通过prev回溯:
6→4→3→2→1→0
得到序列:[3, 1, 2, 2, 4, 5]

  1. 复杂度分析:
  • 时间复杂度:O(n²),需要两层嵌套循环
  • 空间复杂度:O(n),用于存储dp和prev数组
  1. 优化考虑:
    对于只需要长度的情况,可以使用O(nlogn)的贪心+二分查找算法,但为了构造具体序列,O(n²)的解法是必要的。
最长递增子序列的变种——最长递增子序列的长度与具体序列(允许重复元素) 题目描述: 给定一个整数数组nums,找到最长递增子序列(LIS)的长度,并返回其中一个具体的LIS序列。注意:这个变种问题允许子序列中的元素可以重复出现(即非严格递增),但要求返回的序列必须是原数组中的实际元素按原顺序组成的。 解题过程: 问题理解: 我们需要找到数组中最长的递增子序列(允许相等元素),并返回这个序列本身。这与标准LIS问题不同,标准问题通常只要求长度或任意一个序列,而这里需要构造出具体的序列。 动态规划状态定义: 定义dp[ i ]为以第i个元素结尾的最长递增子序列的长度。 状态转移方程: 对于每个位置i,我们需要检查所有j < i的元素: 如果nums[ j] ≤ nums[ i ](允许相等),那么: dp[ i] = max(dp[ i], dp[ j ] + 1) 记录路径信息: 为了能够回溯构造具体的序列,我们需要记录每个位置的前驱位置。定义prev[ i]表示在以nums[ i]结尾的最长递增子序列中,nums[ i ]的前一个元素的位置。 算法步骤: 初始化dp数组全为1(每个元素本身构成长度为1的子序列) 初始化prev数组全为-1(表示没有前驱) 遍历数组,对于每个i: 遍历所有j < i 如果nums[ j] ≤ nums[ i]且dp[ j] + 1 > dp[ i ]: 更新dp[ i] = dp[ j ] + 1 记录prev[ i ] = j 找到dp数组中的最大值及其位置 通过prev数组回溯构造具体序列 示例演示: 考虑数组nums = [ 3, 1, 2, 2, 4, 3, 5 ] 初始化: dp = [ 1, 1, 1, 1, 1, 1, 1 ] prev = [ -1, -1, -1, -1, -1, -1, -1 ] 处理过程: i=0: 第一个元素,保持初始状态 i=1: nums[ 1]=1 < nums[ 0]=3,但dp[ 0]+1=2 > dp[ 1]=1,更新dp[ 1]=2, prev[ 1 ]=0 i=2: 比较j=0,1 j=0: nums[ 0]=3 > nums[ 2 ]=2,跳过 j=1: nums[ 1]=1 ≤ nums[ 2]=2,dp[ 1]+1=3 > dp[ 2]=1,更新dp[ 2]=3, prev[ 2 ]=1 i=3: 比较j=0,1,2 j=2: nums[ 2]=2 ≤ nums[ 3]=2,dp[ 2]+1=4 > dp[ 3]=1,更新dp[ 3]=4, prev[ 3 ]=2 i=4: 比较所有j <4 j=3: nums[ 3]=2 ≤ nums[ 4]=4,dp[ 3]+1=5 > dp[ 4]=1,更新dp[ 4]=5, prev[ 4 ]=3 i=5: 比较所有j <5 找到满足条件的最优j=1: dp[ 1 ]+1=3 i=6: 比较所有j <6 j=4: nums[ 4]=4 ≤ nums[ 6]=5,dp[ 4]+1=6 > dp[ 6]=1,更新dp[ 6]=6, prev[ 6 ]=4 最终dp = [ 1, 2, 3, 4, 5, 3, 6 ] 最大值6出现在位置6,通过prev回溯: 6→4→3→2→1→0 得到序列:[ 3, 1, 2, 2, 4, 5 ] 复杂度分析: 时间复杂度:O(n²),需要两层嵌套循环 空间复杂度:O(n),用于存储dp和prev数组 优化考虑: 对于只需要长度的情况,可以使用O(nlogn)的贪心+二分查找算法,但为了构造具体序列,O(n²)的解法是必要的。