一些經常使用的算法技巧

一、巧用數組下標數組

例:在統計一個字符串中字幕出現的次數時,咱們就能夠把這些字母當作數組下標,在遍歷的時候,若是字母A遍歷到,則arr['a']就能夠加1了。bash

法一:利用對象的key值不重複優化

var str = 'hajshdjldjf';
function count(str){
    var obj = {};
    for(var i = 0; i < str.length; i++){
        if(obj[str[i]]){
            obj[str[i]]++;
        }else{
            obj[str[i]] = 1;
        }
    }
    console.log(obj);
    return obj;
}
count(str);複製代碼

法二:利用數組的下標ui

var str = 'hajshdjldjf';
function count(str){
    var arr = [];
    for(var i = 0; i < str.length; i++){
        if(arr[str[i]]){
            arr[str[i]]++;
        }else{
            arr[str[i]] = 1;
        }
    }
}
count(str);複製代碼

其實這兩種方法的思想是一致的。spa

例:給你n個無序的int整型數組arr,而且這些整數的取值範圍都在0-20之間,要你在 O(n) 的時間複雜度中把這 n 個數按照從小到大的順序打印出來。指針

對於這道題,若是你是先把這 n 個數先排序,再打印,是不可能O(n)的時間打印出來的。可是數值範圍在 0-20。咱們就能夠巧用數組下標了。把對應的數值做爲數組下標,若是這個數出現過,則對應的數組加1。
code

利用對象:cdn

var arr = [1,2,3,4,5,4,5,6,6,7,6,9,17,16,15,14,12,11];
//常規解法  利用對象的key值不能重複去計算次數
//res去記錄數字出現的順序
function fn(arr){
    arr.sort(function(a,b){
        return a - b;
    });
    var res = [];
    var resdetail = [];
    for(var i = 0; i < arr.length; i++){
        if(res.length === 0 || res[res.length-1] !== arr[i]){
            res.push(arr[i]);
            var obj = {
                key:arr[i],
                value:1
            };
            resdetail.push(obj);
        }else{
            resdetail[resdetail.length-1].value++;
        }
    }
    console.log(resdetail);
    return resdetail;


}
fn(arr);複製代碼

利用數組下標對象

var arr = [1,2,3,4,5,4,5,6,6,7,6,9,17,16,15,14,12,11];
//利用數組下標  時間複雜度爲O(n)
function fn(arr){
    var temp = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0];
    for(var i = 0; i < arr.length; i++){
        temp[arr[i]]++;
    }
    for(var j = 0; j < 21; j++){
        for(var k = 0; k < temp[j]; k++){
            console.log(j);
        }
    }
}
fn(arr);複製代碼

二、巧用取餘blog

有時候咱們在遍歷數組的時候,會進行越界判斷,若是下標差很少要越界了,咱們就把它置爲0從新遍歷。特別是在一些環形的數組中,例如用數組實現的隊列。每每會寫出這樣的代碼:

for (int i = 0; i < N; i++)
{
    if (pos < N) {
        //沒有越界
        // 使用數組arr[pos]
    else
        {
            pos = 0;//置爲0再使用數組
            //使用arr[pos]
        }
        pos++;
    }
}複製代碼

實際上咱們能夠經過取餘的方法來簡化代碼

for (int i = 0; i < N; i++) {
    //使用數組arr[pos]   (咱們假設剛開始的時候pos < N)
    pos = (pos + 1) % N;
}複製代碼

三、巧用雙指針

對於雙指針,在作關於單鏈表的題是特別有用,好比「判斷單鏈表是否有環」、「如何一次遍歷就找到鏈表中間位置節點」、「單鏈表中倒數第 k 個節點」等問題。對於這種問題,咱們就可使用雙指針了,會方便不少。

(1)判斷單鏈表中是否有環

咱們能夠設置一個快指針和一個慢指針,慢的一次移動一個節點,快的一次移動兩個節點。若是存在環,快指針會在第二次遍歷時和慢指針相遇。

(2)如何一次遍歷就找到鏈表中間位置節點

同樣是設置一個快指針和慢指針。慢的一次移動一個節點,快的一次移動兩個。當快指針遍歷完成時,慢指針恰好到達中點。

(3)單鏈表中倒數第 k 個節點

設置兩個指針,其中一個指針先移動k-1步,從第k步開始,兩個指針以相同的速度移動。當那個先移動的指針遍歷完成時,第二個指針指向的位置即倒數第k個位置。

四、巧用位移

有時候咱們在進行除數或乘數運算的時候,例如n / 2,n / 4, n / 8這些運算的時候,咱們就能夠用移位的方法來運算了,這樣會快不少。

例如:

n / 2 等價於 n >> 1

n / 4 等價於 n >> 2

n / 8 等價於 n >> 3。

這樣經過移位的運算在執行速度上是會比較快的,也能夠顯的你很厲害的樣子,哈哈。

還有一些 &(與)、|(或)的運算,也能夠加快運算的速度。例如判斷一個數是不是奇數,你可能會這樣作

if(n % 2 == 1){
    dosomething();
}複製代碼

不過咱們用與或運算的話會快不少。例如判斷是不是奇數,咱們就能夠把n和1相與了,若是結果爲1,則是奇數,不然就不會。即

if(n & 1 == 1){
    dosomething();
)複製代碼

五、設置哨兵位

在鏈表的相關問題中,咱們常常會設置一個頭指針,並且這個頭指針是不存任何有效數據的,只是爲了操做方便,這個頭指針咱們就能夠稱之爲哨兵位了。

例如咱們要刪除頭第一個節點是時候,若是沒有設置一個哨兵位,那麼在操做上,它會與刪除第二個節點的操做有所不一樣。可是咱們設置了哨兵,那麼刪除第一個節點和刪除第二個節點那麼在操做上就同樣了,不用作額外的判斷。固然,插入節點的時候也同樣。

有時候咱們在操做數組的時候,也是能夠設置一個哨兵的,把arr[0]做爲哨兵。例如,要判斷兩個相鄰的元素是否相等時,設置了哨兵就不怕越界等問題了,能夠直接arr[i] == arr[i-1]?了。不用怕i = 0時出現越界。

六、與遞歸相關的一些優化

(1)對於能夠遞歸的問題考慮狀態保存。

當咱們使用遞歸來解決一個問題時,很容易產生重複去算同一個子問題,這個時候咱們要考慮狀態保存以防止重複計算。

例:斐波那契數列

function fn(n){
    if(n <= 2){
        return 1;
    }else{
        return fn(n-1) + fn(n-2);
    }
}
console.log(fn(10));複製代碼

不過對於可使用遞歸解決的問題,咱們必定要考慮是否有不少重複計算。顯然對於 f(n) = f(n-1) + f(n-2) 的遞歸,是有不少重複計算的。如


就有不少重複計算了。這個時候咱們要考慮狀態保存。而且能夠自底向上。

function fn(n){
    var res = [];
    res[0] = 1;
    res[1] = 1;
    for(var i = 2; i < n; i++){
        res[i] = res[i-1] + res[i-2];
    }
    console.log(res[n-1]);
    return res[n-1];
}
fn(10);複製代碼

進一步優化:使用兩個變量。

function fn(n){
    var a = 1;
    var b = 1;
    for(var i = 3; i <= n; i++){
        a = a + b;
        b = a - b;
    }
    return a;
}
fn(10);複製代碼
相關文章
相關標籤/搜索