【個人メモ】ニウケトピック:NC91最長上昇部分列(3)

説明
長さ n の配列 arr を指定して、arr の最長の昇順サブシーケンスを出力します。(回答が複数ある場合は、値による比較のため辞書順が小さいものを出力してください(注:単一文字のASCIIコード値とは異なります)) データ範囲:0≦n≦200000、0≦arr_i≦
1000000000
: 空間計算量 O(n)、時間計算量 O(nlogn)

例: 入力: [2,1,5,3,6,4,8,9,7]
戻り値: [1,3,4,8,9]

入力: [1,2,8,6,4]
戻り値: [1,2,4]
説明: 3 つの最長増加サブシーケンス (1, 2, 8)、(1, 2, 6)、(1) があります。 , 2, 4)、このうち 3 番目のものが数値比較の辞書順で最も小さいため、答えは (1, 2, 4) となります。

問題解決のアイデア(Niuke などの回答を参照):
貪欲 + 二分法を使用して、
尾が長くなり、尾を形成している要素が小さくなるように貪欲に望みます。そのため、尾から最初のものを見つけます。より大きい 置換がない場合は、長さを更新する必要があることを意味します。
たとえば、次のシーケンス A[ ] = 1 4 7 2 5 9 10 3 がある場合、LIS の長さを求めます。
A[1] = 1、B[1] に 1 を入れる、このとき B[1] = 1、B[ ] = {1}、len = 1 A[2] = 4、B[2 ] に 4 を
入れる, この時 B[2] = 4, B[ ] = {1,4}, len = 2
A[3] = 7, B[3] に 7 を入れます, この時 B[3] = 7, B [ ] = {1,4,7}、len = 3
A[4] = 2、2 は 4 より小さいため、B[2] の 4 を 2 に置き換え、B[ ] = {1,2 ,7 }, len = 3
A[5] = 5、5 は 7 より小さいため、B[3] の 7 を 5 に置き換えます。その後、B[ ] = {1,2,5}、len = 3 A[6
] = 9、B[4]に9を入れます、このとき B[4] = 9、B[] = {1,2,5,9}、len = 4
A[7] = 10、Bに10を入れます[5]、このとき B[5] = 10、B[ ] = {1,2,5,9,10}、len = 5
A[8] = 3、3 は 5 より小さいため、5 を置き換えますこのとき、B[3] = {1,2,3,9,10}、len = 5となり、
  最終的にLISの長さは5になります。しかし!ここでの 1 2 3 9 10 は、正しい最長昇順サブシーケンスではありません。B シーケンスは必ずしも最長の昇順サブシーケンスを表すわけではなく、最長のサブシーケンスの長さに対応するソートされた最小シーケンスのみを表します。
(上記はhttps://blog.csdn.net/lxt_Lucia/article/details/81206439からのものです)
この質問は辞書編集上の最小順序の部分列を取得する必要があるため、上記に基づいて変更されています;
Record curLen[ i] は、現在の要素 arr[i] が、最後に増加する最も長い部分列の長さであることを意味します。curLen
[i] によれば、最大長から逆順にたどる、つまり、 の条件を満たす要素です。 curLen[i] == ans は、長さ ans の辞書編集順序が最小のスキームです。

class Solution {
    
    
public:
    /**
     * retrun the longest increasing subsequence
     * @param arr int整型vector the array
     * @return int整型vector
     */
    vector<int> LIS(vector<int>& arr) {
    
    
        // write code here
        vector<int> tail(arr.size());  // 贪心+二分得到的子序列
        vector<int> curlen(arr.size()); // curlen[i]表示以arr[i]为结尾的元素对应的最长上升子序列长度
        // 遍历arr
        int tail_size = 0; // 因为tail初始化了一个固定的大小,所以得有一个表示tail当前大小的数
        int left, right, mid;
        for(int i=0; i<arr.size(); i++){
    
    
            // 二分法从tail中找第一个比arr[i]大的数
            left=0;
            right = tail_size;
            while(left<right){
    
    
                mid = left + (right-left)/2;
                if(tail[mid] >= arr[i])
                    right = mid;
                else
                    left = mid + 1;
            }
            // 若找到,找到后用arr[i]将那个较大的数替换掉;
            // 若找不到,说明tail所有数都比arr[i]小,则把arr[i]加入末尾
            tail[left] = arr[i]; 
            curlen[i] = left + 1;
            if(tail_size==left) // 找不到的情况,tail大小+1
                tail_size++;
        }
        /*
		eg:[2,1,5,3,6,4,8,9,7]
		arr[0]:2, tail:[2], curLen[0]:1
		arr[1]:1, tail:[1], curLen[1]:1
		arr[2]:5, tail:[1,5], curLen[2]:2
		arr[3]:3, tail:[1,3], curLen[3]:2
		arr[4]:6, tail:[1,3,6], curLen[4]:3
		arr[5]:4, tail:[1,3,4], curLen[5]:3
		arr[6]:8, tail:[1,3,4,8], curLen[6]:4
		arr[7]:9, tail:[1,3,4,8,9], curLen[7]:5
		arr[8]:7, tail:[1,3,4,7,9], curLen[8]:4  (8被替换成7,是以7为结尾的长度)
		ans = 5
		*/
        vector<int> res(tail_size); // 存放结果,虽然tail数组不是最终结果,但tail的大小为结果长度(最长上升子序列长度)
        // 逆序地从最大长度开始遍历
        int i,j;
        for(i=arr.size()-1, j=tail_size; i>=0; i--){
    
    
            if(curlen[i]==j)  // 以arr[i]为结尾的最长子序列长度为j说明arr[i]即为res的第j-1位
                // 因为相同长度(curlen相同)的情况下,越往后(i越大)的末尾元素越小(大的被小的替换了),所以从后往前取值可得到题目要求的结果
                res[--j] = arr[i];
            //j=5, curLen[i=7]:5, res[4]=arr[7], res: 0 0 0 0 9
			//j=4, curLen[i=6]:4, res[3]=arr[6], res: 0 0 0 8 9
			//j=3, curLen[i=5]:3, res[2]=arr[5], res: 0 0 4 8 9
			//j=2, curLen[i=3]:2, res[1]=arr[3], res: 0 3 4 8 9
			//j=1, curLen[i=1]:1, res[0]=arr[1], res: 1 3 4 8 9
        }
        return res;
    }
};

おすすめ

転載: blog.csdn.net/zoey_peak/article/details/125680304