↓快轉到主要內容
  1. LeetCode/

LeetCode 327: Count of Range Sum

·2 分鐘· ·
LeetCode Hard Merge-Sort Divide-and-Conquer Prefix-Sum Binary-Indexed-Tree
Wei Yi Chung
作者
Wei Yi Chung
Working at the contributing of open source, distributed systems, and data engineering.
目錄

基本資料
#

難易度: hard 第一次嘗試:2026-08-01

  • 提交紀錄:AC(08-01)→ AC ×5(09-01)→ AC ×4(09-27)→ WA ×2 → AC(10-18)→ AC(10-19,改成左閉右開)

解題思路
#

要數有幾個子陣列和落在 [lower, upper]。

用前綴和 P,子陣列 [i, j) 的和是 P[j] - P[i],問題變成:

有幾對 i < j 滿足 lower <= P[j] - P[i] <= upper

這是「數 i < j 的 pair」的題型,對前綴和陣列做 merge sort 計數:合併兩半之前,對左半的每個 P[i],右半合法的 P[j] 是一段連續區間,用兩個只往右走的指針卡出左右邊界。

完整的推導寫在 Merge Sort 與分治計數 那篇。

我的練習紀錄
#

這題我重打了很多次:

  • 08-01:第一次寫,用閉區間 [start, end] 的寫法,一次 AC。
  • 09-01、09-27:同一份解法又重打、提交了 9 次。
  • 10-18:把參數改名成 (i, j),結果跟迴圈變數 i 撞名,連續 WA 兩次:
    • 遞迴寫成 countAndMergeSort(0, mid),左半的起點錯了。
    • 拿掉了 for i in range(...) 迴圈,只算了一個 i。
  • 10-19:照著 merge sort 練習梯子的寫法,改成左閉右開 [low, high),一次 AC。

參數名稱不要跟迴圈變數撞名,這種錯誤不會報錯,只會悄悄算錯。

正確解法(左閉右開)
#

class Solution:
    def countRangeSum(self, nums: List[int], lower: int, upper: int) -> int:
        n = len(nums)
        prefix = [0] * (n + 1)
        for i in range(n):
            prefix[i + 1] = prefix[i] + nums[i]

        def mergeAndSort(low: int, high: int) -> int:
            if high - low <= 1:
                return 0
            mid = (low + high) // 2
            count = mergeAndSort(low, mid) + mergeAndSort(mid, high)

            j1 = j2 = mid
            for i in range(low, mid):
                while j1 < high and prefix[j1] - prefix[i] < lower:
                    j1 += 1                       # 第一個 >= lower 的位置
                while j2 < high and prefix[j2] - prefix[i] <= upper:
                    j2 += 1                       # 第一個 > upper 的位置
                count += j2 - j1

            prefix[low:high] = sorted(prefix[low:high])
            return count

        return mergeAndSort(0, n + 1)

注意遞迴範圍是 [0, n + 1):前綴和陣列有 n + 1 個元素。

另一種解法:BIT + 離散化
#

從左到右掃前綴和,對每個 P[j] 查「前面有幾個 P[i] 落在 [P[j] - upper, P[j] - lower]」,再把 P[j] 加進 BIT。前綴和的值很大,要先離散化,查詢邊界用 bisect 換成 rank。

複雜度
#

時間 O(n log n),空間 O(n)。

要特別避免的錯誤
#

  • 參數名稱跟迴圈變數撞名。
  • 遞迴的左半起點寫成 0。
  • 前綴和陣列有 n + 1 個元素,左閉右開的遞迴範圍是 [0, n + 1)。
  • 上下界方向寫反:P[i] 的範圍是 [P[j] - upper, P[j] - lower],upper 在左邊。

面試口說整理
#

我先建前綴和,子陣列和就是兩個前綴和的差,問題變成數 i < j 且 lower <= P[j] - P[i] <= upper 的 pair。對前綴和做 merge sort:合併前兩半都已排序,對左半每個 P[i],右半合法的 P[j] 是一段連續區間,用兩個只往右的指針找出邊界相減。每層 O(n),共 O(n log n)。也可以用 BIT 加離散化。

相關文章

LeetCode 3548: Equal Sum Grid Partition II
·2 分鐘
LeetCode Hard Prefix-Sum Hash-Table Matrix Enumeration
LeetCode 3548 解題筆記:枚舉橫切(直切用轉置),兩邊差值要等於較大那邊某一格的值;那一邊只有一列或一欄時只能去掉兩端,否則任何一格都可以
LeetCode 2382: Maximum Segment Sum After Removals
·1 分鐘
LeetCode Hard Union-Find Prefix-Sum Ordered-Set Array
LeetCode 2382 解題筆記:倒著做,把刪除變成加回去;Union-Find 的根記區段總和,最大值只會變大
LeetCode 2302: Count Subarrays With Score Less Than K
·1 分鐘
LeetCode Hard Sliding-Window Prefix-Sum Binary-Search Array
LeetCode 2302 解題筆記:分數 = 總和 × 長度,都是正數,視窗越大分數越大;分數 >= k 就縮左邊,以 right 結尾的合法子陣列有 right - left + 1 個
LeetCode 2968: Apply Operations to Maximize Frequency Score
·1 分鐘
LeetCode Hard Sliding-Window Sorting Prefix-Sum Binary-Search Array
LeetCode 2968 解題筆記:排序後,把一段全部變成同一個值,最省的目標是中位數;用前綴和 O(1) 算花費,花費隨視窗變大而變大,可以滑動
LeetCode 2106: Maximum Fruits Harvested After at Most K Steps
·1 分鐘
LeetCode Hard Sliding-Window Prefix-Sum Binary-Search Array
LeetCode 2106 解題筆記:採到的一定是連續一段水果;走完 [L, R] 的最少步數是 (R - L) + min(|start - L|, |R - start|),在水果陣列上用滑動視窗
LeetCode 2102: Sequentially Ordinal Rank Tracker
·1 分鐘
LeetCode Hard Heap Design Two-Heaps
LeetCode 2102 解題筆記:雙 heap 維持「前 k 名」和「其他」,自訂 lt 處理字串的反向比較