雜湊表 — 實戰題解

雜湊、堆疊與佇列優先度 4/5 — 高價值 — 這裡有缺口就會掉關高價值 更新於 Nov 12, 2026

範圍 — 雜湊表家族的經典 LC 題目,寫成題目卡,各自歸在它所歸約到的 hash_map.md 模板底下;本檔沒有自己的模板。 另見 — 核心文件:hash_map.md — 這些題目歸約到的十個模板、辨識表,以及有序 map(TreeMap / SortedDict)的 API 參考。 鄰近文件:prefix_sum.md — 前綴和自成一家的完整說明;hashing.md — 雜湊怎麼運作,以及計數與 rolling hash 的慣用寫法;set.md — 只管有沒有,不管值。

LeetCode 題目清單

總覽

這份是 hash_map.md 的題目集:模板在那邊,這裡的每一題都是一張卡,寫明它歸約到哪個模板、以及多加了哪一步。各個 ## 章節依核心文件十個模板的順序排列。最好的詳解放在別份文件的題目,在這裡只留一列指標,所以 Index 仍然列出整組題目。

索引

LC 題目 歸約到 難度 詳解在
1 Two Sum hash_map § Template 1 簡單 hash_map.md(錨點題)
1010 Pairs of Songs With Total Durations Divisible by 60 hash_map § Template 1 中等 下方
532 K-diff Pairs in an Array hash_map § Template 1 中等 set_examples.md
336 Palindrome Pairs hash_map § Template 1 困難 palindrome.md § Template 9
352 Data Stream as Disjoint Intervals hash_map § Template 1 困難 下方
242 Valid Anagram hash_map § Template 2 簡單 hash_map.md(錨點題)
554 Brick Wall hash_map § Template 2 中等 下方
846 Hand of Straights hash_map § Template 2 中等 下方
1726 Tuple with Same Product hash_map § Template 2 中等 下方
187 Repeated DNA Sequences hash_map § Template 2 中等 hashing.md § 2-5
763 Partition Labels string § Template 8 中等 string.md
49 Group Anagrams hash_map § Template 3 中等 hash_map.md(錨點題)
734 Sentence Similarity hash_map § Template 3 簡單 下方
560 Subarray Sum Equals K hash_map § Template 4 中等 hash_map.md(錨點題)
525 Contiguous Array hash_map § Template 4 中等 下方
325 Maximum Size Subarray Sum Equals k hash_map § Template 4 中等 prefix_sum_examples.md § 1
523 Continuous Subarray Sum hash_map § Template 4 中等 prefix_sum_examples.md § 2
974 Subarray Sums Divisible by K hash_map § Template 4 中等 prefix_sum.md § Template 3
1248 Count Number of Nice Subarrays hash_map § Template 4 中等 prefix_sum.md § Template 6
438 Find All Anagrams in a String hash_map § Template 5 中等 hash_map.md(錨點題)
3 Longest Substring Without Repeating Characters sliding_window § Template 3 中等 sliding_window.md
424 Longest Repeating Character Replacement sliding_window § Template 3 中等 sliding_window_examples.md § 5
953 Verifying an Alien Dictionary hash_map § Template 6 簡單 hash_map.md(錨點題)
670 Maximum Swap hash_map § Template 6 中等 下方
2471 Minimum Number of Operations to Sort a Binary Tree by Level hash_map § Template 6 中等 下方
205 Isomorphic Strings hash_map § Template 7 簡單 hash_map.md(錨點題)
290 Word Pattern hash_map § Template 7 簡單 hash_map.md
133 Clone Graph hash_map § Template 8 中等 hash_map.md(錨點題)
1257 Smallest Common Region hash_map § Template 8 中等 下方
710 Random Pick with Blacklist hash_map § Template 9 困難 hash_map.md(錨點題)
692 Top K Frequent Words hash_map § Template 10 中等 hash_map.md(錨點題,堆積與分桶兩種解法)
347 Top K Frequent Elements heap § Template 2 中等 heap.md(堆積與分桶兩種解法)
621 Task Scheduler heap § Template 8 中等 heap_examples.md
767 Reorganize String heap § Template 7 中等 heap_examples.md
36 Valid Sudoku hashing § Template 6 中等 hashing.md
146 LRU Cache linked_list § Template 7 中等 linked_list.md
139 Word Break dp § Template 1b 中等 dp.md
322 Coin Change knapsack § Template 4 中等 knapsack.md
1048 Longest String Chain dp_advanced § Longest String Chain 中等 dp_advanced.md
1044 Longest Duplicate Substring string_matching_kmp_rolling_hash § 2-3 困難 string_matching_kmp_rolling_hash.md
204 Count Primes combinatorics_math_patterns § Sieve 中等 combinatorics_math_patterns.md
496 Next Greater Element I monotonic_stack § Template 1 簡單 monotonic_stack.md
503 Next Greater Element II monotonic_stack § Template 4 中等 monotonic_stack.md
739 Daily Temperatures stack § Template 3 中等 stack.md
853 Car Fleet monotonic_stack § 2-10 中等 monotonic_stack.md
729 My Calendar I hash_map § Language APIs 中等 design_examples.md
731 My Calendar II hash_map § Language APIs 中等 design_examples.md
732 My Calendar III hash_map § Language APIs 困難 design_examples.md

