← 題庫 / Archive
2026-04-23 Daily Medium ArrayHash TablePrefix Sum

2615. Sum of Distances

題目 / Problem

中文: 給定一個 0-indexed 整數陣列 nums。定義一個長度相同的陣列 arr,其中 arr[i] 等於「所有滿足 nums[j] == nums[i]j != i 的下標 j」的 |i - j| 總和。若找不到這樣的 j,則 arr[i] = 0。請回傳 arr

English: You are given a 0-indexed integer array nums. Build arr of the same length where arr[i] is the sum of |i - j| over every index j satisfying nums[j] == nums[i] and j != i. If no such j exists, set arr[i] = 0. Return arr.

Constraints - 1 <= nums.length <= 10^5 - 0 <= nums[i] <= 10^9

Example: nums = [1,3,1,1,2][5,0,3,4,0]. 對於 i = 0,與它相同的下標有 2, 3,所以 arr[0] = |0-2| + |0-3| = 2 + 3 = 5。 For i = 0, the matching indices are 2, 3, giving |0-2| + |0-3| = 5.

名詞解釋 / Glossary

  • 雜湊表 / hash map (unordered_map): 把「鍵 key」映射到「值 value」的資料結構,平均 O(1) 查詢。這裡用它把「數值 → 出現過的所有下標」分組。A key→value container with average O(1) lookup. We use it to bucket indices by the value they hold.
  • 分組 / grouping: 只有相同數值的下標會互相影響答案,因此先把它們分成一組再處理。Only indices sharing the same value contribute to each other's answer, so we process each value's indices together.
  • 前綴和 / prefix sum: 一個陣列累計到位置 m 為止的總和。用它可以用 O(1) 查出「一段區間的總和」,避免重複加總。A running total up through position m; lets us compute any segment's sum in O(1) instead of re-scanning.
  • qsort (C): C 標準函式庫的排序函式,需要自訂比較函式。The C standard library's sort; you supply a comparator function that returns negative / zero / positive.
  • 結構體 / struct: C 裡把多個欄位綁成一個型別。這裡用來一起儲存「值 + 原本的下標」。Groups multiple fields into one type; we use it to keep each value together with its original index during sorting.
  • long long: 64 位整數型別。最長下標總距離可達 10^5 × 10^5 = 10^10,會超過 32 位 int,所以必須升格。A 64-bit integer type; the total distance can reach 10^10, which overflows 32-bit int.
  • Range-for / 結構化綁定 (C++): for (auto& [k, v] : map) 一次解開 pair 的兩個欄位。auto& 避免拷貝。Iterates a map while unpacking each entry into named variables; auto& avoids copying.

思路

暴力作法是對每個 i 再掃一次整個陣列找相同值的 j,是 O(n²),在 n = 10^5 時會超時。觀察到只有「值相同」的下標之間才會互相貢獻,所以先把相同值的下標歸成一組,各組之間獨立處理。對某一組已排序好的下標 p_0 < p_1 < … < p_{k-1},位置 m 的答案等於「它與左邊所有下標的距離和」加「它與右邊所有下標的距離和」。因為組內是遞增的,左邊那部分等於 m * p_m - (p_0 + p_1 + … + p_{m-1})(把 mp_m 減去左邊總和),右邊那部分等於 (p_{m+1} + … + p_{k-1}) - (k-1-m) * p_m(右邊總和減去 k-1-mp_m)。我們維護整組的 total 以及一邊掃一邊累加的 leftSum,rightSum = total - leftSum - p_m 就自動得到,不需要另建陣列。總和最多約 10^10,必須用 long long 避免溢位。

