228. Summary Ranges
題目 / Problem
中文
給你一個已排序且元素皆唯一的整數陣列 nums。
一個範圍 [a,b] 表示從 a 到 b(含兩端)的所有整數。
請回傳能剛好覆蓋陣列中所有數字的最小、且已排序的範圍清單。也就是說:nums 中每個元素都被恰好一個範圍覆蓋,而且範圍裡不能出現任何「不在 nums 裡」的整數。
每個範圍 [a,b] 的輸出格式:
- 若 a != b,輸出 "a->b"
- 若 a == b,輸出 "a"
English
You are given a sorted, unique integer array nums.
A range [a,b] is the set of all integers from a to b inclusive.
Return the smallest sorted list of ranges that cover all the numbers in the array exactly — every element is covered by exactly one range, and no integer outside nums appears inside any range.
Each range [a,b] is printed as:
- "a->b" if a != b
- "a" if a == b
Constraints / 約束
- 0 <= nums.length <= 20
- -2^31 <= nums[i] <= 2^31 - 1
- All values are unique / 所有值皆唯一
- nums is sorted ascending / nums 已升序排序
Worked example / 範例
Input: nums = [0,1,2,4,5,7]
Output: ["0->2","4->5","7"]
解釋:0,1,2 連續成一段 → "0->2";4,5 連續成一段 → "4->5";7 自成一段 → "7"。
名詞解釋 / Glossary
- 連續區段 / Consecutive run:陣列中相鄰且差值剛好為 1 的一串數字(例如
0,1,2)。本題就是把陣列切成這些區段。 / A maximal stretch of numbers where each is exactly 1 more than the previous (e.g.0,1,2). The whole task is splitting the array into these runs. - 指標 / Pointer (index):一個整數變數,用來記住「目前看到陣列的哪個位置」,例如
i、start。 / An integer variable that remembers which array position you are currently looking at, e.g.i,start. - 不變量 / Invariant:在迴圈每一輪都保持為真的性質,幫助我們確信程式正確。本題的不變量是:
start永遠指向「當前區段的第一個元素」。 / A property that stays true on every loop iteration, giving confidence the logic is correct. Here:startalways points to the first element of the current run. - 動態陣列 / Dynamic array:大小可在執行時增長的陣列。C 用
malloc配置記憶體;C++ 用std::vector自動管理。 / An array whose size can grow at runtime. In C you allocate withmalloc; in C++std::vectorhandles it automatically. malloc/free:C 的記憶體配置與釋放函式。malloc(n)向系統要n個位元組並回傳指標;用完要記得釋放(本題回傳給 LeetCode,故不釋放)。 / C functions to request (malloc) and release (free) heap memory.malloc(n)returns a pointer tonbytes.snprintf:C 的安全字串格式化函式,把數字等內容寫進固定大小的字元緩衝區,並保證不會寫超過界線。 / A safe C function that formats values (like numbers) into a fixed-size character buffer without overflowing it.std::to_string:C++ 把整數轉成字串的函式,例如to_string(42)得到"42"。 / A C++ function that converts an integer to its text form, e.g.to_string(42)→"42".
思路
最直覺的「暴力」想法是:把陣列裡每個數字單獨看成一段,輸出一堆單一數字。但題目要求「最小」的清單,所以連續的數字必須合併成一段,這種暴力法不符合要求。關鍵觀察是:因為陣列已經排序且唯一,連續區段就等於「相鄰兩數差恰好為 1」的一串數字。於是我們只要一次掃描陣列,用一個指標 start 記住「目前這段的起點」,然後不斷往後走,只要下一個數字仍然等於前一個加 1,就代表還在同一段裡;一旦發現「下一個數字不等於前一個加 1」或「走到陣列盡頭」,就表示當前段在此結束,我們把 nums[start] 到當前數字輸出成一個範圍,再把 start 移到下一個位置開始新的一段。輸出時只要判斷段頭和段尾是否相同:相同就印單一數字,不同就印 "頭->尾"。整個過程只掃一遍,且維持不變量「start 永遠指向當前段第一個元素」,所以正確又高效。
The brute-force idea is to emit every number as its own one-element range, but the problem demands the smallest list, so consecutive numbers must be merged — brute force fails that requirement. The key observation is that since the array is sorted and unique, a consecutive run is exactly a stretch where each neighbor differs by 1. So we do a single pass: a pointer start marks where the current run began, and we advance as long as the next value equals the previous value plus 1, meaning we are still inside the same run. The moment the next value breaks that "+1" chain (or we hit the end of the array), the current run is complete: we output the range from nums[start] to the current value, then reset start to the next position to begin a fresh run. When emitting, we compare the run's first and last element: equal → print a single number; different → print "first->last". One pass, with the invariant "start always points at the first element of the current run," makes it both correct and efficient.
逐步走查 / Walkthrough
Input / 輸入:nums = [0,1,2,4,5,7],長度 6。
We walk i from 0 to n-1; start marks the run's beginning. 當 i 是最後一個,或 nums[i]+1 != nums[i+1] 時,就結束當前段。
| i | nums[i] | start (run 起點) | 結束條件? / Run ends here? | 動作 / Action |
|---|---|---|---|---|
| 0 | 0 | 0 | nums[1]=1 == 0+1 → 不結束 |
繼續 / keep going |
| 1 | 1 | 0 | nums[2]=2 == 1+1 → 不結束 |
繼續 / keep going |
| 2 | 2 | 0 | nums[3]=4 != 2+1 → 結束 |
段 = nums[0..2] = 0..2,頭≠尾 → 輸出 "0->2";start = 3 |
| 3 | 4 | 3 | nums[4]=5 == 4+1 → 不結束 |
繼續 / keep going |
| 4 | 5 | 3 | nums[5]=7 != 5+1 → 結束 |
段 = nums[3..4] = 4..5,頭≠尾 → 輸出 "4->5";start = 5 |
| 5 | 7 | 5 | i 是最後一個 → 結束 |
段 = nums[5..5] = 7..7,頭=尾 → 輸出 "7";start = 6 |
最終結果 / Final result:["0->2", "4->5", "7"] ✓
Solution — C
/*
* 演算法 / Algorithm:
* 單次掃描,用 start 記住當前連續區段的起點。 / Single pass; `start` marks the run's start.
* 當下一個數不等於目前數+1(或到陣列末端)時,區段結束,輸出一個範圍。
* When the next value breaks the +1 chain (or we hit the end), close the run and emit it.
*/
// LeetCode 要求回傳一個字串陣列,並用 *returnSize 告訴呼叫端共有幾個字串。
// LeetCode wants an array of strings; *returnSize reports how many strings there are.
char** summaryRanges(int* nums, int numsSize, int* returnSize) {
// 空陣列:沒有任何範圍,回傳數量設為 0,並回傳 NULL。
// Empty array: zero ranges, set count to 0 and return NULL.
if (numsSize == 0) {
*returnSize = 0; // 告訴呼叫端結果有 0 個 / tell caller there are 0 results
return NULL; // 沒有東西可回傳 / nothing to return
}
// 最多會有 numsSize 個範圍(每個數字各自成段時)。先配置這麼大的指標陣列。
// At most numsSize ranges (when no two numbers are consecutive). Allocate that many string pointers.
// sizeof(char*) 是「一個字串指標」的位元組大小。 / sizeof(char*) = size of one string pointer.
char** result = (char**)malloc(sizeof(char*) * numsSize);
int count = 0; // 目前已產生的範圍數量 / how many ranges produced so far
int start = 0; // 當前區段的起點索引 / index where current run starts
// 逐一檢視每個元素 / examine each element one by one
for (int i = 0; i < numsSize; i++) {
// 區段在此結束的條件:i 是最後一個元素,或下一個數不等於目前數 +1。
// The run ends here if: i is the last element, OR the next value is not (current + 1).
// 注意用 (long) 轉型避免 nums[i]+1 在 nums[i]==INT_MAX 時溢位。
// Cast to long so nums[i]+1 won't overflow when nums[i] == INT_MAX.
if (i == numsSize - 1 || (long)nums[i] + 1 != nums[i + 1]) {
// 為這個範圍配置字串緩衝區。每個 int 最多 11 字元(含負號),
// 加上 "->" 與結尾 '