陣列 — 實戰題解

陣列與字串優先度 4/5 — 高價值 — 這裡有缺口就會掉關高價值 更新於 Nov 12, 2026

範圍 — array.md 自己那些模板(索引當雜湊、一次掃描的即時狀態)背後的題目,每題都放在它歸約到的卡片底下;歸屬在別份文件的陣列標籤題,在這裡只留一列指標。 另見:array.md — 母表:基本操作、這些題目歸約到的模板,以及告訴你一題到底該歸哪張表的選擇表;2_pointers.md、sliding_window.md、prefix_sum.md、difference_array.md — 吃掉大部分 array 標籤題目的四個模式家族;sort.md、matrix.md、kadane_algorithm.md、stock_trading.md — 選擇表會把題目導向的其他主表。

LeetCode 題目清單

總覽

array.md 放模板;這份文件放應用它們的題目。每張卡片都寫出它歸約到哪個模板,以及多出來的那一步。 多數標成 array 的題目其實是視窗、指標、前綴和、排序或 DP 題:它們列在 其他歸屬,連到詳解它們的文件,所以每題都只在一個地方解。

索引

模式 題目(易 → 難) 核心 ⭐
Template 9: Cyclic Sort, Index as Hash LC 448 · LC 442 LC 41(錨定題,在 array.md)
Template 11: Running State Scan LC 1375 · LC 1041 · LC 670 LC 849(錨定題,在 array.md)

其他歸屬

LC 題目 歸屬 為什麼放在那裡
41 First Missing Positive array § Template 9 循環排序卡片的錨定題
88 Merge Sorted Array 2_pointers § Template 5 反向寫入是兩個讀取指標加一個寫入指標
121 Best Time to Buy and Sell Stock stock_trading.md 目前為止的最低價;股票家族共用一個狀態機
122 Best Time to Buy and Sell Stock II greedy § Template 3 貪心累加的錨定題:把每一段上漲加起來
189 Rotate Array array § Template 10 三次反轉卡片的錨定題
238 Product of Array Except Self prefix_sum.md 前綴乘積乘上後綴乘積;輸出陣列兼當前綴陣列
251 Flatten 2D Vector array § Template 1 惰性雙游標迭代器的錨定題
287 Find the Duplicate Number binary_search_on_answer.md 它禁止修改 nums,所以不能用正負號標記:在值域上二分搜尋,或用 Floyd 的環偵測
334 Increasing Triplet Subsequence patience_sorting.md first / second 就是上限兩堆的 patience sorting 堆頂
406 Queue Reconstruction by Height sort.md 先按身高由高到低排,再插到索引 k
849 Maximize Distance to Closest Person array § Template 11 即時狀態卡片的錨定題
1109 Corporate Flight Bookings difference_array.md 在端點上做區間更新,再做一次前綴和
1567 Maximum Length of Subarray With Positive Product kadane_algorithm.md 以這裡結尾的最長正積/負積長度,Kadane 式

索引當雜湊題目

Find All Numbers Disappeared in an Array — LC 448

關鍵 — 把 nums[v - 1] 變成負數,代表「看過值 v」。掃完一次後,還是正數的格子就指出一個從沒出現過的值。 每次讀取都要經過 abs(),因為它讀的那格可能已經被標記過。

歸約到:array § Template 9 — 用正負號標記而不是搬動值,並收集每個缺口而不只是第一個。

python
# python
# LC 448 - Find All Numbers Disappeared in an Array
# IDEA: sign marking - negate nums[v - 1] for every v; the indices left positive are missing
# time = O(n), space = O(1) besides the output
def findDisappearedNumbers(nums):
    for x in nums:
        i = abs(x) - 1          # x may already be negative: it was marked by an earlier value
        if nums[i] > 0:         # mark once; a second negation would unmark a duplicate
            nums[i] = -nums[i]
    missing = []
    for i in range(len(nums)):
        if nums[i] > 0:
            missing.append(i + 1)
    return missing

陷阱

  • 沒有 > 0 防護就取負。 [1, 1] 會把第 0 格翻兩次,又變回正數,回傳 [1, 2] 而不是 [2]。
  • 直接讀 x。 標記之後 x 可能是負的;nums[x - 1] 在 Python 會默默從尾端索引,在 Java 會丟例外。
  • 呼叫端的陣列被改了。 如果還要再用,用 abs 還原。

相似題 — LC 268(只缺一個值:總和或 XOR 更簡單)· LC 645(一個重複、一個缺少,用同一組標記找出)

Find All Duplicates in an Array — LC 442

關鍵 — 第二次讀到值 v 時,第 v - 1 格已經是負的:這次讀取就是重複。每個值最多出現兩次, 所以每次讀取做一次判斷,就能把每個重複值剛好回報一次。

歸約到:array § Template 9 — 正負號標記,發現某格已經是負的時,那就是答案,而不是缺口。

python
# python
# LC 442 - Find All Duplicates in an Array
# IDEA: sign marking - the first visit negates nums[v - 1], a second visit finds it negative
# time = O(n), space = O(1) besides the output
def findDuplicates(nums):
    duplicates = []
    for x in nums:
        i = abs(x) - 1          # always through abs(): x itself may have been marked
        if nums[i] < 0:
            duplicates.append(i + 1)   # second visit to value i + 1
        else:
            nums[i] = -nums[i]         # first visit
    return duplicates

