給定一個整數陣列 nums,實作 NumArray 類別:
NumArray(int[] nums) — 以整數陣列 nums 初始化物件int sumRange(int left, int right) — 回傳 nums[left] 到 nums[right](含兩端)的元素總和sumRange 可能被呼叫非常多次,因此需要 O(1) 時間回答每次查詢。
若每次查詢都從 left 加到 right,時間複雜度為 O(n),對大量查詢效率極差。
前綴和是解決區間求和問題的經典技巧:
預先建立陣列 prefix,其中 prefix[i] 代表 nums[0] + nums[1] + ... + nums[i-1](即前 i 個元素的總和)。特別地,prefix[0] = 0(空前綴)。
建構方式:
prefix[0] = 0
prefix[1] = nums[0]
prefix[2] = nums[0] + nums[1]
...
prefix[i] = prefix[i-1] + nums[i-1]
查詢區間 [left, right] 的總和:
sumRange(left, right) = prefix[right+1] - prefix[left]
直觀解釋:prefix[right+1] 是從頭到 right 的總和,prefix[left] 是從頭到 left-1 的總和,兩者相減恰好得到 [left, right] 的總和。
這樣預處理只需 O(n) 時間,之後每次查詢只需一次減法,達到 O(1)。
NumArray:O(n),需要遍歷整個陣列一次來建立前綴和陣列。sumRange 查詢:O(1),每次查詢只需一次陣列存取與一次減法運算。若共有 q 次查詢,總時間為 O(n + q),而暴力法為 O(n × q),差距非常顯著。
O(n),額外儲存長度為 n+1 的前綴和陣列。
Constructor NumArray(nums): 1. Create prefix array of size n+1, initialized to 0 2. For i from 0 to n-1: a. prefix[i+1] = prefix[i] + nums[i] sumRange(left, right): 1. Return prefix[right+1] - prefix[left]
每次 sumRange(left, right) 都從 left 累加到 right。
預先計算所有 (left, right) 組合的答案,存在二維陣列中。
建立線段樹支援區間查詢。
如果陣列允許更新怎麼辦? 若 nums[i] 可以被修改,前綴和方案每次更新需要 O(n) 時間重新計算。可以使用二元索引樹(Binary Indexed Tree / Fenwick Tree),讓更新和查詢都達到 O(log n)。這就是 LC 307 Range Sum Query - Mutable 的題目。
如何推廣到二維矩陣? 將一維前綴和推廣為二維前綴和(2D Prefix Sum),可以 O(1) 回答任意子矩陣的元素總和。這就是 LC 304 Range Sum Query 2D - Immutable 的題目。
前綴和能否用於非靜態問題? 前綴和的本質是「預處理換查詢時間」,只適合靜態資料。對於需要頻繁修改的場景,應考慮線段樹(Segment Tree)或樹狀陣列(BIT),它們能在修改與查詢之間取得更好的平衡。