- 原题链接🔗:长递增子序列
- 难度:中等⭐️⭐️
题目
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的
子序列
。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。
示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
1 <= nums.length <= 2500
-104 <= nums[i] <= 104
进阶:
你能将算法的时间复杂度降低到 O(n log(n)) 吗?
动态规划
动态规划(Dynamic Programming,简称DP)是一种通过把原问题分解为相对简单的子问题的方式来求解复杂问题的方法。它通常用于优化问题,特别是那些具有重叠子问题和最优子结构的问题。动态规划通常用于计算科学、管理科学、经济学、生物信息学等领域。
动态规划的关键步骤包括:
定义状态:确定问题的状态,这些状态通常与问题的时间或空间维度相关。
确定状态转移方程:找出状态之间的关系,即如何从一个状态转移到另一个状态。
确定边界条件:定义问题的基本情况,也就是动态规划的起点。
计算顺序:确定计算状态的顺序,以确保在计算当前状态之前,所有依赖的状态都已经被计算。
实现算法:编写代码实现上述步骤。
动态规划的常见应用包括:
- 斐波那契数列
- 背包问题
- 最长公共子序列(LCS)
- 最长递增子序列(LIS)
- 矩阵链乘问题
- 硬币找零问题
- 编辑距离问题
动态规划与贪心算法不同,贪心算法在每一步都做出局部最优的选择,而动态规划则通过考虑所有可能的状态来找到全局最优解。
题解
- 解题思路:
LeetCode 上的“长递增子序列”问题通常指的是找出一个序列中最长的严格递增子序列(子序列中的元素不一定是连续的)。这个问题可以通过动态规划来解决。下面是一个解题思路:
定义状态:定义一个数组
dp
,其中dp[i]
表示以第i
个元素结尾的最长递增子序列的长度。初始化:由于每个元素至少可以单独构成一个长度为1的递增子序列,所以
dp
数组的初始值可以设为1
。状态转移方程:对于每个元素
nums[i]
,遍历它之前的所有元素nums[j]
(其中j < i
),如果nums[j] < nums[i]
,则说明nums[i]
可以接在nums[j]
后面形成一个更长的递增子序列。这时,更新
dp[i]
的值为max(dp[i], dp[j] + 1)
。遍历更新:遍历数组,对于每个元素,根据状态转移方程更新
dp
数组。得到结果:遍历结束后,
dp
数组中的最大值即为整个数组的最长递增子序列的长度。复杂度分析:时间复杂度为 O(n^2),其中 n 是数组
nums
的长度,因为需要两层循环遍历数组。
- c++ demo:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int lengthOfLIS(vector<int>& nums) {
if (nums.empty()) return 0;
vector<int> dp(nums.size(), 1); // 初始化dp数组,每个元素至少可以构成长度为1的递增子序列
int maxLength = 0;
for (int i = 0; i < nums.size(); ++i) {
for (int j = 0; j < i; ++j) {
if (nums[i] > nums[j]) {
dp[i] = max(dp[i], dp[j] + 1);
}
}
maxLength = max(maxLength, dp[i]); // 更新最长递增子序列的长度
}
return maxLength;
}
int main() {
vector<int> nums = { 10, 9, 2, 5, 3, 7, 101, 18 };
cout << "The length of the Longest Increasing Subsequence is: " << lengthOfLIS(nums) << endl;
return 0;
}
- 输出结果:
The length of the Longest Increasing Subsequence is: 4
- 代码仓库地址:lengthOfLIS