1、關於leetcode
這是第一篇關於leetcode的題解,就先扯點關於leetcode的話。
其實很早前就在博客園看到過leetcode一些題解,總以為跟一般OJ大同小異,直到最近點開了一篇博文Leetcode 編程訓練,無意間點進leetcode的主頁看了下,乖乖,居然能用JavaScript提交代碼(還能用python、ruby等)!瞬間來了興趣,一口氣把幾十道水題都切完了,代碼放在了github,有興趣的可以參考或者幫忙review一下。對於個人認為有意思的題目,樓主也會時不時地寫些題解和大家分享下。
2、解題過程###
今天要說的是Rotate Array這題,並不是說這題有多么地難(leetcode把它難度定位為EASY),而是讓我理解了JavaScript中以前聽說過但是一直沒引起重視的一個很重要的性質。
先回到這道題本身,題目很簡單,給一個數組,向右移動k位,求新的數組,關鍵來了,題目要求你Do not return anything, modify nums in-place instead.。右移k位,相當於把數組最右邊的k位放到數組開頭,還要考慮k大於數組長度的情況,似乎也很容易想到,寫下如下代碼:
var rotate = function(nums, k) {
k %= nums.length;
var tmp = [];
if (k)
tmp = nums.slice(-k);
nums.splice(-k, k);
nums = tmp.concat(nums);
};
tmp保存了右邊要移動要前面的數組(slice),而nums自己則截掉后面要移動的數組(splice),然后把兩段數組一拼,不是說直接修改nums數組么,那把結果直接賦給nums就ok了!但是無情地返回了wrong answer,leetcode不提供sample,但是出錯了會給一組出錯了的數據,數據如下:
Input: [1,2], 1
Output: [1]
Expected: [2,1]
嘗試着把數組帶入,打印結果:
var rotate = function(nums, k) {
k %= nums.length;
var tmp = [];
if (k)
tmp = nums.slice(-k);
nums.splice(-k, k);
nums = tmp.concat(nums);
console.log(nums); // [2, 1]
};
rotate([1, 2], 1);
靠,輸出的真的是你expected的東西啊!正當我百思不得其解的時候,我突然意識到我犯了一個很嚴重的錯誤。leetcode服務器匹配你的結果正確與否,不可能進入你寫的函數里去判斷!而實際上,它應該是這樣判斷的:
var rotate = function(nums, k) {
k %= nums.length;
var tmp = [];
if (k)
tmp = nums.slice(-k);
nums.splice(-k, k);
nums = tmp.concat(nums);
};
var a = [1, 2];
rotate(a, 1);
console.log(a); // [1]
確實與expected的不符!為什么數組a會變成1?怎樣才能變成expected的答案?我們接下去看。
3、JavaScript函數的參數傳遞方式###
關於變量值的復制我們都已經很清楚了,基本類型(undefined、null、boolean、number、string)和引用類型(object)是不一樣的。如果從一個變量向另一個變量復制基本類型的值,會在變量對象上創建一個新值,然后把該值賦值到為新變量分配的位置上,此后這兩個變量可以參與任何操作而不會互相影響。而當從一個變量向另一個變量復制引用類型的值時,同樣也會將存儲在變量對象中的值復制一份放到為新變量分配的空間中,不同的是,這個值的副本實際上是一個指針,而這個指針指向存儲在堆中的一個對象。復制操作結束后,兩個變量實際上將引用同一個對象。因此,改變其中一個變量,就會影響另一個變量:
var a = [0, 1, 2, 3];
var b = a;
b.push(4, 5, 6);
console.log(a); // [0, 1, 2, 3, 4, 5, 6]
而ECMAScript中所有函數的參數都是按值傳遞的,也就是說,把函數外部的值復制給函數內部的參數,就和把值從一個變量復制到另一個變量一樣!弄清楚了這一點,我們再回到上面的代碼。
當沒有執行nums = tmp.concat(nums);這行代碼時,數組a和函數rotate的參數nums都引用着同一個地址,於是它們的值一起改變;當執行這行代碼后,nums指向了一個新的地址,無論它指向哪里,此時的它已經和數組a沒有任何關系,也就是說a的值在這一刻之后不會再變化了。

於是我們很清楚地知道,要想在nums上體現a的變化,函數內的nums參數不能去引用一個新的對象,只能在自身上操作,思考下寫下如下代碼,終於AC:
var rotate = function(nums, k) {
k %= nums.length;
var tmp = [];
if (k)
tmp = nums.slice(-k);
nums.splice(-k, k);
Array.prototype.unshift.apply(nums, tmp);
};
利用JavaScript的這個性質,能出現一些很神奇的效果,這些我也在做題過程中逐漸體會到了一點,具體題目到時再跟大家分享吧!
