那些小而美的算法技巧:前綴和/差分?jǐn)?shù)組

讀完本文胶哲,你可以去力扣拿下如下題目:

560.和為K的子數(shù)組

-----------

今天來聊一道簡單卻十分巧妙的算法問題:算出一共有幾個和為 k 的子數(shù)組陡叠。

image

那我把所有子數(shù)組都窮舉出來,算它們的和身隐,看看誰的和等于 k 不就行了。

關(guān)鍵是,如何快速得到某個子數(shù)組的和呢,比如說給你一個數(shù)組 nums丁稀,讓你實現(xiàn)一個接口 sum(i, j),這個接口要返回 nums[i..j] 的和倚聚,而且會被多次調(diào)用线衫,你怎么實現(xiàn)這個接口呢?

因為接口要被多次調(diào)用惑折,顯然不能每次都去遍歷 nums[i..j]授账,有沒有一種快速的方法在 O(1) 時間內(nèi)算出 nums[i..j] 呢?這就需要前綴和技巧了惨驶。

PS:我認(rèn)真寫了 100 多篇原創(chuàng)白热,手把手刷 200 道力扣題目,全部發(fā)布在 labuladong的算法小抄粗卜,持續(xù)更新屋确。建議收藏,按照我的文章順序刷題休建,掌握各種算法套路后投再入題海就如魚得水了乍恐。

一、什么是前綴和

前綴和的思路是這樣的测砂,對于一個給定的數(shù)組 nums茵烈,我們額外開辟一個前綴和數(shù)組進(jìn)行預(yù)處理:

int n = nums.length;
// 前綴和數(shù)組
int[] preSum = new int[n + 1];
preSum[0] = 0;
for (int i = 0; i < n; i++)
    preSum[i + 1] = preSum[i] + nums[i];
image

這個前綴和數(shù)組 preSum 的含義也很好理解,preSum[i] 就是 nums[0..i-1] 的和砌些。那么如果我們想求 nums[i..j] 的和呜投,只需要一步操作 preSum[j+1]-preSum[i] 即可,而不需要重新去遍歷數(shù)組了存璃。

回到這個子數(shù)組問題仑荐,我們想求有多少個子數(shù)組的和為 k,借助前綴和技巧很容易寫出一個解法:

int subarraySum(int[] nums, int k) {
    int n = nums.length;
    // 構(gòu)造前綴和
    int[] sum = new int[n + 1];
    sum[0] = 0; 
    for (int i = 0; i < n; i++)
        sum[i + 1] = sum[i] + nums[i];
    
    int ans = 0;
    // 窮舉所有子數(shù)組
    for (int i = 1; i <= n; i++)
        for (int j = 0; j < i; j++)
            // sum of nums[j..i-1]
            if (sum[i] - sum[j] == k)
                ans++;

    return ans;
}

這個解法的時間復(fù)雜度 O(N^2) 空間復(fù)雜度 O(N)纵东,并不是最優(yōu)的解法粘招。不過通過這個解法理解了前綴和數(shù)組的工作原理之后,可以使用一些巧妙的辦法把時間復(fù)雜度進(jìn)一步降低偎球。

二洒扎、優(yōu)化解法

前面的解法有嵌套的 for 循環(huán):

for (int i = 1; i <= n; i++)
    for (int j = 0; j < i; j++)
        if (sum[i] - sum[j] == k)
            ans++;

第二層 for 循環(huán)在干嘛呢?翻譯一下就是衰絮,在計算袍冷,有幾個 j 能夠使得 sum[i]sum[j] 的差為 k。毎找到一個這樣的 j猫牡,就把結(jié)果加一胡诗。

我們可以把 if 語句里的條件判斷移項,這樣寫:

if (sum[j] == sum[i] - k)
    ans++;

優(yōu)化的思路是:我直接記錄下有幾個 sum[j]sum[i] - k 相等,直接更新結(jié)果煌恢,就避免了內(nèi)層的 for 循環(huán)骇陈。我們可以用哈希表,在記錄前綴和的同時記錄該前綴和出現(xiàn)的次數(shù)瑰抵。

int subarraySum(int[] nums, int k) {
    int n = nums.length;
    // map:前綴和 -> 該前綴和出現(xiàn)的次數(shù)
    HashMap<Integer, Integer> 
        preSum = new HashMap<>();
    // base case
    preSum.put(0, 1);

    int ans = 0, sum0_i = 0;
    for (int i = 0; i < n; i++) {
        sum0_i += nums[i];
        // 這是我們想找的前綴和 nums[0..j]
        int sum0_j = sum0_i - k;
        // 如果前面有這個前綴和缩歪,則直接更新答案
        if (preSum.containsKey(sum0_j))
            ans += preSum.get(sum0_j);
        // 把前綴和 nums[0..i] 加入并記錄出現(xiàn)次數(shù)
        preSum.put(sum0_i, 
            preSum.getOrDefault(sum0_i, 0) + 1);
    }
    return ans;
}

