题目描述
给定数组arr,设长度为n,输出arr的最长递增子序列。(如果有多个答案,请输出其中字典序最小的)
示例1
输入
[2,1,5,3,6,4,8,9,7]
返回值
[1,3,4,8,9]
示例2
输入
[1,2,8,6,4]
返回值
[1,2,4]
说明
其最长递增子序列有3个,(1,2,8)、(1,2,6)、(1,2,4)其中第三个字典序最小,故答案为(1,2,4)
解题思路
下面的“标号”讲的过于抽象,我推荐《最长递增子序列 - murphy_gb - 博客园》 这篇文章,里面的动图很形象。
- 我们将大问题拆分为小问题,把数组缩短,再慢慢加长直到完整。如示例 1 中 原数组 arr = [2, 1, 5, 3, 6, 4, 8, 9, 7] 我们把最初的子问题定为 [2, 1],下一个子问题既往后加长一位 [2, 1, 5],以此类推。
- 为了能在遍历完子问题后精确地在原数组 arr 中找出组成最长递增子序列 LCS 的元素,我们可以使用“标号”的方法,在 arr 中组成 LCS 的元素上标上序号,比如示例 1 中 arr = [2, 1, 5, 3, 6, 4, 8, 9, 7], LCS = [1, 3, 4, 8, 9],LCS[0] = arr[1],LCS[1] = arr[3],LCS[2] = arr[5]。所以如何标号就是这个问题的关键。
- 如何标号呢?我们需要新增一个数组 temp,每当子问题增加一个元素 e 时,e 就与 temp 最后一个元素就进行比较,如果 e 比 temp 的最后一个元素大,则直接在 temp 最后面添加 e;反之,则在 temp 中从左往右寻找第一个比 e 大的数,并用 e 替换之。然后 e 在 temp 中的索引就是我们要找的标号,我们将标号存起来,继续下一个子问题。
- 在 nums 中标完号后,为了满足题目要求的字典序最小,我们需要从后往前遍历,标号从大到小,倒着填入 LCS 中,最后我们获得结果 LCS。
Java代码实现
public class Solution {
public int[] LIS (int[] arr) {
int[] nums = new int[arr.length];
int[] temp = new int[arr.length];
nums[0] = 1;
int tempIndex = 0;
temp[tempIndex] = arr[0];
for (int i = 1; i < arr.length; ++i) {
int left = 0, right = tempIndex;
if (arr[i] > temp[tempIndex]) {
++tempIndex;
nums[i] = tempIndex + 1;
temp[tempIndex] = arr[i];
} else {
while (left <= right) { // 注意这里 left <= right 而不是 left < right,我们要替换的是第一个比 arr[i] 大的元素
int mid = (right + left) / 2;
if (temp[mid] > arr[i]) {
right = mid - 1;
} else {
left = mid + 1;
}
}
temp[left] = arr[i];
nums[i] = left + 1;
}
}
int[] res = new int[tempIndex + 1];
for (int i = nums.length - 1; i >= 0; --i) {
if (nums[i] == tempIndex + 1) {
res[tempIndex] = arr[i];
--tempIndex;
}
}
return res;
}
} 
京公网安备 11010502036488号