一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第28天,点击查看活动详情。
题目描述
给你一个整数数组 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
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路分析
- 今天的算法题目是数组题目,题目要求找出其中 最长严格递增子序列的长度。
- 这个题目是经典问题,有多种解法,首先可以使用基础动态规划方法解决,思路的核心是以 nums[i] 为结尾的最长上升子序列。定义 DP 数组,初始化 DP[i] = 1, 然后采用 for 双重循环, 定义变量 j 且 j < i, 在 [j, i] 区间,动态更新递增的最大值。基础的DP的时间复杂度是O(n * n), 空间复杂度是O(n)。
- 首先得学会上面的解法,上面的解法是最常见的思路。我们需要求的是最长的上升子序列,可以假设已经得到上升子序列的结尾数越小,便利后面街上一个数,则会有可能得到一个更大的最长上升子序列。
- 定义新状态,tail[i] 表示:长度为 i + 1 的 所有 上升子序列的结尾的最小值。初始化 tail[0] = nums[0]。
- 继续遍历 nums, 如果 nums[i] > tail[end], 则 end++, tail[end] = nums[i], 找到了下一个更大的数。否则在有序数组 tail 中查找第一个大于 nums[i] 的数,让他变小。这里采用二分查找提升效率。
- 最终,有序数组 tail 的长度,就是所求的「最长上升子序列」的长度。下列实现代码是大神 liweiwei1419 实现,看了诸多高票题解,个人认为这个是写的最好的。向大佬致敬!供参考。
通过代码
public class Solution {
public int lengthOfLIS(int[] nums) {
int len = nums.length;
if (len <= 1) {
return len;
}
// tail 数组的定义:长度为 i + 1 的上升子序列的末尾最小是几
int[] tail = new int[len];
// 遍历第 1 个数,直接放在有序数组 tail 的开头
tail[0] = nums[0];
// end 表示有序数组 tail 的最后一个已经赋值元素的索引
int end = 0;
for (int i = 1; i < len; i++) {
// 【逻辑 1】比 tail 数组实际有效的末尾的那个元素还大
if (nums[i] > tail[end]) {
// 直接添加在那个元素的后面,所以 end 先加 1
end++;
tail[end] = nums[i];
} else {
// 使用二分查找法,在有序数组 tail 中
// 找到第 1 个大于等于 nums[i] 的元素,尝试让那个元素更小
int left = 0;
int right = end;
while (left < right) {
int mid = left + ((right - left) >>> 1);
if (tail[mid] < nums[i]) {
left = mid + 1;
} else {
right = mid;
}
}
tail[left] = nums[i];
}
}
end++;
return end;
}
}
总结
- 在算法学习过程中,自己常常感到还有这种解法的神奇,还需要不断的刷题,思考,不断学习完善自己的知识体系。更好的完善思路,提升自己的能力!
- 坚持算法每日一题,加油!