零、參考資料
一、大體思路
規律(思路)是:(注:結果集中存儲的是都是下標,不是原數組的數據)
1. 遍歷原數組,比對當前值與結果集最後一位
2. 如果當前值大,則推入結果集(注意,是推入,即拓寬結果集)
3. 如果當前值小,則用二分法(這也是一個優化)去找結果集中比當前值大的項,然後(用當前值在原數組中的下標)替換掉結果集中的結果
4. 前驅節點追溯,替換掉錯誤的節點 - 這是對第 3 步的修正
二、最簡單(理想)的情況
let origin = [10, 11, 12, 13, 14, 15, 16, 0]; function getSequence(origin = []) { const result = [0]; // 默認以數組中第 0 個爲基準來做序列,注意[存放的是數組索引] const len = origin.length; let resultLastIndex; // 結果集中最後一位的數據,而結果集記錄的是數據在 origin 數組中的下標 for (let i = 0; i < len; i++) { const current = origin[i]; // if (arrI !== 0) { // 在vue newIndexToOldIndexMap 中,0代表需要創建新元素,無需進行位置移動操作,此處不需要,先行註釋掉 resultLastIndex = result[result.length - 1]; if (current > origin[resultLastIndex]) { // 對應規律(思路) 2 result.push(i); continue; } // } } return result; } let idxArr = getSequence(origin); let rst = idxArr.map(idx => origin[idx]); console.log(rst);
三、貪心 + 二分處理的情況
在最理想的情況之上處理規律(思路) 3
let origin = [10, 11, 12, 13, 14, 15, 16, 0]; function getSequence(origin = []) { const result = [0]; // 默認以數組中第 0 個爲基準來做序列,注意[存放的是數組索引] const len = origin.length; let resultLastIndex; // 結果集中最後一位的數據,而結果集記錄的是數據在 origin 數組中的下標 let start, end, middle; for (let i = 0; i < len; i++) { const current = origin[i]; // if (arrI !== 0) { // 在vue newIndexToOldIndexMap 中,0代表需要創建新元素,無需進行位置移動操作,此處不需要,先行註釋掉 resultLastIndex = result[result.length - 1]; if (current > origin[resultLastIndex]) { // 對應規律(思路) 2 result.push(i); continue; } // 通過二分查找,在結果集中找到僅大於當前值的(所有大於當前值的結果中的最小值),用當前值的索引將其替換掉 // 二分運算,對應規律(思路) 3 start = 0; end = result.length - 1; while (start < end) { middle = ((start + end) / 2) | 0; // 向下取整 if (current > origin[result[middle]]) { start = middle + 1; } else { end = middle; } } // 進行替換 if (current < origin[result[end]]) { result[end] = i; } // } } return result; } let idxArr = getSequence(origin); let rst = idxArr.map(idx => origin[idx]); console.log(rst);
四、前驅節點追溯處理的情況
貪心 + 二分處理的場景覆蓋了所有情況,但是這裏有個問題:如果源數組是 [102, 103, 101, 105, 106, 108, 107, 109, 104] ,那麼篩選出的子序列是 [101, 103, 104, 106, 107, 109] ,
而正確的應該是 [102, 103, 105, 106, 107, 109] ,即長度沒問題,內部數據確是錯的
所以,需要引入一個回溯列表 p,來記錄一些數據,其長度和 origin 數組長度一致,所有位置上均爲 0,這個數組記錄的也是數據在 origin 中的下標,如果當前數據進入到了結果集,則記錄下(結果集中)前值(具體看代碼和圖示)
let origin = [102, 103, 101, 105, 106, 108, 107, 109, 104]; function getSequence(origin = []) { const result = [0]; // 默認以數組中第 0 個爲基準來做序列,注意[存放的是數組索引] const len = origin.length; const p = new Array(len).fill(0); // 最後要標記索引,放的東西不用關心,但是要和源數組一樣長 // const p = Array.from({ length: len }, () => 0); // 另外一種寫法 let resultLastIndex; // 結果集中最後一位的數據,而結果集記錄的是數據在 origin 數組中的下標 let start, end, middle; for (let i = 0; i < len; i++) { const current = origin[i]; // if (current !== 0) { // 在vue newIndexToOldIndexMap 中,0代表需要創建新元素,無需進行位置移動操作,此處不需要,先行註釋掉 resultLastIndex = result[result.length - 1]; if (current > origin[resultLastIndex]) { // 對應規律(思路) 2 result.push(i); p[i] = resultLastIndex; // 當前放到末尾的要記錄他前面的索引,用於追溯 continue; } // 通過二分查找,在結果集中找到僅大於當前值的(所有大於當前值的結果中的最小值),用當前值的索引將其替換掉 start = 0; end = result.length - 1; while (start < end) { middle = ((start + end) / 2) | 0; // 向下取整 if (current > origin[result[middle]]) { start = middle + 1; } else { end = middle; } } if (current < origin[result[end]]) { result[end] = i; p[i] = result[end - 1]; // 記住前值, 其實就是和 p[i] = resultLastIndex; 這句在不同的邏輯分支上 } // } } // 對應規律(思路) 4,從 p 的末尾開始回溯修正 let i = result.length; let last = result[i - 1]; while(i-- > 0) { result[i] = last; last = p[last]; } return result; } let result = getSequence(origin); console.log(result.map(r => origin[r]))