NC91 最长递增子序列

NC91 最长递增子序列

描述
给定数组arr,设长度为n,输出arr的最长递增子序列。(如果有多个答案,请输出其中 按数值(注:区别于按单个字符的ASCII码值)进行比较的 字典序最小的那个)
示例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)

解题思路:这道题用的是动态规划加二分查找的思路,动态数组maxLen保存的是当前下标i所对应元素能形成的最长递增子序列的长度,maxLen[0]初始化为1,动态数组seq没有明确的含义,但其长度是最终的连续递增子序列的长度,由于算法的原因,seq中保存的元素很接近最后的连续递增子数组,但确实不是最终的连续递增子数组,原因在下面进行说明。对于数组arr[2,1,5,3,6,4,8,9,7],下面开始分析maxLen和seq的生成过程:

  1. 初始条件下,maxLen[0] = 1, seq[0] = arr[0],也即maxLen = [1], seq = [2];
  2. 当进来一个新的元素1,由于1比2小,我们在seq中寻找第一个大于等于1的元素,即2,将其替换为1,同时maxLen[1] = seq.size(),即maxLen = [1, 1];
  3. 当遍历到5,由于5比1大,直接添加,seq = [1, 5], maxLen[2] = seq.size(), maxLen = [1, 1, 2];
  4. 当遍历到3,由于3比5小,在seq中寻找第一个大于等于3的元素,即5,替换,seq = [1, 3], maxLen = [1, 1, 2, 2];
  5. 遍历到6,seq = [1, 3, 6], maxLen = [1, 1, 2, 2, 3];
  6. 遍历到4,seq = [1, 3, 4], maxLen = [1, 1, 2, 2, 3, 3];
  7. 遍历到8,seq = [1, 3, 4, 8], maxLen = [1, 1, 2, 2, 3, 3, 4];
  8. 遍历到9,seq = [1, 3, 4, 8, 9], maxLen = [1, 1, 2, 2, 3, 3, 4, 5];
  9. 最后遍历到7,seq = [1, 3, 4, 7, 9], maxLen = [1, 1, 2, 2, 3, 3, 4, 5, 4];

可以看到,maxLen含义明确,始终代表的是以当前元素结尾的最长连续子序列的长度,而seq则多数时候也可以代表最长连续子序列,但因为我们算法的原因,并不完全是最终结果,比如看第9步,7应该是在9之后的,但我们仍然把它放到了前面,为什么会这样呢?主要是算法的惯性,当我们遇到一个新的元素比seq当前最后一个元素小的时候,我们会默认先找到seq中大于等于该元素的第一个元素,并且将其替换为当前元素,这可以保证在维持当前最大长度的同时,把更小的元素放在前面,这样在某些情况下可以减小seq中的最大值,保证后面有更多的元素可以加入,比如有一段序列[8,11,7,9,10],如果没有上面那个动态更新,那seq中是[8,11],后面的10就无论如何加不进来了,但有了这个动态更新之后,8会被替换成7,11会被替换成9,10就可以加到最后的结果里面了。

前一部分利用动态规划和二分查找的方法获得了maxLen和seq数组,其中有用的信息是seq的size和maxLen的内容,seq的size即是最后res的size,我们从maxLen从后往前遍历,变量j表示res剩下还未填充的元素,初始化为res的size,当发现maxLen[i] = j的时候,将j减一(下标),同时res[j]赋值为arr[i],从算法的性质可知,maxLen中的元素代表的是以arr[i]结束的最长连续子数组的长度,因此maxLen中两个数相同的话,越靠后的数据,对应的arr中的数也是越小的,比如上面第6步中,maxLen = [1, 1, 2, 2, 3, 3],最后一个3对应的seq是[1,3,4],倒数第二个3对应的是[1,3,6],我们从后往前遍历,刚好可以形成一个按ASCII码值排序更小的结果。代码如下:

代码

import java.util.*;


public class Solution {
    /**
     * retrun the longest increasing subsequence
     * @param arr int整型一维数组 the array
     * @return int整型一维数组
     */
    public int[] LIS (int[] arr) {
        // write code here
        int len = arr.length;
        List<Integer> maxLen = new ArrayList<>(len);
        List<Integer> seq = new ArrayList<>();
        if(len<=1) return arr;
        seq.add(arr[0]);
        maxLen.add(1);
        for(int i=1; i<len; i++){
            if(arr[i]>seq.get(seq.size()-1)){
                seq.add(arr[i]);
                maxLen.add(seq.size());
            }else{
                int left = 0, right = seq.size();
                while(left<right){
                    int mid = left + (right - left) / 2;
                    if(seq.get(mid)<arr[i]) left = mid + 1;
                    else right = mid;
                }
                maxLen.add(right+1);
                seq.set(right,arr[i]);
            }
        }
        int[] res = new int[seq.size()];
        for(int i=maxLen.size()-1, j=seq.size(); j>0; i--){
            if(maxLen.get(i)==j) res[--j] = arr[i];
        }
        return res;
    }
}

参考资料
https://blog.nowcoder.net/n/78d57989c2104ebe823368cc1301113f?f=comment

NC91 最长递增子序列

上一篇:判断元素滚动状态


下一篇:windows下的wxWidgets环境配置