一個很常見的問題,找出一個陣列中和為給定值的兩個數的下標。為了簡單一般會註明解只有一個之類的。
最容易想到的方法是迴圈遍歷,這裡就不說了。
在JS中比較優雅的方式是利用JS的物件作為hash的方式:
1 var twoSum = function(nums, target) { 2 var hash = {}; 3 var i; 4 for (var i = 0; i < nums.length; i++ ) { 5 if (typeof hash[nums[i]] !== "undefined") { 6 return [i, hash[nums[i]]]; 7 } 8 hash[target - nums[i]] = i; 9 } 10 };
這裡面還可以做一些小的優化,比如把length拿出來,重複使用的 nums[i] 也抽取出來,遍歷的順序反過來等,最後大概弄成這個樣子:
var twoSum2 = function(nums, target) { var hash = {}; var i; var tmp; for (i = nums.length; i--; ) { tmp = nums[i]; if (typeof hash[tmp] !== "undefined") { return [i, hash[tmp]]; } hash[target - tmp] = i; } };
不過這些小的優化基本上不影響大局,在leetcode上測試了好幾遍,排名總在75%到85%之間浮動。居然連90%都沒達到,我對你很失望
讓我再想想……
哦對了我記得前幾天看到過一個優化迴圈的方法叫做Duff Device來著,不如試試?
連結在這裡 https://en.wikipedia.org/wiki/Duff%27s_device。(怎麼插入連結來著?)
這東西我的大概理解就是就是把for迴圈稍微展開一下,本來迴圈80次的行為,改寫成執行10次迴圈,每次迴圈裡執行8次。這樣可以省下一點呼叫for迴圈的開銷。不過這個前提是for迴圈裡的執行程式碼比較簡單。如果耗時主要在每個迴圈裡的話,這招就不太管用了聽說。
於是我照著介紹的例子,把上面的程式碼改寫了一下:
1 var twoSum1 = function(nums, target) { 2 var hash = {}; 3 var i = 0; 4 var startIndex = nums.length % 8; 5 var iterations = nums.length / 8; 6 do { 7 8 switch(startIndex) { 9 case 0: if(typeof hash[nums[i]] !== "undefined") {return [i, hash[nums[i]]]} hash[target - nums[i]] = i; i++; 10 case 7: if(typeof hash[nums[i]] !== "undefined") {return [i, hash[nums[i]]]} hash[target - nums[i]] = i; i++; 11 case 6: if(typeof hash[nums[i]] !== "undefined") {return [i, hash[nums[i]]]} hash[target - nums[i]] = i; i++; 12 case 5: if(typeof hash[nums[i]] !== "undefined") {return [i, hash[nums[i]]]} hash[target - nums[i]] = i; i++; 13 case 4: if(typeof hash[nums[i]] !== "undefined") {return [i, hash[nums[i]]]} hash[target - nums[i]] = i; i++; 14 case 3: if(typeof hash[nums[i]] !== "undefined") {return [i, hash[nums[i]]]} hash[target - nums[i]] = i; i++; 15 case 2: if(typeof hash[nums[i]] !== "undefined") {return [i, hash[nums[i]]]} hash[target - nums[i]] = i; i++; 16 case 1: if(typeof hash[nums[i]] !== "undefined") {return [i, hash[nums[i]]]} hash[target - nums[i]] = i; i++; 17 } 18 19 } while (--iterations); 20 21 };
嗯,看上去很厲害的樣子。接下來在瀏覽器裡測試一下:
先構造一個長度為1000的陣列,來來來:
呃……好像沒什麼差別。把資料加大一點,到100000吧:
臥槽牛逼啊!趕緊把這個放leetCode上試試!
……
……
……
然而在leetCode上試了之後,雖然通過了,發現和之前的執行效率並沒有多大差別……依然沒有到90%
一定是leetCode上的單測資料太小了!於是自個兒在leetCode上的那個小輸入框裡貼上了一個十幾萬長度的資料,跑了一遍,發現仍然沒有什麼卵用。
所以大概是我的測試方法有問題?資料翻了100倍時間居然並沒有增加多少,好奇怪。
好吧,這個方法就暫時擱著。再想想其它歪主意。
用js自帶的forEach做迴圈試試吧,說不定會比手寫的要快點。於是有了下列程式碼:
var twoSum = function (nums, target) { var hash = {}; var result = []; nums.forEach(function (v, i) { if (typeof hash[v] !== "undefined") { result[0] = i; result[1] = hash[v]; } hash[target - v] = i; }); return result; }
不過這有個問題是,forEach迴圈沒法正常地退出,所以無論怎樣都要遍歷一下整個陣列,不能像前面那樣找到正確的就退出了,感覺太好。
所以只能讓它不正常地退出了:
var twoSum = function (nums, target) { var hash = {}; var result = []; try { nums.forEach(function (v, i) { if (typeof hash[v] !== "undefined") { result[0] = i; result[1] = hash[v]; throw "err"; } hash[target - v] = i; }); } catch (err) { return result; } }
好的就這樣,跑一下試試:
噢好吧,好歹過了90%……
想到更快的方法再更………………