|
|
|
|
package class09;
|
|
|
|
|
|
|
|
|
|
// 本题测试链接 : https://leetcode.com/problems/longest-increasing-subsequence
|
|
|
|
|
public class Code03_LIS {
|
|
|
|
|
|
|
|
|
|
public static int lengthOfLIS(int[] arr) {
|
|
|
|
|
if (arr == null || arr.length == 0) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
// ends数组
|
|
|
|
|
// ends[i]表示 : 目前所有长度为i+1的递增子序列的最小结尾
|
|
|
|
|
int[] ends = new int[arr.length];
|
|
|
|
|
// 根据含义, 一开始ends[0] = arr[0]
|
|
|
|
|
ends[0] = arr[0];
|
|
|
|
|
// ends有效区范围是0...right,right往右为无效区
|
|
|
|
|
// 所以一开始right = 0, 表示有效区只有0...0范围
|
|
|
|
|
int right = 0;
|
|
|
|
|
// 最长递增子序列的长度
|
|
|
|
|
// 全局变量,抓取每一步的答案,取最大的结果
|
|
|
|
|
int max = 1;
|
|
|
|
|
for (int i = 1; i < arr.length; i++) {
|
|
|
|
|
int l = 0;
|
|
|
|
|
int r = right;
|
|
|
|
|
// 在ends[l...r]范围上二分
|
|
|
|
|
// 如果 当前数(arr[i]) > ends[m],砍掉左侧
|
|
|
|
|
// 如果 当前数(arr[i]) <= ends[m],砍掉右侧
|
|
|
|
|
// 整个二分就是在ends里寻找 >= 当前数(arr[i])的最左位置
|
|
|
|
|
// 就是从while里面出来时,l所在的位置。
|
|
|
|
|
// 如果ends中不存在 >= 当前数(arr[i])的情况,将返回有效区的越界位置
|
|
|
|
|
// 也就是从while里面出来时,l所在的位置,是有效区的越界位置
|
|
|
|
|
// 比如 : ends = { 3, 5, 9, 12, 再往右无效}
|
|
|
|
|
// 如果当前数为8, 从while里面出来时,l将来到2位置
|
|
|
|
|
// 比如 : ends = { 3, 5, 9, 12, 再往右无效}
|
|
|
|
|
// 如果当前数为13, 从while里面出来时,l将来到有效区的越界位置,4位置
|
|
|
|
|
while (l <= r) {
|
|
|
|
|
int m = (l + r) / 2;
|
|
|
|
|
if (arr[i] > ends[m]) {
|
|
|
|
|
l = m + 1;
|
|
|
|
|
} else {
|
|
|
|
|
r = m - 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// 从while里面出来,看l的位置
|
|
|
|
|
// 如果l比right大,说明扩充了有效区,那么right变量要随之变大
|
|
|
|
|
// 如果l不比right大,说明l没有来到有效区的越界位置,right不变
|
|
|
|
|
right = Math.max(right, l);
|
|
|
|
|
// l的位置,就是当前数应该填到ends数组里的位置
|
|
|
|
|
ends[l] = arr[i];
|
|
|
|
|
// 更新全局变量
|
|
|
|
|
max = Math.max(max, l + 1);
|
|
|
|
|
}
|
|
|
|
|
return max;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|