陷阱

  • 出現三次的值會被這個判斷回報兩次,但 LC 442 保證最多兩次;如果沒有這個保證,就另外記一個「已回報」標記。
  • LC 287 看起來一樣,其實不是。 它禁止修改 nums,所以這個解法在那裡是錯的;見其他歸屬。

相似題 — LC 287(一個重複值,輸入唯讀)· LC 645(集合不符)

一次掃描的即時狀態題目

Number of Times Binary String Is Prefix-Aligned — LC 1375

關鍵 — 第 i 步之後,位元 1..i+1 全部打開,剛好等價於目前翻過的最大索引是 i + 1。 已經翻過 i + 1 個不同的索引(輸入是排列),而且沒有一個大於 i + 1,所以它們正好就是 1..i+1。

歸約到:array § Template 11 — 帶著的值是目前最大值。因為輸入是排列,它取代了 Template 4 的頻率陣列。

python
# python
# LC 1375 - Number of Times Binary String Is Prefix-Aligned (formerly Bulb Switcher III)
# IDEA: carry the largest index flipped so far; the prefix is aligned when it equals the step count
# time = O(n), space = O(1)
def numTimesAllBlue(flips):
    largest = 0
    aligned = 0
    for step, index in enumerate(flips, start=1):
        largest = max(largest, index)
        if largest == step:        # step indices flipped, none above step -> exactly 1..step
            aligned += 1
    return aligned

陷阱

  • 拿從 0 開始的位置比。 用 enumerate(flips) 的 i 寫 largest == i,在 [3, 2, 4, 1, 5] 上永遠不成立, 回傳 0 而不是 2;索引是從 1 開始的。
  • 每一步都檢查翻過的位元是 O(n²),在 n = 5·10⁴ 時會超時。

Robot Bounded In Circle — LC 1041

關鍵 — 把指令跑一次。回到原點的機器人是有界的。面向北方以外任何方向的機器人也有界: 跑四次(面向南方的話兩次)之後位移會互相抵消。只有結束時不在原點而且仍面向北方的機器人會越走越遠。

歸約到:array § Template 11 — 帶著的狀態是 (x, y, heading),答案從跑完一次後的狀態讀出。

python
# python
# LC 1041 - Robot Bounded In Circle
# IDEA: simulate one pass; bounded iff back at the origin or no longer facing north
# time = O(n), space = O(1)
def isRobotBounded(instructions):
    moves = [(0, 1), (1, 0), (0, -1), (-1, 0)]   # north, east, south, west: clockwise
    x = 0
    y = 0
    heading = 0                                   # index into moves; 0 = north
    for c in instructions:
        if c == "L":
            heading = (heading + 3) % 4           # one step counter-clockwise
        elif c == "R":
            heading = (heading + 1) % 4           # one step clockwise
        else:                                     # "G"
            x += moves[heading][0]
            y += moves[heading][1]
    return (x == 0 and y == 0) or heading != 0

陷阱

  • 只檢查原點。 "GL" 結束在 (0, 1)、面向西方,不在原點,但它走的是一個正方形,所以有界;方向的判斷會抓到它。
  • Java 裡的 (heading - 1) % 4 在 heading 為 0 時是 -1,索引越界。像上面那樣改成加 3,而不是減 1。

Maximum Swap — LC 670

關鍵 — 從右往左掃數字,帶著目前看過的最大數字的索引(相同時取最右邊那個)。任何比這個最大值小的數字 都可以和它交換;最好的交換是最左邊的那個數字,而掃描是往左走的,所以最後記下的就是它。

歸約到:array § Template 11 — 從右邊掃,帶著每個數字右邊最大值的索引。

python
# python
# LC 670 - Maximum Swap
# IDEA: right-to-left scan carrying the index of the largest digit to the right;
#       the leftmost digit smaller than that maximum is swapped with it
# time = O(d), space = O(d)   d = number of digits (at most 9)
def maximumSwap(num):
    digits = list(str(num))
    max_idx = len(digits) - 1          # index of the largest digit in digits[i:]
    left = -1                          # the best swap found so far
    right = -1
    for i in range(len(digits) - 1, -1, -1):
        if digits[i] > digits[max_idx]:
            max_idx = i                # strictly larger: ties keep the rightmost copy
        elif digits[i] < digits[max_idx]:
            left = i                   # further left than any earlier find, so better
            right = max_idx
    if left == -1:
        return num                     # already the largest arrangement
    digits[left], digits[right] = digits[right], digits[left]
    return int("".join(digits))

陷阱

  • 更新最大值時用 >=。 1993 必須和最右邊的 9 交換(9913);>= 會把 max_idx 移到最左邊的 9,回傳 9193。
  • 一找到候選就停。 在 98368 上,從右邊第一個找到的是 6,得到 98386;最左邊比較小的數字是 3,得到 98863。
  • 暴力試每一對是 O(d²) — 9 位數時沒問題,但那不是面試官想看的答案。

第二種做法 — 一張 {digit: last index} 表加上由左往右掃,在 hash_map_examples.md。同樣 O(d);它問的是「後面有沒有更大的數字?」,而不是帶著最大值走。