A brute force scan of all j for each i is O(n²) and will time out at n = 10^5. The key observation is that only indices with the same value contribute to each other, so we bucket the indices by value and handle each bucket independently. Within a single bucket the indices are naturally sorted (we encounter them in left-to-right order). For the m-th index p_m of a bucket of size k, its answer splits into a left part and a right part: the left part is m * p_m - (p_0 + … + p_{m-1}) (we subtract m copies of p_m minus the sum of indices before it), and the right part is (p_{m+1} + … + p_{k-1}) - (k - 1 - m) * p_m. We keep a rolling leftSum as we sweep through the bucket, derive rightSum = total - leftSum - p_m on the fly, and avoid a second pass. The worst-case distance total is about 10^10, which overflows 32-bit integers, so everything must be computed in long long.

逐步走查 / Walkthrough

Input: nums = [1, 3, 1, 1, 2].

分組 / Group by value: - value 1 → indices [0, 2, 3] - value 3 → indices [1] - value 2 → indices [4]

處理組 1,k = 3,total = 0 + 2 + 3 = 5:

m pm (index) leftSum before rightSum = total − leftSum − pm left = m·pm − leftSum right = rightSum − (k−1−m)·pm ans[pm] leftSum after
0 0 0 5 − 0 − 0 = 5 0·0 − 0 = 0 5 − 2·0 = 5 5 0
1 2 0 5 − 0 − 2 = 3 1·2 − 0 = 2 3 − 1·2 = 1 3 2
2 3 2 5 − 2 − 3 = 0 2·3 − 2 = 4 0 − 0·3 = 0 4 5

處理組 3 和組 2: 每組只有一個下標,k = 1,左邊與右邊都是空的,所以 ans[1] = 0ans[4] = 0。Groups with only one index trivially produce 0.

最終 / Final: arr = [5, 0, 3, 4, 0]

Solution — C

// 演算法:把相同值的下標分在一組(用 qsort 排序 {值, 下標} 配對後,
// 相同值會自然相鄰)。再對每組用前綴和,讓位置 m 的答案在 O(1) 內算出。
// Algorithm: sort {value, index} pairs so equal values are adjacent, then for
// each value-group sweep once using a running leftSum to get each answer in O(1).

#include <stdlib.h>   // 為了 malloc / calloc / free / qsort / for malloc, calloc, free, qsort

// 把「值 + 原下標」綁成一個型別,方便一起排序 / group value and original index together for sorting
typedef struct {
    int val;   // nums[i] 的數值 / the value
    int idx;   // 它在 nums 中的原始下標 / its original position in nums
} Pair;

// qsort 的比較函式:先按 val,再按 idx(idx 其實本來就遞增,但寫出來更安全)
// Comparator for qsort: order by val, then by idx (indices are already in order,
// but specifying it makes behavior deterministic).
static int cmp(const void *a, const void *b) {
    const Pair *pa = (const Pair*)a;   // 轉回 Pair 指標 / cast void* back to Pair*
    const Pair *pb = (const Pair*)b;
    if (pa->val != pb->val)
        // 不能直接 return pa->val - pb->val(可能溢位);用比較結果 -1/0/1 / avoid subtraction overflow
        return (pa->val > pb->val) - (pa->val < pb->val);
    return (pa->idx > pb->idx) - (pa->idx < pb->idx);
}