歸約到指向別份文件的那一列,就在那份文件詳解;列在這裡,是為了讓在本檔搜尋時還找得到它。

查補數的例題

Pairs of Songs With Total Durations Divisible by 60 — LC 1010

關鍵 — 一首歌的補數是餘數,不是值:t 會和之前每一首 t % 60 等於 (60 - t % 60) % 60 的歌配對,所以 map 依餘數計歌數,答案是把次數加上去,而不是回傳一個索引。

歸約到:hash_map § Template 1 — 鍵是 t % 60,值是之前有幾首歌是這個餘數,因為題目要數配對,而不是找出一組。

python
# python
# LC 1010 - Pairs of Songs With Total Durations Divisible by 60
# IDEA: count songs per remainder; each song pairs with every earlier song whose remainder completes it to 0 mod 60
# time = O(n), space = O(1)   at most 60 remainders
def numPairsDivisibleBy60(time):
    count_of_rem = {}                       # {remainder: songs seen so far}
    pairs = 0
    for t in time:
        rem = t % 60
        need = (60 - rem) % 60              # remainder 0 pairs with 0, not with 60
        pairs += count_of_rem.get(need, 0)  # look up BEFORE recording: a song never pairs with itself
        count_of_rem[rem] = count_of_rem.get(rem, 0) + 1
    return pairs

陷阱

  • need = 60 - rem 沒有外層的 % 60,遇到 60 的倍數就會去查鍵 60:[60, 60, 60] 回傳 0 而不是 3。
  • 先記錄再查找,餘數是 0 或 30 的歌會和自己配對:[60] 回傳 1 而不是 0。

Data Stream as Disjoint Intervals — LC 352

關鍵 — 新值 v 只可能碰到結束於 v - 1 的區間,以及開始於 v + 1 的區間,所以兩個邊界 map —— {start: end} 和 {end: start} —— 讓每次合併都只是兩次 O(1) 查找。這兩個 map 永遠剛好存著目前的不相交區間。

歸約到:hash_map § Template 1 — 要探查的「夥伴」是鄰居 v - 1 和 v + 1,各在一個邊界 map 裡;只有 getIntervals 需要排序。

python
# python
# LC 352 - Data Stream as Disjoint Intervals
# IDEA: {start: end} and {end: start}; a new value probes v-1 (an end) and v+1 (a start) and merges with each
# time = O(1) per addNum, O(k log k) per getIntervals, space = O(n)   k = disjoint intervals
class SummaryRanges:
    def __init__(self):
        self.seen = set()              # every value added; a repeat changes nothing
        self.start_to_end = {}
        self.end_to_start = {}

    def addNum(self, value):
        if value in self.seen:
            return
        self.seen.add(value)
        start, end = value, value
        if value - 1 in self.end_to_start:          # an interval ends right before value
            start = self.end_to_start.pop(value - 1)
        if value + 1 in self.start_to_end:          # an interval starts right after value
            end = self.start_to_end.pop(value + 1)
        self.start_to_end[start] = end              # overwrites the left interval's old end
        self.end_to_start[end] = start              # overwrites the right interval's old start

    def getIntervals(self):
        result = []
        for start in sorted(self.start_to_end):
            result.append([start, self.start_to_end[start]])
        return result

試跑

