前綴和與差分陣列
先花 O(n) 算好前綴和,之後任何區間的總和都只要一次減法;反過來,差分陣列讓「整段都加上一個數」也只要改兩格。
做什麼
3
8
1/13
a
- 90
- 31
- 22
- 13
- 34
- 55
- 16
- 97
- 68
- 79
- 710
- 411
prefix(比 a 多一格)
- 00
- 91
正在計算答案讀寫的格子區間
prefix[1] = prefix[0] + a[0] = 0 + 9 = 9:位置 1 之前所有數的總和。只建一次,一趟就完成。
亮起來的是這一步執行的程式碼
function buildPrefix(a: number[]): number[] { const prefix = [0]; for (const x of a) prefix.push(prefix[prefix.length - 1] + x); return prefix;} function rangeSum(prefix: number[], l: number, r: number): number { return prefix[r + 1] - prefix[l];} function applyRangeAdds(a: number[], updates: [number, number, number][]): number[] { const diff = new Array(a.length + 1).fill(0); for (let i = 0; i < a.length; i++) { diff[i] = a[i] - (i > 0 ? a[i - 1] : 0); } for (const [l, r, x] of updates) { diff[l] += x; diff[r + 1] -= x; } const out: number[] = []; let running = 0; for (let i = 0; i < a.length; i++) { running += diff[i]; out.push(running); } return out;}模型假設與範圍
- Big O 與實測步數以頁面列出的基本操作為單位;視覺化的快照、畫圖、程式高亮和輸出成本另計,不是執行時間 benchmark。
什麼時候用
- 資料不會變、但要問很多次「某一段的總和」:先建前綴和,之後每次 O(1)。
- 要對很多段一起加值、最後才看結果:差分陣列讓每次更新只改兩格,最後一趟還原。
- 資料會一直變、又要隨時查詢:前綴和撐不住,改用線段樹或樹狀陣列。
和其他主題的關係
- 由這些組成
- 資料結構 · 動態陣列
- 被這些用到
- 考題:接雨水
時間與空間複雜度(Big O)
| 操作 | 平均 | 最差 |
|---|---|---|
| 建前綴和 | O(n) | O(n) |
| 區間總和查詢 | O(1) | O(1) |
| 差分陣列:區間加值 | O(1) | O(1) |
| 差分陣列:還原成數值 所有更新做完後只做一次 | O(n) | O(n) |
| 改一個值後重建 常常改值的話改用線段樹 | O(n) | O(n) |
空間:O(n)
Big O 實測:n 變大時步數怎麼長
數的是:每次查詢或更新讀寫的格子數(1,000 個隨機區間平均)
| Big O | n = 1,000 | n = 4,000 | n = 16,000 | 成長倍數:實測(理論) | |
|---|---|---|---|---|---|
| 直接加總一個區間 | O(n) | 334 | 1,334 | 5,334 | ×16 (×16) |
| 前綴和查詢 | O(1) | 2 | 2 | 2 | ×1.0 (×1.0) |
| 差分陣列的區間加值 | O(1) | 2 | 2 | 2 | ×1.0 (×1.0) |
和其他做法比
| 直接加總 | 前綴和(建表+查詢) | 倍數 | |
|---|---|---|---|
| n = 1,000,1,000 次查詢 | 334,337 | 3,000 | ×111 |
| n = 100,000,100,000 次查詢 | 3,330,882,377 | 300,000 | ×11,103 |
隨機區間,數讀取的格子數。前綴和要先花 n 格建表,之後每次查詢只讀 2 格;查詢次數一多,建表的成本就攤掉了。值會變動的話前綴和得整個重建,那時改用線段樹或樹狀陣列。
真實世界裡的它
- 「和為 k 的子陣列」這類題目:前綴和加雜湊表,O(n) 數出所有符合的區間。
- 影像處理的積分圖(summed-area table):二維前綴和,任何矩形的總和都是四個數加減。
- 航班訂位、排程這類「一段時間內加 x」的批次更新:差分陣列。
取捨與陷阱
- 差一的錯誤:prefix 比原陣列多一格,a[l..r] 是 prefix[r+1] − prefix[l],不是 prefix[r] − prefix[l]。
- 總和可能溢位:大陣列的前綴和要用 64 位元整數(Java 的 long)。
- 差分陣列也要多一格,r + 1 才不會越界。
LeetCode 練習
- 1480.Running Sum of 1d ArrayEasy最基本的前綴和(在新分頁開啟 LeetCode)
- 303.Range Sum Query - ImmutableEasy先算前綴和,之後每次查詢 O(1)(在新分頁開啟 LeetCode)
- 560.Subarray Sum Equals KMedium前綴和加雜湊表數符合的區間(在新分頁開啟 LeetCode)
- 1109.Corporate Flight BookingsMedium差分陣列:整段加值只改兩格(在新分頁開啟 LeetCode)
- 1094.Car PoolingMedium差分陣列檢查任何時刻是否超載(在新分頁開啟 LeetCode)