// LeetCode 指定的函式簽名:回傳 long long 陣列,並透過 *returnSize 回傳長度
// LeetCode-required signature: returns a heap-allocated long long array and writes length into *returnSize.
long long* distance(int* nums, int numsSize, int* returnSize) {
    *returnSize = numsSize;                                                      // 設定回傳長度 / set output length

    // calloc 會把每個元素初始化為 0,單元素組直接就是正確答案 / calloc zero-fills, which is already
    // correct for groups of size 1 (they never get overwritten).
    long long *ans = (long long*)calloc((size_t)numsSize, sizeof(long long));

    // 為每個下標建立一個 Pair,準備排序 / build one Pair per index, ready to sort
    Pair *arr = (Pair*)malloc(sizeof(Pair) * (size_t)numsSize);
    for (int i = 0; i < numsSize; i++) {
        arr[i].val = nums[i];  // 複製值 / copy the value
        arr[i].idx = i;        // 記住原本位置 / remember the original index
    }

    // 排序後,相同 val 的 Pair 會排在連續區段 / after sorting, equal values form contiguous runs
    qsort(arr, (size_t)numsSize, sizeof(Pair), cmp);

    // 用兩個指標 l、r 掃出每一段「相同值」的區間 [l, r) / sweep contiguous runs of equal values
    int l = 0;
    while (l < numsSize) {
        int r = l;
        // 把 r 推到下一個不同值的位置 / advance r past all equal values
        while (r < numsSize && arr[r].val == arr[l].val) r++;

        int k = r - l;             // 本組的元素個數 / size of this value-group

        // 先算整組下標總和 total / first compute the total of all indices in this group
        long long total = 0;
        for (int i = l; i < r; i++) total += arr[i].idx;

        // leftSum 在掃的過程中累加:目前位置左邊所有下標的和 / running sum of indices already visited
        long long leftSum = 0;
        for (int i = l; i < r; i++) {
            int m = i - l;                      // m 是在組內的位置(從 0 算起)/ position within the group
            long long pm = arr[i].idx;          // 這個元素的原下標 / original index of current element
            // 右邊的總和就是總和扣掉左邊與自己 / right-side sum is what remains after removing leftSum and pm
            long long rightSum = total - leftSum - pm;
            // 左邊貢獻:m 個 (pm - p_j) 加起來 = m*pm - leftSum / left part: m*pm minus indices before me
            long long left  = (long long)m * pm - leftSum;
            // 右邊貢獻:(k-1-m) 個 (p_j - pm) 加起來 = rightSum - (k-1-m)*pm / right part
            long long right = rightSum - (long long)(k - 1 - m) * pm;
            ans[arr[i].idx] = left + right;     // 寫回原本下標的位置 / write to the original slot
            leftSum += pm;                      // 下一輪前把自己併入 leftSum / fold current index into leftSum
        }

        l = r;   // 跳到下一組 / jump to next group
    }

    free(arr);   // 歸還 Pair 陣列的記憶體 / release the Pair buffer
    return ans;  // ans 由呼叫端釋放(LeetCode 的慣例)/ caller frees ans, per LeetCode convention
}

Solution — C++

// 演算法:用 unordered_map 把「值 → 該值出現的所有下標」分組,再對每組用
// 前綴和掃一遍,每個位置 O(1) 取得答案。時間與空間皆為 O(n) 平均。
// Algorithm: bucket indices by value with an unordered_map, then sweep each
// bucket once using a running leftSum. Average O(n) time and space.

#include <vector>
#include <unordered_map>
using namespace std;