text
add  start_to_end            end_to_start             getIntervals
1    {1:1}                   {1:1}                    [[1,1]]
3    {1:1, 3:3}              {1:1, 3:3}               [[1,1],[3,3]]
7    {1:1, 3:3, 7:7}         {1:1, 3:3, 7:7}          [[1,1],[3,3],[7,7]]
2    {1:3, 7:7}              {3:1, 7:7}               [[1,3],[7,7]]     2 joins [1,1] and [3,3]
6    {1:3, 6:7}              {3:1, 7:6}               [[1,3],[6,7]]

陷阱

  • 沒有 seen 集合:依序加入 1, 3, 2 之後再加一次 2,第二次找不到相鄰的邊界,於是在 [1, 3] 裡面多出一個 [2, 2]。
  • 合併時只 pop 一邊,會留下過時的邊界:把 [1,1] 和 [3,3] 接起來後,若還留著 end_to_start[1],之後的 2 又會再合併一次。

不適用時 — getIntervals 呼叫得遠比 addNum 頻繁:把區間存在有序 map 裡,讀取就是 O(k)、不必每次重新排序(hash_map § Language APIs)。

也歸約到 Template 1 題目 詳解在 難度
LC 1 Two Sum hash_map § Template 1 簡單
LC 532 K-diff Pairs in an Array set_examples.md 中等
LC 336 Palindrome Pairs palindrome.md § Template 9 困難

頻率計數的例題

Brick Wall — LC 554

關鍵 — 垂直線在磚縫最多的地方穿過的磚最少,所以去數磚縫的位置(每一列累積的寬度,不含牆的右邊界),答案是 列數 - 最大的次數。

歸約到:hash_map § Template 2 — 被計數的東西是前綴寬度,而不是輸入裡的元素。

python
# python
# LC 554 - Brick Wall
# IDEA: count every inner edge position across rows; cross where the most edges line up
# time = O(total bricks), space = O(distinct edge positions)
def leastBricks(wall):
    edges_at = {}                          # {distance from the left: rows with an edge there}
    for row in wall:
        position = 0
        for i in range(len(row) - 1):      # the last brick ends at the wall's edge: never a gap
            position += row[i]
            edges_at[position] = edges_at.get(position, 0) + 1
    most_edges = 0
    for count in edges_at.values():
        most_edges = max(most_edges, count)
    return len(wall) - most_edges

陷阱

  • 把每列的最後一塊磚也算進去:牆的右邊界每一列都有,most_edges 會變成 len(wall),每個輸入都回傳 0。
  • 每列都只有一塊磚([[1],[1],[1]]):沒有任何內部磚縫,map 是空的,對它取 max() 會丟例外 —— 把 most_edges 從 0 開始,答案就是 3。

Hand of Straights — LC 846

關鍵 — 剩下最小的那張牌只能當一組的開頭,所以依排序走過相異的牌,對接下來 groupSize 張牌各花掉 count[card] 張,永遠不會做出本來可以更好的選擇。

歸約到:hash_map § Template 2 — 計數表依鍵的排序讀取;Java 的 TreeMap 免費維持這個順序(hash_map § Language APIs)。

python
# python
# LC 846 - Hand of Straights
# IDEA: count the cards; from the smallest card up, every copy of it starts a run of groupSize consecutive cards
# time = O(n log n), space = O(n)
def isNStraightHand(hand, groupSize):
    if len(hand) % groupSize != 0:
        return False
    count = {}
    for card in hand:
        count[card] = count.get(card, 0) + 1
    for card in sorted(count):             # the smallest remaining card must start a group
        starts = count[card]               # groups that start at this card
        if starts == 0:
            continue
        for nxt in range(card, card + groupSize):
            if count.get(nxt, 0) < starts:
                return False               # a run would have a gap
            count[nxt] -= starts
    return True

複雜度 — 排序相異的牌是 O(n log n);內層迴圈只對當開頭的牌執行,而每次至少用掉 groupSize 張牌,所以總共是 O(n)。

陷阱

  • 依插入順序走過鍵:hand = [2, 1], groupSize = 2 從 2 開始,找不到 3,回傳 False;依排序則從 1 開始,回傳 True。
  • 一次只扣一組而不是 starts 組:結果正確,但 [1]*k + [2]*k 配 groupSize = 2 會把同一段重走 k 次。

Tuple with Same Product — LC 1726

