方法一:动态规划 O(N^2)
- dp[i]表示以nums[i]为结尾的最长上升序列的长度
- 从前缀中获得dp【i】
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];//dp[i]表示以nums[i]为结尾的最长上升序列的长度
Arrays.fill(dp, 1); //初始值设为1,即子序列为1个数字
int max = 0;
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if(nums[j] < nums[i]) {
dp[i] = Math.max(dp[i], dp[j] + 1);//遍历i之前的元素,滚动更新记录一个+1的最大值
}
}
max = Math.max(max, dp[i]);
}
return max;
}
}
- 输出最长递增子序列
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp, 1); //初始值设为1,即子序列为1个数字
int max = 0;
int maxLastIndex = 0;//最长递增序列的最后一个节点index
Map<Integer, Integer> map = new HashMap<>();//<当前节点index,前序节点index>
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
// dp[i] = Math.max(dp[i], dp[j] + 1);
if (dp[j] + 1 > dp[i]) {
map.put(i, j);// i的前序节点为j,滚动更新
dp[i] = dp[j] + 1;
}
}
}
// max = Math.max(max, dp[i]);
if (dp[i] > max) {
max = dp[i];
maxLastIndex = i; // 最长序列的最后一个节点index
}
}
// 获取路径
StringBuffer sb = new StringBuffer();
sb.append(nums[maxLastIndex]);
sb.append(",");
while (map.get(maxLastIndex) != null) {
sb.append(nums[map.get(maxLastIndex)]);
sb.append(",");
maxLastIndex = map.get(maxLastIndex);
}
System.out.println(sb.reverse().toString().substring(1, sb.length()));
return max;
}
}
方法二:贪心 + 二分查找 O(NlogN)
-
贪心:如果我们要使上升子序列尽可能的长,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。
-
维护一个数组
arr
,arr[i]
表示长度为i
的最长上升子序列的末尾元素的最小值,arr
一定是升序的。 注意,arr不一定为题目求的上升子序列的解。 -
遍历nums,
- 如果
nums[i]
大于arr
中所有元素,则扩容arr
,加上nums[i]
; - 如果
nums[i]
在arr
大小范围中,则插入nums[i]
到合适位置覆盖arr
中的某个元素。由于arr
是升序的,寻找合适位置的过程使用二分查找,复杂度为O(logN)
- 如果
-
遍历完nums后,arr长度即为所求。
-
遍历插入替换元素的过程可以理解为覆盖卡牌:
简而言之:
-
维护一个结果数组,如果当前元素比结果数组的值都大的的话,就追加在结果数组后面(相当于递增序列长度加了1)
-
否则的话用当前元素覆盖掉第一个比它大的元素,表示长度为
i
的最长上升子序列的末尾元素的最小值还可以更小。
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
if (n == 0) {
return 0;
}
int[] arr = new int[n + 1];
arr[1] = nums[0];
int len = 1;
for (int i = 1; i < n; ++i) {
if (nums[i] > arr[len]) {
len++;
arr[len] = nums[i];
} else { // 可以替换arr中的元素,替换哪一个呢?由于arr升序,所以用二分来确定位置。
//找第一个比nums[i]大的值, 然后把它用numsi替换掉
int low = 1, high = len;
while (low <= high) {
int mid = (high + low) / 2;
if (arr[mid] < nums[i]) {
low = mid + 1;
} else {
high = mid - 1;
}
}
if (high == -1) {//没找到,arr中所有的数都比num[i]大
arr[0] = nums[i];
} else {
arr[low] = nums[i];
}
}
}
return len;
}
}
//nums升序有重复元素,找第一个>=target的数的index
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
int res = 0;
if (left == nums.length) {//tar大于所有nums
res = left;
} else if (right == -1) {//tar小于所有nums
res = 0;
} else {//tar在nums里边,则一定能找到,找到时,nums[mid] = tar,循环最后一次时,left=right=mid一定还在tar的位置上,退出while后,right-1.所以用left
res = left;//
}
return res;
}
}