class Solution {
public:
    // LeetCode 要求成員函式名為 distance,回傳 vector<long long>
    // LeetCode requires a member function named distance returning vector<long long>.
    vector<long long> distance(vector<int>& nums) {
        int n = (int)nums.size();                // 陣列長度 / problem size
        vector<long long> ans(n, 0);             // 預設 0,單元素組就是正確答案 / defaults cover size-1 groups

        // unordered_map<int, vector<int>>:鍵是 nums 的值,值是它出現過的下標列表
        // Hash map from a value in nums to the list of indices where it appears.
        unordered_map<int, vector<int>> groups;
        groups.reserve(n);                       // 預留桶數,減少重新雜湊 / avoid rehashing as we insert

        // 一次掃過 nums,把下標 i 推到對應值的 vector 尾端 / one pass to bucket indices
        for (int i = 0; i < n; i++) {
            groups[nums[i]].push_back(i);        // 若 key 不存在會自動新建空 vector / default-inserts empty vector
        }

        // Range-for 搭配結構化綁定:直接把 pair<const int, vector<int>> 拆成 (val, idxs)
        // auto& 代表取參考(不拷貝),我們只讀不改,其實用 const auto& 也可以
        // Range-for with structured bindings unpacks each map entry into named vars;
        // auto& avoids copying the vector inside each entry.
        for (auto& [val, idxs] : groups) {
            (void)val;                           // 我們用不到 val 本身,只需要下標列表 / val unused; suppress warning
            int k = (int)idxs.size();            // 本組元素個數 / size of this bucket

            // idxs 內的下標本來就是遞增的(我們按照 i 從 0 到 n-1 推入)
            // Indices inside idxs are already sorted because we pushed i in order.
            long long total = 0;
            for (int x : idxs) total += x;       // 計算整組下標總和 / sum of all indices in this bucket

            long long leftSum = 0;               // 位置 m 左邊所有下標之和 / running sum of earlier indices
            for (int m = 0; m < k; m++) {
                long long pm = idxs[m];                                  // 目前元素的原下標 / current index
                long long rightSum = total - leftSum - pm;               // 右邊之和 = 全部 - 左邊 - 自己 / right sum
                long long left  = (long long)m * pm - leftSum;           // 左邊距離和 / distance sum to the left
                long long right = rightSum - (long long)(k - 1 - m) * pm;// 右邊距離和 / distance sum to the right
                ans[idxs[m]] = left + right;                             // 寫回答案 / write answer to original slot
                leftSum += pm;                                           // 併入 leftSum,給下一個 m 使用 / update rolling sum
            }
        }

        return ans;   // 回傳答案 vector / return the answer vector
    }
};

複雜度 / Complexity

  • Time — C: O(n log n)qsort 的排序步驟主導;後續掃描每個元素只被訪問一次。The sort dominates; the subsequent sweep visits each element exactly once.
  • Time — C++: O(n) average。unordered_map 插入與查詢平均 O(1),後續對每組做線性掃。Hash map insert/lookup is average O(1), and the sweep is linear.
  • Space: O(n) — 回傳陣列本身 O(n),加上 Pair 陣列(C)或哈希桶(C++)各 O(n)。The answer array plus the auxiliary pair buffer (C) or map buckets (C++) are each O(n).

Here n = nums.length

Pitfalls & Edge Cases

  • 整數溢位 / Integer overflow: 最多 n = 10^5 個下標、每個差 < 10^5,總和可達 10^10,超過 32 位 int。全程使用 long long(C)或 long long(C++)。必要的乘法 (long long)m * pm 要先轉型,否則乘法本身就先溢位了。Distances can reach 10^10; always use long long, and cast operands before multiplication ((long long)m * pm), not after.
  • 單一出現的值 / Singletons: 只出現一次的值應得 0。我們用 calloc / vector<long long> ans(n, 0) 預填 0,單元素組在公式中 m = 0k - 1 - m = 0leftSum = rightSum = 0,算出來也是 0,不需特判。ans is zero-initialized, and the formula naturally yields 0 for size-1 groups.
  • 比較函式寫法 / Comparator subtraction: 在 C 的 qsort 裡絕不能寫 return pa->val - pb->val,因為 val 可達 10^9,相減可能溢位。改用 (a > b) - (a < b)。Never subtract in a qsort comparator when values can be large; use (a>b)-(a<b).
  • 回傳長度 / Return length (C): 忘記寫 *returnSize = numsSize 會讓 LeetCode 判題以為你回傳空陣列,拿到 Wrong Answer。Forgetting *returnSize is a very common source of WA in LeetCode C.
  • 保持下標順序 / Preserving index order within a group: 我們依賴「組內下標遞增」才能套用 m * pm − leftSum 公式。C 透過 qsort 的穩定排序式比較器 (先 val 再 idx) 保證,C++ 則靠「按 i 從小到大 push_back」自然有序。The left-part formula requires each group to be in ascending index order; we preserve that by tie-breaking on idx (C) or by insertion order (C++).