關鍵 — 數出每個乘積有幾組索引配對;同一個乘積有 c 組配對,就有 C(c, 2) 種「兩組配對」的選法,而每一種 (a, b), (c, d) 會給出 8 個有序 tuple,所以答案是 sum(8 * C(c, 2))。

歸約到:hash_map § Template 2 — 被計數的是一組配對的乘積,答案是把次數組合起來,而不是互相比較。

python
# python
# LC 1726 - Tuple with Same Product
# IDEA: count index pairs per product; c pairs give C(c, 2) choices, each yielding 8 orderings
# time = O(n^2), space = O(n^2)
def tupleSameProduct(nums):
    pairs_with = {}                        # {product: index pairs i < j with that product}
    for i in range(len(nums)):
        for j in range(i + 1, len(nums)):
            product = nums[i] * nums[j]
            pairs_with[product] = pairs_with.get(product, 0) + 1
    total = 0
    for c in pairs_with.values():
        total += 8 * (c * (c - 1) // 2)    # choose 2 pairs, then 2 * 2 * 2 orderings
    return total
java
// java
// LC 1726 - Tuple with Same Product
// IDEA: count index pairs per product; c pairs give C(c, 2) choices, each yielding 8 orderings
// time = O(n^2), space = O(n^2)
public int tupleSameProduct(int[] nums) {
    Map<Integer, Integer> pairsWith = new HashMap<>();   // values <= 10^4, so a product fits an int
    for (int i = 0; i < nums.length; i++) {
        for (int j = i + 1; j < nums.length; j++) {
            pairsWith.merge(nums[i] * nums[j], 1, Integer::sum);
        }
    }
    int total = 0;
    for (int c : pairsWith.values()) {
        total += 4 * c * (c - 1);          // 8 * c * (c - 1) / 2, without the division
    }
    return total;
}

陷阱

  • 忘了乘 8:[2, 3, 4, 6] 只有一種兩組配對(2·6 = 3·4),答案是 8 而不是 1。這些順序是:第一組內 2 種 × 第二組內 2 種 × 哪一組在前 2 種。
  • 乘 8 的前提是兩組配對沒有共用元素。這只因為值都相異才成立:a·b = a·c 會推出 b = c。
也歸約到 Template 2 題目 詳解在 難度
LC 242 Valid Anagram hash_map § Template 2 簡單
LC 187 Repeated DNA Sequences hashing.md § 2-5 中等
LC 763 Partition Labels string § Template 8 中等

用算出來的鍵分組的例題

Sentence Similarity — LC 734

關鍵 — 相似關係是對稱但不遞移的,所以每一組配對都用無序鍵 (min, max) 只存一次,每個字的位置就是一次 O(1) 的成員查詢;不需要圖。

歸約到:hash_map § Template 3 — 無序配對鍵 — 標準鍵讓 ["great", "fine"] 和 ["fine", "great"] 成為同一筆;因為不需要計數,用集合就夠了。

python
# python
# LC 734 - Sentence Similarity
# IDEA: store each similar pair under the key (min, max); compare the sentences word by word
# time = O(n + p), space = O(p)   n words, p pairs
def areSentencesSimilar(sentence1, sentence2, similarPairs):
    if len(sentence1) != len(sentence2):
        return False
    similar = set()
    for a, b in similarPairs:
        similar.add((min(a, b), max(a, b)))       # {a, b} in either order -> one key
    for a, b in zip(sentence1, sentence2):
        if a != b and (min(a, b), max(a, b)) not in similar:
            return False
    return True

陷阱

  • 在原始配對串列裡查 [a, b]:結果正確,但每個字都是 O(p),而且還得再試一次 [b, a]。
  • 一個字和自己相似,不需要列出配對:沒有任何配對時 ["great"] 對 ["great"] 是 True,a != b 的檢查會給出這個結果。
  • 沒有遞移性:great ~ fine 和 fine ~ good 在這裡不會推出 great ~ good。LC 737 加上了遞移性,那就需要 union-find(dfs_examples.md)。
也歸約到 Template 3 題目 詳解在 難度
LC 49 Group Anagrams hash_map § Template 3 中等

前綴和計數表的例題

Contiguous Array — LC 525

關鍵 — 把每個 0 當成 -1,子陣列裡 0 和 1 一樣多,若且唯若它的和是 0,也就是若且唯若它兩端的累積和相等。map 記下每個累積和第一次出現的索引,並預先放入 {0: -1},所以 i - first[sum] 就是結尾在 i 的最長平衡子陣列。

歸約到:hash_map § Template 4 — 目標是相同的前綴和(k = 0),而 map 存的是第一次出現的索引而不是次數,因為題目問的是最長。

python
# python
# LC 525 - Contiguous Array
# IDEA: 0 -> -1, then the longest subarray with sum 0 = the widest gap between two equal running sums
# time = O(n), space = O(n)
def findMaxLength(nums):
    first_index = {0: -1}          # running sum 0 before index 0: subarrays that start at 0
    running = 0
    longest = 0
    for i, x in enumerate(nums):
        running += 1 if x == 1 else -1
        if running in first_index:
            longest = max(longest, i - first_index[running])
        else:
            first_index[running] = i      # keep the FIRST index only: it gives the longest span
    return longest
java
// java
// LC 525 - Contiguous Array
// IDEA: 0 -> -1, then the longest subarray with sum 0 = the widest gap between two equal running sums
// time = O(n), space = O(n)
public int findMaxLength(int[] nums) {
    Map<Integer, Integer> firstIndex = new HashMap<>();
    firstIndex.put(0, -1);                 // subarrays that start at index 0
    int running = 0;
    int longest = 0;
    for (int i = 0; i < nums.length; i++) {
        running += (nums[i] == 1) ? 1 : -1;
        if (firstIndex.containsKey(running)) {
            longest = Math.max(longest, i - firstIndex.get(running));
        } else {
            firstIndex.put(running, i);    // first occurrence only
        }
    }
    return longest;
}

試跑

text
nums = [0,0,0,0,1,1]
i  x  running  first_index (after)              longest
0  0  -1       {0:-1, -1:0}                     0
1  0  -2       {..., -2:1}                      0
2  0  -3       {..., -3:2}                      0
3  0  -4       {..., -4:3}                      0
4  1  -3       -3 seen at 2  -> 4 - 2 = 2       2
5  1  -2       -2 seen at 1  -> 5 - 1 = 4       4   nums[2..5] = [0,0,1,1]

陷阱

  • 沒有預先放入 {0: -1}:[0, 1] 回傳 0 而不是 2,因為那個平衡子陣列從索引 0 開始。
  • 每次重複都更新索引,留下的是最後一次出現:[0, 1, 0, 1] 回傳 2 而不是 4。
  • 計數表(LC 560 的 {sum: count})回答的是「有幾個」,不是「有多長」;這裡的值必須是索引。
也歸約到 Template 4 題目 詳解在 難度
LC 560 Subarray Sum Equals K hash_map § Template 4 中等
LC 325 Maximum Size Subarray Sum Equals k prefix_sum_examples.md § 1 中等
LC 523 Continuous Subarray Sum prefix_sum_examples.md § 2 中等
LC 974 Subarray Sums Divisible by K prefix_sum.md § Template 3 中等
LC 1248 Count Number of Nice Subarrays prefix_sum.md § Template 6 中等

固定視窗字元計數的例題

錨點題在核心文件裡詳解;長度可變、同樣維護計數表的視窗,在滑動視窗的文件裡詳解。

歸約到 Template 5,或歸約到滑動視窗 題目 詳解在 難度
LC 438 Find All Anagrams in a String hash_map § Template 5 中等
LC 3 Longest Substring Without Repeating Characters sliding_window § Template 3 中等
LC 424 Longest Repeating Character Replacement sliding_window_examples.md § 5 中等

排名表的例題

Maximum Swap — LC 670

關鍵 — last[d] 是數字 d 最右邊的索引。第一個「後面還有更大數字」的位置 i,是一次交換唯一能把最高位數字變大的地方,而最好的交換對象是這些數字裡最大的那個、取它的最後一個位置。

歸約到:hash_map § Template 6 — 一次建好的值 → 位置 map,所以「我右邊有沒有更大的數字、在哪裡?」最多只要 9 次查找。不用 map 的三指標掃描在 array_examples.md § 4。

python
# python
# LC 670 - Maximum Swap
# IDEA: {digit: last index}; at the first position beaten by a larger digit later, swap with that digit's last copy
# time = O(n), space = O(1)   n digits, at most 10 keys
def maximumSwap(num):
    digits = list(str(num))
    last = {}
    for i, ch in enumerate(digits):
        last[int(ch)] = i                       # a later copy overwrites: the LAST index wins
    for i, ch in enumerate(digits):
        current = int(ch)
        for d in range(9, current, -1):         # the largest digit first
            if last.get(d, -1) > i:
                j = last[d]
                digits[i], digits[j] = digits[j], digits[i]
                return int("".join(digits))     # only one swap is allowed
    return num                                  # already the largest arrangement
java
// java
// LC 670 - Maximum Swap
// IDEA: {digit: last index}; at the first position beaten by a larger digit later, swap with that digit's last copy
// time = O(n), space = O(1)   10 digits
public int maximumSwap(int num) {
    char[] digits = String.valueOf(num).toCharArray();
    int[] last = new int[10];
    for (int i = 0; i < digits.length; i++) {
        last[digits[i] - '0'] = i;
    }
    for (int i = 0; i < digits.length; i++) {
        int current = digits[i] - '0';
        for (int d = 9; d > current; d--) {
            if (last[d] > i) {
                char tmp = digits[i];
                digits[i] = digits[last[d]];
                digits[last[d]] = tmp;
                return Integer.parseInt(new String(digits));
            }
        }
    }
    return num;
}

試跑

text
num = 2736   last = {2:0, 7:1, 3:2, 6:3}
i=0  digit 2: try 9, 8 (absent), 7 -> last[7] = 1 > 0  -> swap 0 and 1 -> 7236, stop

陷阱

  • 和較大數字的第一個位置交換:1993 會得到 9193;用最後一個位置才是 9913。
  • 取第一個找到的較大數字,而不是最大的:2736 會把 2 和 3 交換,回傳 3726。
  • 沒有可以改進的位置(9973):原封不動回傳 num。

Minimum Number of Operations to Sort a Binary Tree by Level — LC 2471

關鍵 — 各層彼此獨立,所以答案是把每一層排好所需最少交換次數的總和。{value: current index} map 讓每次交換都是 O(1):在索引 i,取出該放在這裡的值目前在哪,更新 map 的兩筆,再交換。每次交換都會把一個值放到最終位置,所以次數是最少的。

歸約到:hash_map § Template 6 — 一個值 → 位置的 map,隨著陣列變動保持最新;BFS 負責提供每一層。

python
# python
# LC 2471 - Minimum Number of Operations to Sort a Binary Tree by Level
# IDEA: BFS per level; min swaps to sort a level = place sorted[i] at i via a {value: current index} map
# time = O(n log n), space = O(w)   w = the widest level
from collections import deque

def minimumOperations(root):
    queue = deque([root])
    operations = 0
    while queue:
        level = []
        for _ in range(len(queue)):
            node = queue.popleft()
            level.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        operations += min_swaps(level)
    return operations

def min_swaps(arr):
    target = sorted(arr)
    position = {}                               # {value: current index}; values are unique
    for i, v in enumerate(arr):
        position[v] = i
    swaps = 0
    for i in range(len(arr)):
        want = target[i]
        if arr[i] != want:
            j = position[want]                  # where the value that belongs at i sits now
            position[arr[i]] = j                # update the map BEFORE the swap overwrites arr[i]
            position[want] = i
            arr[i], arr[j] = arr[j], arr[i]
            swaps += 1
    return swaps
java
// java
// LC 2471 - Minimum Number of Operations to Sort a Binary Tree by Level
// IDEA: BFS per level; min swaps to sort a level = place sorted[i] at i via a {value: current index} map
// time = O(n log n), space = O(w)
public int minimumOperations(TreeNode root) {
    Queue<TreeNode> queue = new LinkedList<>();
    queue.offer(root);
    int operations = 0;
    while (!queue.isEmpty()) {
        int size = queue.size();
        int[] level = new int[size];
        for (int i = 0; i < size; i++) {
            TreeNode node = queue.poll();
            level[i] = node.val;
            if (node.left != null) queue.offer(node.left);
            if (node.right != null) queue.offer(node.right);
        }
        operations += minSwaps(level);
    }
    return operations;
}

private int minSwaps(int[] arr) {
    int[] target = arr.clone();
    Arrays.sort(target);
    Map<Integer, Integer> position = new HashMap<>();   // {value: current index}
    for (int i = 0; i < arr.length; i++) {
        position.put(arr[i], i);
    }
    int swaps = 0;
    for (int i = 0; i < arr.length; i++) {
        int want = target[i];
        if (arr[i] != want) {
            int j = position.get(want);
            position.put(arr[i], j);       // update the map before the swap
            position.put(want, i);
            arr[j] = arr[i];
            arr[i] = want;
            swaps++;
        }
    }
    return swaps;
}

試跑

text
min_swaps([3, 1, 2])   target = [1, 2, 3]   position = {3:0, 1:1, 2:2}
i=0  want 1 at j=1   position -> {3:1, 1:0}   arr = [1, 3, 2]   swaps = 1
i=1  want 2 at j=2   position -> {3:2, 2:1}   arr = [1, 2, 3]   swaps = 2
i=2  3 already in place                                          -> 2

陷阱

  • 先交換、再更新 map:arr[i] 已經是 want 了,被換走的值的新索引會寫到錯的鍵底下。
  • 用 arr.index(want) 找 want 而不是查 map:結果正確,但每次交換 O(w),每層 O(w²)。
也歸約到 Template 6 題目 詳解在 難度
LC 953 Verifying an Alien Dictionary hash_map § Template 6 簡單

雙射的例題

這張卡的兩題都在核心文件裡詳解。

歸約到 Template 7 題目 詳解在 難度
LC 205 Isomorphic Strings hash_map § Template 7 簡單
LC 290 Word Pattern hash_map § Template 7 簡單

舊節點對新節點表的例題

Smallest Common Region — LC 1257

關鍵 — 每個串列列出一個父區域和它的子區域,所以 {child: parent} map 把這些串列變成一棵有父指標的樹,答案就是最低共同祖先:把 region1 的所有祖先放進集合,再讓 region2 往上走,直到碰到其中一個。

歸約到:hash_map § Template 8 — map 是 {child: parent} 而不是 {original: clone};往上走的那段就是 tree_lca_distance.md 的父指標 LCA。

python
# python
# LC 1257 - Smallest Common Region
# IDEA: {child: parent} from the lists; region1's ancestors in a set; walk region2 up until it hits one
# time = O(total names), space = O(total names)
def findSmallestRegion(regions, region1, region2):
    parent = {}
    for group in regions:
        for child in group[1:]:            # group[0] contains every other name in the list
            parent[child] = group[0]
    ancestors = set()
    node = region1
    while node is not None:
        ancestors.add(node)                # region1 counts as its own ancestor
        node = parent.get(node)            # the root has no parent -> None
    node = region2
    while node not in ancestors:           # the root is in the set, so this stops
        node = parent[node]
    return node
java
// java
// LC 1257 - Smallest Common Region
// IDEA: {child: parent} from the lists; region1's ancestors in a set; walk region2 up until it hits one
// time = O(total names), space = O(total names)
public String findSmallestRegion(List<List<String>> regions, String region1, String region2) {
    Map<String, String> parent = new HashMap<>();
    for (List<String> group : regions) {
        for (int i = 1; i < group.size(); i++) {
            parent.put(group.get(i), group.get(0));
        }
    }
    Set<String> ancestors = new HashSet<>();
    for (String node = region1; node != null; node = parent.get(node)) {
        ancestors.add(node);
    }
    String node = region2;
    while (!ancestors.contains(node)) {
        node = parent.get(node);
    }
    return node;
}

陷阱

  • 沒把 region1 放進它自己的祖先集合:當 region1 包含 region2("North America"、"New York")時,往上走會越過 "North America",答成 "Earth"。
  • 改存 {parent: [children]}:那是由上往下 DFS 的正確形狀,但要找祖先就得從根開始搜尋。
也歸約到 Template 8 題目 詳解在 難度
LC 133 Clone Graph hash_map § Template 8 中等

虛擬映射的例題

歸約到 Template 9 題目 詳解在 難度
LC 710 Random Pick with Blacklist hash_map § Template 9 困難

map 加堆積的例題

LC 692 在核心文件裡詳解,有堆積和依次數分桶兩種解法。其他從計數表出發的 top-K 與排程題,在堆積的文件裡詳解。

歸約到 Template 10,或歸約到堆積的卡 題目 詳解在 難度
LC 692 Top K Frequent Words hash_map § Template 10 中等
LC 347 Top K Frequent Elements heap § Template 2 中等
LC 621 Task Scheduler heap_examples.md,歸約到 heap § Template 8 中等
LC 767 Reorganize String heap_examples.md,歸約到 heap § Template 7 中等