JS和雙索引查找

前言

在很多算法題中,要求查找一個(gè)特定的內(nèi)容,可能是一個(gè)數(shù)字,也有可能是一個(gè)子串,或者是題目定義的一個(gè)滿足某個(gè)性質(zhì)的元素...這種題如果使用暴力搜索或者嵌套循環(huán),往往會(huì)超時(shí),所以我們需要一種更好的方法來降低時(shí)間復(fù)雜度:雙索引技術(shù)。

起源之二分法

這種查找方法的老祖宗,就是我們高中就學(xué)習(xí)過的二分法了。當(dāng)然,在大學(xué)的數(shù)據(jù)結(jié)構(gòu)和算法的課程里,它的地位也非常重要,只因?yàn)樗且粋€(gè)O(logn)的算法。
那它的雙索引是什么呢?
開始索引和結(jié)束索引。即查找范圍的始索引和終索引。
上代碼:

function binarySearch(arr, target) {
    var len = arr.length;
    // 在arr[l...r]之中查找target
    var l = 0,
        r = len - 1;
    while (l <= r) {
        //防止大數(shù)溢出,所以mid不是~~(l+r)/2
        var mid = l + ~~((r - l) / 2);
        if (arr[mid] == target)
            return mid;

        if (arr[mid] > target)
            r = mid - 1;
        else
            l = mid + 1;
    }

    return -1;
}

// 遞歸方式實(shí)現(xiàn)二分查找法

function __binarySearch2(arr, l,  r, target){

    if( l > r )
        return -1;

    var mid = l + ~~((r - l) / 2);
 
    if( arr[mid] === target )
        return mid;
    else if( arr[mid] > target )
        return __binarySearch2(arr, l, mid-1, target);
    else
        return __binarySearch2(arr, mid+1, r, target);

}

function binarySearch2(arr,target){
    var len = arr.length;
    return __binarySearch2( arr , 0 , len-1, target);
}

普通雙索引

以leetcode第283題為例:題目要求我們把非零元素全部排列在前面,0全部放到最后。
這時(shí)我們需要兩個(gè)索引:一個(gè)用來遍歷數(shù)組,一個(gè)用來計(jì)算非零元素的個(gè)數(shù)。
我們?cè)O(shè)第一個(gè)指針為i,第二個(gè)指針為k,數(shù)組遍歷[0...i]區(qū)間,最后得到的[0..k]區(qū)間里面全是非零元素,[k+1...i]是0


var moveZeroes = function (nums) {
    let k = 0;
    for (let i = 0; i < nums.length; i++) {
         if(nums[i]){
            nums[k++] = nums[i]; 
         }
     }
     for(let i = k;i<nums.length;i++){
         nums[i] = 0;
     }
    return nums;
};

優(yōu)化:交換非零元素和零元素即可

var moveZeroes = function (nums) {
    let k = 0;
    for (let i = 0; i < nums.length; i++) {
        if (nums[i]) {
            //防止極端情況:全是非零
            if(i !== k)
            [nums[k], nums[i]] = [nums[i], nums[k]];
            k++;
        }
    }
    return nums;
};

對(duì)撞指針

這是雙索引的一種特殊情況:即第一個(gè)索引在前,第二個(gè)索引在后,在程序運(yùn)行的過程中兩者不斷靠近,最后達(dá)到一個(gè)臨界范圍,程序結(jié)束。
以leetcode的167題為例:題目要求我們找出一個(gè)數(shù)組中相加為target的兩個(gè)數(shù),并返回他們的次序。
因?yàn)檩斎氲臄?shù)組是已經(jīng)排序好的,所以雙索引的最初指向分別為首和尾。
我們假設(shè)兩個(gè)指針為i和j,[i...j]范圍內(nèi)就是我們關(guān)注的,如果numbers[i]+numbers[j]等于target返回結(jié)果,小了就讓i右移,大了就讓j左移

var twoSum = function (numbers, target) {
    let i = 0, j = numbers.length - 1;
    while (i < j) {
        if (numbers[i] + numbers[j] === target) return [i + 1, j + 1]
        else if (numbers[i] + numbers[j] < target) i++
        else j--
    }
    return []
};