比如說下面這個情況,需要前綴和 8 就能找到和為 k 的子數(shù)組了谍憔,之前的暴力解法需要遍歷數(shù)組去數(shù)有幾個 8,而優(yōu)化解法借助哈希表可以直接得知有幾個前綴和為 8主籍。

image

這樣习贫,就把時間復(fù)雜度降到了 O(N),是最優(yōu)解法了千元。

PS:我認(rèn)真寫了 100 多篇原創(chuàng)苫昌,手把手刷 200 道力扣題目,全部發(fā)布在 labuladong的算法小抄幸海,持續(xù)更新祟身。建議收藏,按照我的文章順序刷題物独,掌握各種算法套路后投再入題海就如魚得水了袜硫。

三、總結(jié)

前綴和不難挡篓,卻很有用婉陷,主要用于處理數(shù)組區(qū)間的問題。

比如說官研,讓你統(tǒng)計班上同學(xué)考試成績在不同分?jǐn)?shù)段的百分比秽澳,也可以利用前綴和技巧:

int[] scores; // 存儲著所有同學(xué)的分?jǐn)?shù)
// 試卷滿分 150 分
int[] count = new int[150 + 1]
// 記錄每個分?jǐn)?shù)有幾個同學(xué)
for (int score : scores)
    count[score]++
// 構(gòu)造前綴和
for (int i = 1; i < count.length; i++)
    count[i] = count[i] + count[i-1];

這樣,給你任何一個分?jǐn)?shù)段戏羽,你都能通過前綴和相減快速計算出這個分?jǐn)?shù)段的人數(shù)担神,百分比也就很容易計算了。

但是始花,稍微復(fù)雜一些的算法問題妄讯,不止考察簡單的前綴和技巧。比如本文探討的這道題目衙荐,就需要借助前綴和的思路做進(jìn)一步的優(yōu)化捞挥,借助哈希表去除不必要的嵌套循環(huán)∮且鳎可見對題目的理解和細(xì)節(jié)的分析能力對于算法的優(yōu)化是至關(guān)重要的砌函。

希望本文對你有幫助。

_____________

我的 在線電子書 有 100 篇原創(chuàng)文章,手把手帶刷 200 道力扣題目讹俊,建議收藏垦沉!對應(yīng)的 GitHub 算法倉庫 已經(jīng)獲得了 70k star,歡迎標(biāo)星仍劈!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末厕倍,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子贩疙,更是在濱河造成了極大的恐慌讹弯,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,590評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件这溅,死亡現(xiàn)場離奇詭異组民,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)悲靴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,157評論 3 399
  • 文/潘曉璐 我一進(jìn)店門臭胜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人癞尚,你說我怎么就攤上這事耸三。” “怎么了浇揩?”我有些...
    開封第一講書人閱讀 169,301評論 0 362
  • 文/不壞的土叔 我叫張陵仪壮,是天一觀的道長。 經(jīng)常有香客問我胳徽,道長睛驳,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,078評論 1 300
  • 正文 為了忘掉前任膜廊,我火速辦了婚禮乏沸,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘爪瓜。我一直安慰自己蹬跃,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 69,082評論 6 398
  • 文/花漫 我一把揭開白布铆铆。 她就那樣靜靜地躺著蝶缀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪薄货。 梳的紋絲不亂的頭發(fā)上翁都,一...
    開封第一講書人閱讀 52,682評論 1 312
  • 那天,我揣著相機(jī)與錄音谅猾,去河邊找鬼柄慰。 笑死鳍悠,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的坐搔。 我是一名探鬼主播藏研,決...
    沈念sama閱讀 41,155評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼概行!你這毒婦竟也來了蠢挡?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 40,098評論 0 277
  • 序言:老撾萬榮一對情侶失蹤凳忙,失蹤者是張志新(化名)和其女友劉穎业踏,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體涧卵,經(jīng)...
    沈念sama閱讀 46,638評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡堡称,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,701評論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了艺演。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,852評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡桐臊,死狀恐怖胎撤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情断凶,我是刑警寧澤伤提,帶...
    沈念sama閱讀 36,520評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站认烁,受9級特大地震影響肿男,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜却嗡,卻給世界環(huán)境...
    茶點故事閱讀 42,181評論 3 335
  • 文/蒙蒙 一其弊、第九天 我趴在偏房一處隱蔽的房頂上張望几蜻。 院中可真熱鬧,春花似錦、人聲如沸愉耙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,674評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芦圾。三九已至,卻和暖如春帝牡,著一層夾襖步出監(jiān)牢的瞬間往毡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,788評論 1 274
  • 我被黑心中介騙來泰國打工靶溜, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留开瞭,地道東北人懒震。 一個月前我還...
    沈念sama閱讀 49,279評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像惩阶,于是被迫代替她去往敵國和親挎狸。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,851評論 2 361

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