JS leetcode 旋轉數組 題解分析


壹 ❀ 引

今天來做一道同樣簡單,但是挺有趣的題,題目來自leetcode189. 旋轉數組,題目描述如下:

給定一個數組,將數組中的元素向右移動 k 個位置,其中 k 是非負數。

示例 1:

輸入: [1,2,3,4,5,6,7] 和 k = 3
輸出: [5,6,7,1,2,3,4]

解釋:
向右旋轉 1 步: [7,1,2,3,4,5,6]
向右旋轉 2 步: [6,7,1,2,3,4,5]
向右旋轉 3 步: [5,6,7,1,2,3,4]
示例 2:

輸入: [-1,-100,3,99] 和 k = 2
輸出: [3,99,-1,-100]

解釋:
向右旋轉 1 步: [99,-1,-100,3]
向右旋轉 2 步: [3,99,-1,-100]
說明:

盡可能想出更多的解決方案,至少有三種不同的方法可以解決這個問題。
要求使用空間復雜度為 O(1) 的 原地 算法。

老規矩,在分析完題目后,我先來說說我的實現思路,再來解析優質的解答。

貳 ❀ 解題思路

其實也不用被題目嚇到,說是旋轉數組,其實就是給定一個k表示要將數組最后一位數轉移到數組頭部次數的操作,比如第二個例子,k為2,表示一共要進行兩次操作:

第一次將數組最后一位也就是99移動到數組頭部。此時數組變成了[99,-1,-100,3],接着第二次操作,這時候最后一位是3:

此時數組變成了[3,99,-1,-100]

由於k為2,只需要執行2次,所以旋轉數組完成,思路已經很清晰了,我們來實現它:

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var rotate = function(nums, k) {
    // 如果數組只有一位或為空不用做任何操作
    if(nums.length<=1){
        return;
    };
    while(k>0){
        //每次取最后一位,並加入到頭部,由於splice返回的是數組,利用拓展運算符...還原成單個元素
        nums.unshift(...nums.splice(-1,1));
        k--;
    };
};

哎,有同學可能就想到了,我何必一個個的轉義,k為2,說到底就是把數組倒數兩個元素整個搬到數組頭部即可,然后我就開始寫了如下代碼:

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var rotate = function (nums, k) {
    nums.length <= 1 ? nums : nums.push(...nums.splice(0, nums.length - k));
};

2020.8.12更新

經博客園用戶Gary咖喱指出,對於splice第一參數為負數情況描述有誤,特做修正。

注意,這里的splice我是正向剪切,原因是我發現splice的第一個參數為負數時,比如-1表示倒序最后一個開始,第二個參數不管是幾,都只能剪一個:

[1,2,3].splice(-1,1); //[3]
[1,2,3].splice(-1,2); //[3]

當第一參數為負數時,表示倒序剪切,最后一個為-1,倒數第二個為-2,倒數第三個為-3,但裁剪順序還是從左往右裁剪。

如上圖,由於-1是最后一個元素,再往右沒有其它元素,所以第二參數不管是1還是2,都只能裁剪一個,如果我們想裁剪多個,比如:

[1,2,3].splice(-3,3); //[1,2,3]

因為-3是第一個,從左到右剪切3個,這樣就達到目的了。

最后需要補充的就是當第一參數為負數,且絕對值大於數組長度的情況,比如:

[1,2,3].splice(-4,3); //[1,2,3]

其實可以理解為裁剪起點在元素1(也就是索引-3)的左邊,往右裁剪自然涵蓋住了整個數組,所以本質還是對整個數組進行了裁剪。

前面的思路是把尾部剪切了拼到頭部,我們何不反過來,比如[3,99,-1,-100]我們剪3,99push到-100后面呢,所以這才有了nums.length - k表示前面我們應該剪切的個數。

很遺憾,這段代碼看似可以,但提交掛掉了,原因是這段代碼只滿足數組length大於k的情況,比如數組為[1,2],k為3,正確答案是交換3次變成[2,1]

而此時nums.length - k為-1,splice一大特點就是第二參數為0或者負數,表示一個不剪切,所以不符合。

此時,博客園用戶love編程的小可愛想到了%求余,我立馬靈光閃現改進了代碼:

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var rotate = function (nums, k) {
    nums.length <= 1 ? nums : nums.push(...nums.splice(0, nums.length - (k % nums.length)));
};

唯一區別只是在於nums.length - (k % nums.length),什么意思呢?

比如有數組[1,2,3,4],k為4,旋轉4次后你會發現結果和最初的樣子一模一樣。而且只要k為數組長度的整數倍都會造成這種情況。

所以比如k為5,其實可以看成4+1次,我們只用旋轉一次即可了,而%求余正好能達到這個效果:

8%4 //0
9%4 //1
3%4 //3
2%4 //2

所以用k%length算出我們真正要旋轉數組的次數即可,一行代碼搞定。

題目要求最少三種方法解答問題,但我沒能想出其它做法,這里再補充其它不錯的做法。

引用leetcode用戶秦時明月的一個不錯的做法,不用splice,直接利用pop即可:

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var rotate = function(nums,k) {
    for(var i = 0;i<k;i++){
        nums.unshift(nums.pop());
    };
};

這個就比我第一種實現要優雅的多了。

然后我在看leetcode用戶🎃的做法時,splice發揮到了極致:

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {void} Do not return anything, modify nums in-place instead.
 */
var rotate = function(nums, k) {
    nums.splice(0,0,...nums.splice(nums.length-k))
};

這代代碼執行是這樣,比如數組[1,2,3,4],假設k為3,先執行...nums.splice(nums.length-k),得到了2,3,4。此時數組變成了[1]

接着執行前面的nums.splice(0,0,2,3,4),表示從0位前面插入元素2,3,4於是變成了[2,3,4,1]

那么關於本題就說到這了。


免責聲明!

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



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