滑動(dòng)窗口

另一種雙索引的特殊情況就是我們需要讓兩個(gè)索引保持一定的相對(duì)距離,這種做法非常類似一個(gè)滑動(dòng)的窗口,在這個(gè)窗口中找到了答案就結(jié)束程序,沒有找到的話就滑動(dòng)之。
以leetcode的209題為例:給一個(gè)數(shù)組和一個(gè)target,找出數(shù)組中能夠相加得到target的最少元素的子數(shù)組。
這個(gè)題用暴力遍歷時(shí)間復(fù)雜度就是O(n3),就算優(yōu)化到O(n2)還是有點(diǎn)讓人接受不了,但是使用滑動(dòng)窗口解題的話,時(shí)間復(fù)雜度就變成了O(n)
我們假設(shè)兩個(gè)索引l和r,分別代表窗口的左索引和右索引,讓這個(gè)窗口的數(shù)字逐個(gè)相加看是否能得到答案,并且記錄長(zhǎng)度,因?yàn)槲覀兇鸢敢氖窃刈钌俚淖訑?shù)組。

var minSubArrayLen = function (s, nums) {
    let l = 0;
    let r = -1;//nums[l,r]為滑動(dòng)窗口
    let res = nums.length + 1;
    let sum = 0;
    while (l < nums.length) {
        if (r + 1 < nums.length && sum < s) {
            r++;
            sum += nums[r];
        }
        else {
            sum -= nums[l++];
        }
        if (sum >= s) res = Math.min(res, r - l + 1);
    }
    //res沒有更新,沒有結(jié)果
    if (res == nums.length + 1) return 0;
    return res;
};

在滑動(dòng)窗口中做記錄

這是滑動(dòng)窗口的加強(qiáng)版,就像上一道題需要記錄窗口長(zhǎng)度一樣,有時(shí)候我們還需要記錄其他的信息來幫助解題
以leetcode第3題為例:題目要求我們找出最長(zhǎng)的無重復(fù)字符的子串,我們除了需要一個(gè)滑動(dòng)窗口外還需要一個(gè)數(shù)據(jù)結(jié)構(gòu)用來存儲(chǔ)字符的出現(xiàn)次數(shù)(數(shù)組和哈希表都可以)
我們的滑動(dòng)窗口每次檢測(cè)到重復(fù)字符就移動(dòng),并且記錄窗口長(zhǎng)度,最后得出答案。

var lengthOfLongestSubstring = function (s) {
    let l = 0;
    let r = -1;//s[l,r]是滑動(dòng)窗口
    let rst = 0;
    //其實(shí)freq的狀態(tài)就是0和1
    let freq = new Array(256).fill(0);
    while (l < s.length) {
        if (r + 1 < s.length && freq[s[r + 1].charCodeAt()] === 0) {
            r++;
            freq[s[r].charCodeAt()]++;
        }
        //l和r所指的字母都一樣
        else {
            freq[s[l].charCodeAt()]--;
            l++;
        }
        rst = Math.max(rst, r - l + 1);
    }
    return rst;
};
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,076評(píng)論 25 708
  • LeetCode 刷題隨手記 - 第一部分 前 256 題(非會(huì)員),僅算法題,的吐槽 https://leetc...
    蕾娜漢默閱讀 17,909評(píng)論 2 36
  • 背景 一年多以前我在知乎上答了有關(guān)LeetCode的問題, 分享了一些自己做題目的經(jīng)驗(yàn)。 張土汪:刷leetcod...
    土汪閱讀 12,768評(píng)論 0 33
  • 我無力書寫,你對(duì)我的感受。因?yàn)槲覍?duì)你一無所知
    張弓長(zhǎng)lcve閱讀 159評(píng)論 0 0
  • 越來越多的人愛上分享自己的經(jīng)驗(yàn),有的是為了獲得粉絲數(shù),有的是為了將這個(gè)當(dāng)成自己創(chuàng)業(yè)的一個(gè)契機(jī),也有的人是想用輸出倒...
    君君Celia閱讀 788評(píng)論 0 1