JavaScript的two-sum問題解法

迷路的約翰發表於2017-01-05

一個很常見的問題,找出一個陣列中和為給定值的兩個數的下標。為了簡單一般會註明解只有一個之類的。

最容易想到的方法是迴圈遍歷,這裡就不說了。

在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%……

想到更快的方法再更………………

 

  

相關文章