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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM