壹 ❀ 引
一日一題,今天的題目來自於leetcode26. 刪除排序數組中的重復項,其實在之前我們已經做了一道類似的題目,可參考JS leetcode 移除元素 題解分析,關於本題描述如下:
給定一個排序數組,你需要在 原地 刪除重復出現的元素,使得每個元素只出現一次,返回移除后數組的新長度。
不要使用額外的數組空間,你必須在 原地 修改輸入數組 並在使用 O(1) 額外空間的條件下完成。
示例 1:
給定數組 nums = [1,1,2], 函數應該返回新的長度 2, 並且原數組 nums 的前兩個元素被修改為 1, 2。你不需要考慮數組中超出新長度后面的元素。
示例 2:給定 nums = [0,0,1,1,1,2,2,3,3,4], 函數應該返回新的長度 5, 並且原數組 nums 的前五個元素被修改為 0, 1, 2, 3, 4。你不需要考慮數組中超出新長度后面的元素。
我們先來簡單分析題目,再給出解題思路。
貳 ❀ 解題思路
經過leetcode算法題移除元素的教育,這次解題得額外抓住兩個重點:
第一,題目真正的要求是獲取不重復元素的長度,所以即使不刪除重復元素也是允許的。
第二,不需要考慮數組中超出新長度后的元素,這句話的意思其實已經暗示,我們可以將不重復的元素集中在數組前面,后面的重復元素可以不管,比如
[1,2,2,3] => [1,2,3,2] 返回3,因為不重復的元素為 1,2,3
當然,我們可以先站在刪除重復元素的角度來思考這個問題,這對於前端開發者來說更容易接受,直接上代碼:
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function (nums) {
for (var i = 0; i < nums.length;) {
if (nums.indexOf(nums[i]) !== i) {
nums.splice(i, 1);
} else {
i++;
};
};
return nums.length;
};
思路很簡單,因為indexOf獲取的都是每個元素第一次出現的問題,所以只要當前元素的i不是第一次出現,那就說明是重復元素,利用splice刪除執行元素即可。
而站在不刪除元素的角度,這就得利用雙指針進行元素覆蓋,我們先上代碼,再解釋原理:
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function (nums) {
if(nums.length === 0){
return 0;
};
var s = 0, //慢指針,每賦值一次,慢指針往右移動一位
f = 1, //快指針,只要重復就往右移動一位
len = nums.length;
for (; f < len; f++) {
if (nums[s] !== nums[f]) {
nums[s + 1] = nums[f];
s++;
};
};
return s + 1;
};
我們以[1,1,2,3]為例,注意,題目已經說了數組為已排序數組,所以相同元素必定相鄰。
我們指定2個指針,慢指針s(slow)與快指針f(fast),慢指針主要負責當元素不同時進行賦值,快指針主要負責數組遍歷,對每個元素進行比較。
說到底,就是每次比較完成之后,只要這個元素沒重復,就把這個元素通過賦值的形式,依次排在s指針索引的后面;若比較相同就啥都不干,直接讓快指針繼續往后移動。
我們通過圖解,來模擬[1,1,2,3]的賦值過程:
可以看到,一開始由於s為0,f為1,進行了第一次比較,如果相同,s作為起點元素保持不變,f作為快指針繼續尋找下個比較目標;
而只要s與f的元素不同,則將f的元素復制到s的后一位,以此類推,由於s的索引是0,經過幾次復制自增幾次,最后獲取的長度需要額外加1,大致上就是這個思路。
我最開始也是繞了半天,但是通過圖解,真是感嘆雙指針的微妙之處。
當然,雙指針代碼處的if操作其實也可以改寫成如下形式,思路是一樣的,不過我還是認為上述寫法好理解一點,先賦值,s再移動:
if (nums[s] !== nums[f]) {
s++;
nums[s] = nums[f];
};
那么關於本題的分析就到這里了。
