class Solution {
/**
暴力法 + dp
思路:
dp[i] 表示, 必须以第 i 个位置结束的最长递增子序列的长度
每到一个位置, 都要回头扫描一遍已经遍历过数组, 如果存在
比当前位置的数值小的数组, 从他的 dp 中获取它的长度 + 1
可以得到 dp[i] 位置当前的一个可能取值, 去 0...i - 1
这段区间中所有可能取值的最大值, 就是 dp[i] 位置的答案,
如果 0...i - 1 这段区间的数值都比 nums[i] 大, 那么
dp[i] = 1
*/
public int lengthOfLIS(int[] nums) {
int N = nums.length;
if (N == 1) return 1; // 如果数组长度位置, 那么最长递增子序列就是 1
// dp[i] 表示, 必须以第 i 个位置结束的最长递增子序列的长度
int[] dp = new int[N];
dp[0] = 1; // dp[0] 必为 1
int ans = 1;
for (int i = 1; i < N; i++) {
dp[i] = 1;
// 扫描 0...i-1 区域的可能取值
for (int j = i - 1; j >= 0; j--) {
if (nums[i] > nums[j]) // 找到一个可能取值
// 取最大的那个
dp[i] = Math.max(dp[j] + 1, dp[i]);
}
ans = Math.max(ans, dp[i]);
}
return ans;
}
}
class Solution {
/**
二分 + dp
思路:
dp 数组含义, 到达 i 位置的最长递增子序列是谁
先在 dp 数组的有效区域找到第一个比 当前数值 nums[i] 大的
用 nums[i] 去更新这个数; 找不到就追加在最后, size++
比如: [10,9,2,5,3,7,101,18]
i == 0, 10 --> size = 1, dp = [10,0....] // 0....表示无效区域
i == 1, 9 --> size = 1, dp = [9,0....]
i == 2, 2 --> size = 1, dp = [2,0....]
i == 3, 5 --> size = 2, dp = [2,5,0...]
i == 4, 3 --> size = 2, dp = [2,3,0...]
i == 5, 7 --> size = 3, dp = [2,3,7,0...]
i == 6, 101 --> size = 4, dp = [2,3,7,101,0...]
i == 7, 18 --> size = 4, dp = [2,3,7,18,0...]
因为 dp 数组是有序, 所以我们能通过二分查找快速确定要找的位置
整体时间复杂度 O(n * log n)
*/
public int lengthOfLIS(int[] nums) {
int N = nums.length;
if (N == 1) return 1;
int[] dp = new int[N];
int size = 1;
dp[0] = nums[0];
for (int i = 1; i < N; i++) {
// 找 dp 数组中第一个比 nums[i] 大
int mostLeftBigIndex = search(dp, size, nums[i]);
if (mostLeftBigIndex == -1) {
// 如果找不到, 在后面追加
dp[size++] = nums[i];
} else {
// 找到了, 用当前位置的值, 更新 dp 的对应位置
dp[mostLeftBigIndex] = nums[i];
}
}
return size;
}
// 二分查找, 找数组中第一个比 key 大
private int search(int[] dp, int size, int key) {
int l = 0, r = size - 1;
int res = -1;
while (l <= r) {
int m = (l + r) / 2;
if (dp[m] >= key) {
res = m;
r = m - 1;
} else {
l = m + 1;
}
}
return res;
}
}