题目描述
- 难点在于时间复杂度 O(n * logn)的做法
思路 & 代码
动态规划 O( n 2 n^2 n2)
- 先抛砖引玉啦~
- dp[i]:以 nums[i] 结尾的子序列,能达到的最大长度
- 对于 dp[i] 来说,只要找到前面的比 nums[i] 小的 nums[j] 中最大的 dp[j] 即可
class Solution {
public int lengthOfLIS(int[] nums) {
int len = nums.length;
int ans = 1;
// dp[i]:以 nums[i] 结尾的子序列,能达到的最长长度
int[] dp = new int[len];
// 边界
dp[0] = 1;
// 复杂度 O(n^2)
for(int i = 1; i < len; i++){
// 找到前面结点里,比 nums[i] 小的中,dp[] 最长的值
int maxPre = 0;
for(int j = i - 1; j >= 0; j--){
if(nums[i] > nums[j]){
// dp[j] 最优子结构
maxPre = Math.max(dp[j], maxPre);
}
}
// 状态转移方程
dp[i] = maxPre + 1;
ans = Math.max(ans, dp[i]);
}
return ans;
}
}
动态规划 + 二分法 + 贪心 O( n l o g n nlogn nlogn)
- 新定义 tail[i]:代表长度为 i + 1 的递增子序列的最小尾值
- 详见注释。建议还是看看这篇题解,结合里面的动图会更好地理解这道题。
class Solution {
public int lengthOfLIS(int[] nums) {
// dp + 二分 + 贪心
int len = nums.length;
// tail[i]:长度为 i + 1 的递增子序列的最小尾值
int[] tail = new int[len];
// 边界
tail[0] = nums[0];
// 尾值下标
int endIndex = 0;
for(int i = 1; i < len; i++){
// nums[i] > tail[endIndex] 的情况:加入尾,更新 endIndex
if(nums[i] > tail[endIndex]){
tail[++endIndex] = nums[i];
}
// <= 的情况,二分法找到第一个 >= nums[i] 的值,并更新
else{
int left = 0, right = endIndex;
// 区间选择:[left, mid] [mid + 1, right]
while(left < right){
// mid 向下取整,防止 mid == right 的情况导致死循环
int mid = left + (right - left) / 2;
// 直接舍弃掉这部分,mid 是用不到的
if(tail[mid] < nums[i]){
left = mid + 1;
}
// mid 是可能用得到的
else{
right = mid;
}
}
// 更新所取值
tail[left] = nums[i];
}
}
// 由下标获取长度
int ans = endIndex + 1;
return ans;
}
}
/**
* Q:为什么可以直接更新到前方?比如 [2,3,8,18]遇到4,更新成[2,3,4,18]
* 1. 这个数组在 endIndex 不变的维护过程中,只有[0, left]是满足题目条件的序列,[left + 1, endIndex]则不保证
* 2. 但是在 endIndex 增加的维护过程,可以保证“正确性”:因为之前肯定出现过满足题目条件的[0, endIndex]的,
* 1.的维护过程是建立于此的基础上进行更新的,因此 endIndex 值的正确性是可以保证的。
* 3. 小值更新到前方:贪心,这是为了之后遇到较小值也可以增加 endIndex 而服务的
*/
二刷
- 写不出 O(logN) 的,我还写不出你 O(n^2) 的吗!
- 10+ 行代码,还是挺清晰的,记得 dp[nums.length - 1] 不一定是最终答案噢~
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
dp[0] = 1;
int res = 1;
for(int i = 1; i < nums.length; i++) {
int max = 0;
for(int j = 0; j < i; j++) {
if(nums[j] < nums[i]) {
max = Math.max(max, dp[j]);
}
}
dp[i] = max + 1;
res = Math.max(res, dp[i]);
}
return res;
}
}