这是一个关于如何使用动态规划(Dynamic Programming, DP)求解"最长递增子序列"问题的详细博客。内容包括了题目分析、思路解析、案例解释以及代码实现,帮助你深入理解该问题的解法。
📌 题目分析
题目 :给定一个整数数组 nums
,找到其中最长的严格递增子序列的长度。
- 输入 :一个整数数组
nums
。 - 输出:最长递增子序列的长度(严格递增,子序列中的元素需保持相对顺序)。
示例:
- 输入:
[10, 9, 2, 5, 3, 7, 101, 18]
- 输出:
4
- 解释: 最长递增子序列为
[2, 3, 7, 101]
,长度为4
。
🧠 思路分析
这个问题适合用 动态规划 来解决。动态规划的核心思想是将大问题分解成小问题,并将每个小问题的解存储下来,以避免重复计算。我们可以用一个数组 dp
来记录每个位置的最长递增子序列的长度。算法过程如下:
- 初始化
dp
数组,每个元素的初始值为1
,因为每个元素本身就是一个长度为1
的递增子序列。 - 从数组的第一个元素开始,遍历每个元素
i
,对于每个i
,再遍历i
之前的所有元素j
。 - 若
nums[j] < nums[i]
,则nums[i]
可以接在nums[j]
之后构成递增序列。此时更新dp[i]
的值:dp[i] = max(dp[i], dp[j] + 1)
。 - 遍历完所有元素后,
dp
数组中最大的值即为最长递增子序列的长度。
动态规划状态转移方程
设 dp[i]
表示以 nums[i]
结尾的最长递增子序列长度,则:
d p [ i ] = max ( d p [ i ] , d p [ j ] + 1 ) for j < i and n u m s [ j ] < n u m s [ i ] dp[i] = \max(dp[i], dp[j] + 1) \quad \text{for } j < i \text{ and } nums[j] < nums[i] dp[i]=max(dp[i],dp[j]+1)for j<i and nums[j]<nums[i]
最终结果为 dp
数组的最大值 \max(dp)
。
时间复杂度
- 时间复杂度:O(n²),其中 n 是数组的长度。原因是我们对每个元素进行一轮遍历。
- 空间复杂度 :O(n),因为我们用了一个长度为 n 的
dp
数组。
📊 案例解释
案例 1
输入: [10, 9, 2, 5, 3, 7, 101, 18]
过程
- 初始化:
dp = [1, 1, 1, 1, 1, 1, 1, 1]
,每个元素的初始长度为1
。 - 从
nums[0]
到nums[7]
逐步更新dp
值:
- i = 3 :
dp[3] = max(dp[3], dp[2] + 1) = 2
(即[2, 5]
)。 - i = 4 :
dp[4] = 2
(即[2, 3]
)。 - i = 5 :
dp[5] = 3
(即[2, 3, 7]
)。 - i = 6 :
dp[6] = 4
(即[2, 3, 7, 101]
)。 - i = 7 :
dp[7] = 4
(即[2, 3, 7, 18]
)。
最终 dp
数组为 [1, 1, 1, 2, 2, 3, 4, 4]
,最大值为 4
。
案例 2
输入: [0, 1, 0, 3, 2, 3]
- 过程 :
dp[1] = 2
(即[0, 1]
)dp[3] = 3
(即[0, 1, 3]
)dp[4] = 3
(即[0, 1, 2]
)dp[5] = 4
(即[0, 1, 2, 3]
)
- 最长递增子序列的长度为
4
。
🔧 代码实现
python代码
python
import os
import sys
# 请在此输入您的代码
def print_arr(arr):
for i in range(len(arr)):
if i == 0:
print(arr[i],end='')
else:
print(' ',arr[i],end='')
def init_arr(size):
return [1] * size
def solve(dp,arr):
for i in range(0,len(arr)):
for j in range(0,i):
if arr[i] > arr[j]:
dp[i] = max(dp[i],dp[j]+1)
max_len = max(dp)
max_len_index = dp.index(max_len)
res = [arr[max_len_index]]
max_len -= 1
idx = max_len_index - 1
while max_len > 0:
if dp[idx] != dp[idx + 1]:
res.append(arr[idx])
max_len -= 1
idx -= 1
return list(reversed(res))
n = int(input())
arr = list(map(int,input().split()))
dp = init_arr(len(arr))
res = solve(dp,arr)
print_arr(res)
C++代码
c
#include<bits/stdc++.h>
using namespace std;
const int max_len = 1024;
int dp[max_len];
void print_arr(int *, int);
void solve(int *, int);
int main(){
int n;
cin>>n;
int a[n];
for(int i=0;i<n;i++){
cin>>a[i];
}
fill(dp,dp+max_len,1);
solve(a,n);
return 0;
}
void solve(int* a, int arr_len){
for(int i=0;i<arr_len;i++){
for(int j=0;j<i;j++){
if (*(a+i) > *(a+j)){
dp[i] = max(dp[i],dp[j]+1);
}
}
}
print_arr(dp,arr_len);
int* max_value_ptr = max_element(dp,dp+arr_len);
int max_value = *max_value_ptr;
int r_size = max_value;
int max_value_idx = max_value_ptr - dp;
int* res = (int*) malloc(sizeof(int)*r_size);
int j = r_size - 1;
res[j] = a[max_value_idx];
max_value --;
int idx = max_value_idx - 1;
while(max_value > 0){
if(dp[idx] != dp[idx+1]){
res[--j]=a[idx];
max_value --;
}
idx--;
}
print_arr(res,r_size);
}
void print_arr(int* a, int arr_len){
for(int i=0;i<arr_len;i++){
if(i==0){
cout<<a[i];
}else{
cout<<" "<<a[i];
}
}
}
代码说明
- 初始化
dp
:dp[i]
表示以nums[i]
结尾的最长递增子序列的长度,初始值为1
。 - 双重循环 :遍历
nums
的每一对(j, i)
元素,更新dp[i]
。 - 返回结果 :
max(dp)
返回最长递增子序列的长度。
📈 总结
- 动态规划 在求解最长递增子序列问题时,能够高效记录每个位置的状态,避免重复计算。
dp
数组 记录了以每个元素为终点的最长递增子序列的长度,从而将问题分解为小问题进行求解。- 这种方法尽管时间复杂度是 O(n²),但可以应对大部分常见的序列长度,如几千个元素的序列。