Microsoft AI · Coding Interview Practice

18 題 Python 解題筆記

根據提供的 LeetCode / NeetCode 題單整理;每題包含題目摘要、Python 解法、繁體中文說明、Time / Space Complexity,以及 Interview Explanation Flow。

4 Easy 13 Medium 1 Hard String · Hash Table · Sliding Window · Heap · DFS · Intervals · Parsing Updated 2026-09-02
題目描述是根據公開題名與常見面試版本重新整理的原創摘要;重點放在面試時需要理解的輸入輸出、限制與解題策略。

解題總覽

常見模式

這份題單集中在 String / Hash Table / Sliding Window,也包含 Heap、DFS tree、interval sorting、backtracking 與 parsing。

面試重點

每題都要能說出資料結構選擇、核心 invariant、如何處理 edge cases,以及為什麼時間複雜度能維持線性或近線性。

建議順序

先練 anagram / pattern 題,再練 sliding window,最後處理 Minimum Interval、Word Pattern II 與 atoi 的細節。

# Problem Pattern Difficulty 核心想法 Time Space
1Valid AnagramHash Table / CountingEasy比較兩個字串的字元頻率是否一致。O(n)O(1)
2Find Anagram MappingsHash MapEasy建立值到 index list 的映射,再逐一取出對應位置。O(n)O(n)
3Group AnagramsHash Table / CountingMedium用排序字串或 26-count tuple 作為 group key。O(total chars)O(total chars)
4Find All Anagrams in a StringSliding WindowMedium固定長度窗口維護字元頻率,匹配時記錄左端點。O(n)O(1)
5Longest Substring with At Most Two Distinct CharactersSliding WindowMedium窗口最多保留兩種字元,超過就從左收縮。O(n)O(1)
6Longest Substring with At Most K Distinct CharactersSliding WindowMedium將上一題一般化成最多 K 種字元。O(n)O(k)
7Longest Substring Without Repeating CharactersSliding WindowMedium用 last seen index 跳過重複字元。O(n)O(k)
8Find the Longest Substring Containing Vowels in Even CountsBitmask / PrefixMedium用 5-bit mask 表示 vowels parity,相同 mask 代表區間內 vowel 都是偶數。O(n)O(1)
9Top K Frequent ElementsHash Map / Bucket SortMedium先統計頻率,再依頻率 bucket 由高到低取 k 個。O(n)O(n)
10Top K Frequent WordsHash Map / SortingMedium依頻率降冪、字典序升冪排列 unique words,再取前 k 個。O(n + u log u)O(u)
11Sort Characters By FrequencyHash Map / Bucket SortMedium依字元出現次數分 bucket,由高頻到低頻重建字串。O(n)O(n)
12Word PatternHash Map / BijectionEasypattern char 與 word 必須一對一映射。O(n)O(n)
13Word Pattern IIBacktrackingMedium嘗試將 pattern char 映射到不同 substring,並維持雙向唯一。ExponentialO(p+s)
14Time Needed to Inform All EmployeesTree / DFSMedium公司管理關係形成樹,答案是 head 到最深員工的傳遞時間。O(n)O(n)
15Minimum Interval to Include Each QuerySort / HeapHard按 query 遞增處理,heap 保存目前可覆蓋 query 的最短 interval。O((n+q) log n)O(n+q)
16Roman to IntegerParsingEasy從右往左掃,遇到比右側小的值就減,否則加。O(n)O(1)
17Merge IntervalsSort / IntervalsMedium依 start 排序後,和最後一段重疊就延伸 end。O(n log n)O(output)
18String to Integer (atoi)Parsing / SimulationMedium依序處理空白、符號、數字,最後 clamp 到 32-bit 範圍。O(n)O(1)

1. Valid Anagram

EasyHash TableCounting

題目整理

給兩個字串 st,判斷它們是否由完全相同的字元與次數組成。若長度不同,必定不是 anagram。

解法說明

先檢查長度,再用 26 個計數器統計 s 加一、t 減一。最後所有計數都為 0,代表兩者字元頻率完全相同。

class Solution:
    def isAnagram(self, s: str, t: str) -> bool:
        if len(s) != len(t):
            return False

        counts = [0] * 26
        base = ord("a")

        for a, b in zip(s, t):
            counts[ord(a) - base] += 1
            counts[ord(b) - base] -= 1

        return all(count == 0 for count in counts)
Time Complexity: O(n),掃過兩個字串一次。
Space Complexity: O(1),固定 26 個小寫字母計數。

Interview Explanation Flow

Step 1: State the invariant

"Two strings are anagrams if and only if every character appears the same number of times in both strings."

Step 2: Handle length mismatch

"If the lengths differ, they cannot be anagrams, so I return false immediately."

Step 3: Walk through counting

"I increment counts for characters in s and decrement counts for characters in t. If all counts return to zero, the frequencies match."

Example:
s = "anagram"
t = "nagaram"

after adding s and subtracting t:
all character counts become 0

answer = True

Step 4: Complexity

"Time is O(n), and space is O(1) for lowercase English letters."

Possible follow-ups

  • What if input is Unicode? "Use a hash map or Counter instead of a fixed 26-length array."

中文:面試時強調 anagram 的本質就是字元頻率完全一致。

2. Find Anagram Mappings

EasyHash Map

題目整理

給兩個互為 anagram 的整數陣列 nums1nums2,回傳一個 mapping,使得 nums1[i] == nums2[mapping[i]]。若有重複值,任一合法 mapping 都可以。

解法說明

先把 nums2 中每個值對應到它所有出現的位置。接著掃 nums1,對每個值從 map 中取出一個 index。用 list 作為 stack 可以自然支援 duplicate values。

from collections import defaultdict
from typing import List


class Solution:
    def anagramMappings(self, nums1: List[int], nums2: List[int]) -> List[int]:
        positions = defaultdict(list)
        for index, value in enumerate(nums2):
            positions[value].append(index)

        result = []
        for value in nums1:
            result.append(positions[value].pop())
        return result
Time Complexity: O(n),建立 map 與查詢各一次。
Space Complexity: O(n),保存 nums2 的所有 index。

Interview Explanation Flow

Step 1: Reframe as value-to-index lookup

"For every value in nums1, I need to know where the same value appears in nums2."

Step 2: Build an index map

"I store each value in nums2 with a list of all its indices, so duplicates are handled correctly."

Step 3: Walk through an example

nums1 = [12, 28, 46, 32, 50]
nums2 = [50, 12, 32, 46, 28]

positions:
50 -> [0]
12 -> [1]
32 -> [2]
46 -> [3]
28 -> [4]

result = [1, 4, 3, 2, 0]

Step 4: Complexity

"Both time and extra space are O(n)."

Possible follow-ups

  • Why store lists instead of one index? "Lists make the solution correct even if values are duplicated."

中文:重點是把第二個陣列做成 value 到 index 的查表。

3. Group Anagrams

MediumHash TableCounting

題目整理

給一組字串,將互為 anagram 的字串分到同一組。輸出順序通常不限。

解法說明

anagram 的 key 可以是排序後的字串,也可以是 26 個字母頻率。為了避免排序成本,這裡使用 tuple(counts) 當 hash key。

from collections import defaultdict
from typing import List


class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        groups = defaultdict(list)

        for word in strs:
            counts = [0] * 26
            for ch in word:
                counts[ord(ch) - ord("a")] += 1
            groups[tuple(counts)].append(word)

        return list(groups.values())
Time Complexity: O(total chars),每個字元被計數一次。
Space Complexity: O(total chars),輸出 groups 與 hash map。

Interview Explanation Flow

Step 1: Define the grouping key

"Words are anagrams when their character counts are identical, so I use the 26-count tuple as the hash key."

Step 2: Group by key

"For each word, I compute its frequency tuple and append the word to the corresponding group."

Example:
["eat", "tea", "tan", "ate", "nat", "bat"]

key(eat) = key(tea) = key(ate)
key(tan) = key(nat)

groups:
[["eat", "tea", "ate"], ["tan", "nat"], ["bat"]]

Step 3: Complexity

"Counting gives O(total chars). If I used sorting as the key, it would be O(n * k log k)."

Possible follow-ups

  • Why not sorted string? "Sorted string is simpler, but count tuple avoids per-word sorting."

中文:面試時先說清楚 anagram group 的 key 是字母頻率。

4. Find All Anagrams in a String

MediumSliding Window

題目整理

給字串 sp,找出 s 中所有長度等於 p 且字元頻率與 p 相同的 substring 起始位置。

解法說明

窗口長度固定為 len(p)。先建立 p 的字母計數,再滑動 s 的窗口,加入右端字元、移除左端字元,若計數相同就記錄左端點。

from typing import List


class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        if len(p) > len(s):
            return []

        base = ord("a")
        need = [0] * 26
        window = [0] * 26

        for i, ch in enumerate(p):
            need[ord(ch) - base] += 1
            window[ord(s[i]) - base] += 1

        result = []
        if window == need:
            result.append(0)

        for right in range(len(p), len(s)):
            left = right - len(p)
            window[ord(s[right]) - base] += 1
            window[ord(s[left]) - base] -= 1
            if window == need:
                result.append(left + 1)

        return result
Time Complexity: O(n),每次比較 26 個字母可視為常數。
Space Complexity: O(1),固定 26 個計數。

Interview Explanation Flow

Step 1: Use fixed-length sliding window

"Every anagram of p has length len(p), so I slide a fixed-size window across s."

Step 2: Compare frequencies

"A window is valid if its character frequency exactly matches p's frequency."

Example:
s = "cbaebabacd", p = "abc"
window size = 3

"cba" -> anagram, index 0
"bae" -> no
"aeb" -> no
...
"bac" -> anagram, index 6

answer = [0, 6]

Step 3: Complexity

"Time is O(n) and space is O(1)."

Possible follow-ups

  • Why not sort each window? "Sorting would cost more; counts update in constant time."

中文:固定長度窗口加上字母頻率比較,是這題的核心。

另一種解法:Matches Counter Optimization

第一種解法每次滑動後直接比較兩個長度 26 的 frequency arrays;雖然仍是常數時間,但可以再用 matches 記錄目前有多少個字母的頻率相等。當 matches == 26,代表目前窗口就是 anagram。

from typing import List


class Solution:
    def findAnagrams(self, s: str, p: str) -> List[int]:
        if len(p) > len(s):
            return []

        base = ord("a")
        need = [0] * 26
        window = [0] * 26

        for i, ch in enumerate(p):
            need[ord(ch) - base] += 1
            window[ord(s[i]) - base] += 1

        matches = sum(1 for i in range(26) if need[i] == window[i])

        result = []
        if matches == 26:
            result.append(0)

        for right in range(len(p), len(s)):
            left = right - len(p)

            add = ord(s[right]) - base
            if window[add] == need[add]:
                matches -= 1
            window[add] += 1
            if window[add] == need[add]:
                matches += 1

            remove = ord(s[left]) - base
            if window[remove] == need[remove]:
                matches -= 1
            window[remove] -= 1
            if window[remove] == need[remove]:
                matches += 1

            if matches == 26:
                result.append(left + 1)

        return result
Time Complexity: O(n),每個字元最多被加入與移出窗口一次;matches 讓每次更新維持 O(1)。
Space Complexity: O(1),只使用兩個固定長度 26 的 frequency arrays。

Interview Explanation Flow — Matches Counter

Step 1: Point out the key observation

"An anagram has the same character frequencies as the original string, just in different order. So instead of generating all permutations, we just need to find windows in s of length len(p) that have identical character frequencies."

Step 2: Explain why sliding window fits

"Since we're looking for substrings of a fixed length, this is a natural fit for a fixed-size sliding window. We slide one character at a time -- adding the new right character and removing the old left character."

s = "cbaebacd", p = "abc"
window size = 3

[c b a] e b a c d  -> match
 [b a e] b a c d
  [a e b] a c d
   [e b a] c d
    [b a c] d      -> match
     [a c d]

Step 3: Explain the matches counter

"Naively comparing two 26-element frequency arrays at every step costs O(26) per slide. Instead, we maintain a matches counter -- how many of the 26 characters currently have equal frequency in both arrays. When matches hits 26, we found an anagram."

need   = {a:1, b:1, c:1, others:0}
window = {a:1, b:1, c:1, others:0}

matches = 26 -> anagram

Step 4: Explain how to update matches

"The key is updating matches correctly. Before changing window, check if it currently equals need; if so, decrement matches because we're about to break that equality. After changing, check again; if they're equal now, increment matches."

# Add the right character.
if window[add] == need[add]:  matches -= 1
window[add] += 1
if window[add] == need[add]:  matches += 1

# Remove the left character; same logic.
if window[remove] == need[remove]:  matches -= 1
window[remove] -= 1
if window[remove] == need[remove]:  matches += 1

Step 5: Walk through a complete example

s = "cbaebacd", p = "abc"

need = {a:1, b:1, c:1, others:0}
initial window = {c:1, b:1, a:1}
matches = 26 -> result = [0]

right=3, left=0: add e, remove c
  add e: 0 -> 1, need[e] = 0, no longer equal -> matches = 25
  remove c: 1 -> 0, need[c] = 1, no longer equal -> matches = 24

right=4, left=1: add b, remove b
  add b: 1 -> 2, need[b] = 1, breaks equality -> matches = 23
  remove b: 2 -> 1, need[b] = 1, equal again -> matches = 24

right=5, left=2: add a, remove a
  add a: 1 -> 2, need[a] = 1, breaks equality -> matches = 23
  remove a: 2 -> 1, need[a] = 1, equal again -> matches = 24

right=6, left=3: add c, remove e
  add c: 0 -> 1, need[c] = 1, equal again -> matches = 25
  remove e: 1 -> 0, need[e] = 0, equal again -> matches = 26
  result = [0, 4]

return [0, 4]

Step 6: Complexity

"Time is O(n) because each character is added and removed exactly once, and each window update is O(1) thanks to the matches counter. Space is O(1) because we only use two fixed-size arrays of 26."

Possible follow-ups

  • Why use an array of size 26 instead of a Counter? "Both work, but a fixed array gives O(1) index access and makes the matches optimization more natural."
  • What if the string contains characters outside lowercase letters? "We would switch to a hash map and track how many required characters are fully satisfied instead of assuming exactly 26 buckets."

中文:開場點出 anagram 等於頻率相同,說明 fixed-size sliding window,重點解釋 matches counter 的更新邏輯,並走完整例子展示每步變化。

5. Longest Substring with At Most Two Distinct Characters

MediumSliding WindowHash Map

題目整理

給一個字串,找出最多包含兩種不同字元的最長 substring 長度。

解法說明

維護一個 sliding window 與字元計數 map。右指針擴張窗口;若不同字元數超過 2,就移動左指針並降低計數,直到窗口重新合法。

from collections import defaultdict


class Solution:
    def lengthOfLongestSubstringTwoDistinct(self, s: str) -> int:
        counts = defaultdict(int)
        left = 0
        best = 0

        for right, ch in enumerate(s):
            counts[ch] += 1

            while len(counts) > 2:
                left_char = s[left]
                counts[left_char] -= 1
                if counts[left_char] == 0:
                    del counts[left_char]
                left += 1

            best = max(best, right - left + 1)

        return best
Time Complexity: O(n),左右指針各走一次。
Space Complexity: O(1),map 最多保留 3 種字元後立刻收縮。

Interview Explanation Flow

Step 1: Define the valid window

"A valid window can contain at most two distinct characters."

Step 2: Expand and shrink

"I expand with the right pointer. If the window becomes invalid, I shrink from the left until it has at most two distinct characters again."

Example:
s = "eceba"

"e"   valid, best = 1
"ec"  valid, best = 2
"ece" valid, best = 3
"eceb" has 3 distinct -> shrink to "eb"
"eba" has 3 distinct -> shrink to "ba"

answer = 3 ("ece")

Step 3: Complexity

"Time is O(n) because each character enters and leaves once."

Possible follow-ups

  • Why sliding window? "Validity is monotonic: after too many distinct characters, moving left can restore validity."

中文:窗口維持最多兩種字元,超過就從左邊收縮。

6. Longest Substring with At Most K Distinct Characters

MediumSliding WindowHash Map

題目整理

給字串 s 與整數 k,找出最多包含 k 種不同字元的最長 substring 長度。

解法說明

這是上一題的泛化。用 hash map 記錄窗口內字元次數,當不同字元數超過 k 時,從左側移除直到合法。

from collections import defaultdict


class Solution:
    def lengthOfLongestSubstringKDistinct(self, s: str, k: int) -> int:
        if k == 0:
            return 0

        counts = defaultdict(int)
        left = 0
        best = 0

        for right, ch in enumerate(s):
            counts[ch] += 1

            while len(counts) > k:
                left_char = s[left]
                counts[left_char] -= 1
                if counts[left_char] == 0:
                    del counts[left_char]
                left += 1

            best = max(best, right - left + 1)

        return best
Time Complexity: O(n),每個字元最多進出窗口一次。
Space Complexity: O(k),窗口內最多保留 k 種字元。

Interview Explanation Flow

Step 1: Generalize the window rule

"The valid window condition is now at most k distinct characters."

Step 2: Maintain counts

"I use a hash map to know how many distinct characters are currently in the window."

Example:
s = "aaabbcc", k = 2

"aaa"   valid
"aaabb" valid, best = 5
"aaabbc" has 3 distinct -> shrink until only "bbc"

answer = 5 ("aaabb")

Step 3: Edge case

"If k == 0, no non-empty substring is valid, so the answer is 0."

Step 4: Complexity

"Time is O(n), space is O(k)."

中文:把合法條件改成最多 k 種字元,其餘就是標準 sliding window。

7. Longest Substring Without Repeating Characters

MediumSliding WindowHash Map

題目整理

給字串 s,找出不含重複字元的最長 substring 長度。

解法說明

last_seen 記錄字元上次出現位置。左指針用 max(left, last_seen[ch] + 1) 更新,確保它只會前進不會後退,即使該字元的上次出現位置其實在目前窗口之前也沒關係。

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        last_seen = {}
        left = 0
        best = 0

        for right, ch in enumerate(s):
            if ch in last_seen:
                left = max(left, last_seen[ch] + 1)

            last_seen[ch] = right
            best = max(best, right - left + 1)

        return best
Time Complexity: O(n),每個字元處理一次。
Space Complexity: O(k),k 是字元種類數。

Interview Explanation Flow

Step 1: Define the invariant

"The current window should always contain no duplicate characters."

Step 2: Jump the left pointer

"When I see a repeated character inside the current window, I move left to one position after its previous occurrence."

Example:
s = "abcabcbb"

abc -> best = 3
next a repeats at index 0, move left to 1
next b repeats, move left to 2
...

answer = 3

Step 3: Complexity

"Time is O(n) because left never moves backward."

Possible follow-ups

  • Why use max(left, last_seen[ch] + 1) logic? "A duplicate before the current window should not move left backward."

中文:核心是維持窗口內沒有重複字元,遇到重複就直接跳過上次位置。

8. Find the Longest Substring Containing Vowels in Even Counts

MediumBitmaskPrefix State

題目整理

給字串 s,找出最長 substring,使其中每個 vowel(a/e/i/o/u)的出現次數都是偶數。

解法說明

用 5-bit mask 表示五個 vowel 的奇偶狀態。遇到某個 vowel 就 toggle 對應 bit。若同一個 mask 曾經在位置 j 出現,代表 j+1..i 之間所有 vowel parity 都回到偶數。

class Solution:
    def findTheLongestSubstring(self, s: str) -> int:
        bit = {"a": 0, "e": 1, "i": 2, "o": 3, "u": 4}
        first_seen = {0: -1}
        mask = 0
        best = 0

        for i, ch in enumerate(s):
            if ch in bit:
                mask ^= 1 << bit[ch]

            if mask in first_seen:
                best = max(best, i - first_seen[mask])
            else:
                first_seen[mask] = i

        return best
Time Complexity: O(n),掃過字串一次。
Space Complexity: O(1),mask 只有 32 種狀態。

Interview Explanation Flow

Step 1: Convert counts to parity

"We only care whether each vowel count is odd or even, not the exact count."

Step 2: Use a bitmask state

"Each vowel gets one bit. Toggling a bit changes that vowel from even to odd or odd to even."

Example:
s = "eleetminicoworoep"

mask 0 means all vowel counts are even.
When the same mask appears again, the substring between
the two positions has even counts for all vowels.

known answer = 13

Step 3: Store earliest occurrence

"To maximize length, I only store the first time each mask appears."

Step 4: Complexity

"Time is O(n), space is O(1) because there are only 32 masks."

中文:用 bitmask 表示 vowel 奇偶狀態,相同狀態之間的區間就是合法 substring。

9. Top K Frequent Elements

MediumHash MapBucket Sort

題目整理

給整數陣列 nums 與整數 k,回傳出現頻率最高的 k 個元素。答案順序通常不限。

解法說明

先用 hash map 統計頻率。因為頻率最多是 n,可以建立 n+1 個 buckets,將元素放到對應頻率的 bucket,最後從高頻到低頻收集 k 個元素。

Frequency 題型家族:這題與 Top K Frequent WordsSort Characters By Frequency 都先用 frequency map 計數;差別在輸出規則、tie-break,以及要取 top-k 還是重建完整字串。
from collections import Counter
from typing import List


class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        freq = Counter(nums)
        buckets = [[] for _ in range(len(nums) + 1)]

        for num, count in freq.items():
            buckets[count].append(num)

        result = []
        for count in range(len(buckets) - 1, 0, -1):
            for num in buckets[count]:
                result.append(num)
                if len(result) == k:
                    return result

        return result
Time Complexity: O(n),統計與 bucket 掃描都是線性。
Space Complexity: O(n),frequency map 與 buckets。

Interview Explanation Flow

Step 1: Count frequencies

"The core information is how many times each number appears."

Step 2: Use frequency buckets

"Since a frequency can only range from 1 to n, I can bucket elements by frequency and scan from high to low."

Example:
nums = [1,1,1,2,2,3], k = 2

freq:
1 -> 3
2 -> 2
3 -> 1

buckets[3] = [1]
buckets[2] = [2]

answer = [1, 2]

Step 3: Complexity

"Bucket sort gives O(n) time, better than sorting all unique elements by frequency."

Possible follow-ups

  • Alternative? "A min-heap of size k gives O(n log k), useful when k is small or streaming."

中文:先統計頻率,再用 bucket 從高頻往低頻取出 k 個元素。

10. Top K Frequent Words

MediumHash MapSortingTop-K

題目整理

給字串陣列 words 與整數 k,回傳出現次數最高的 k 個單字。答案先依 frequency 由高到低排列;若頻率相同,則依 lexicographical order 由小到大排列。公開題目:LeetCode 692

解法說明

先用 Counter 統計每個 word 的頻率,再排序所有 unique words。排序 key 使用 (-count, word):負的 count 讓高頻排在前面,word 本身則處理頻率相同時的字典序。最後取前 k 個。

Frequency 題型家族:先比較 Top K Frequent Elements 的 bucket 思路,再注意本題多了 lexicographical tie-break;Sort Characters By Frequency 則是依頻率重建整個字串。
from collections import Counter
from typing import List


class Solution:
    def topKFrequent(self, words: List[str], k: int) -> List[str]:
        counts = Counter(words)
        ordered = sorted(
            counts,
            key=lambda word: (-counts[word], word),
        )
        return ordered[:k]
Time Complexity: O(n + u log u),n 是 words 數量,u 是 unique words 數量;先計數,再排序 u 個單字。
Space Complexity: O(u),frequency map 與排序結果保存所有 unique words。

Interview Explanation Flow

Step 1: Identify both ordering rules

"I need to rank words by descending frequency, with lexicographical order as the tie-breaker."

Step 2: Count, then sort unique words

"I count each word, then sort the unique words using the key (-frequency, word). Negating the frequency gives descending numeric order while the word remains ascending."

words = ["i", "love", "leetcode", "i", "love", "coding"]
k = 2

counts: i=2, love=2, coding=1, leetcode=1
same frequency: "i" < "love"
answer = ["i", "love"]

Step 3: Complexity

"Counting is O(n), sorting u unique words is O(u log u), and the frequency map uses O(u) space."

Possible follow-ups

  • Can we optimize when k is small? "Yes. A size-k heap can reduce ranking work to O(u log k), but implementing the reversed lexicographical tie-break carefully is important."

中文:本題最重要的差異是雙重排序規則:frequency 降冪、word 字典序升冪。

11. Sort Characters By Frequency

MediumHash MapBucket SortString

題目整理

給字串 s,依每個字元的出現頻率由高到低重新排列;相同字元在結果中要聚在一起。若多個字元頻率相同,可用任意順序。大小寫視為不同字元。公開題目:LeetCode 451

解法說明

先統計每個 character 的 frequency。頻率最大不會超過 len(s),因此可建立 frequency buckets,讓 buckets[f] 保存出現 f 次的字元。最後從最高 frequency 往下掃,把每個字元重複 f 次加入答案。

Frequency 題型家族:Top K Frequent ElementsTop K Frequent Words 都只取前 k 個 item;本題則依 frequency 重建包含所有原始字元的完整字串。
from collections import Counter


class Solution:
    def frequencySort(self, s: str) -> str:
        counts = Counter(s)
        buckets = [[] for _ in range(len(s) + 1)]

        for char, frequency in counts.items():
            buckets[frequency].append(char)

        parts = []
        for frequency in range(len(s), 0, -1):
            for char in buckets[frequency]:
                parts.append(char * frequency)

        return "".join(parts)
Time Complexity: O(n),計數、掃描 buckets 與建立長度 n 的輸出皆為線性。
Space Complexity: O(n),frequency map、buckets 與輸出 parts 最壞皆與輸入長度同階。

Interview Explanation Flow

Step 1: Count frequencies

"The output order depends only on how often each character appears, so I first build a frequency map."

Step 2: Use bounded frequencies

"A character frequency is between 1 and n, so I bucket characters by frequency and scan the buckets from n down to 1."

s = "tree"
counts: t=1, r=1, e=2

bucket[2] = [e]
bucket[1] = [t, r]
output can be "eetr"

Step 3: Complexity

"Every input and output character is processed a constant number of times, so time and space are both O(n)."

Possible follow-ups

  • Why not sort the characters directly? "Sorting the full string costs O(n log n), while bounded frequency buckets give O(n)."

中文:利用 frequency 上限是 n,將字元依次數分 bucket,再從高頻到低頻重建完整字串。

12. Word Pattern

EasyHash MapBijection

題目整理

給 pattern 字串與一個由空白分隔的句子,判斷 pattern 的每個字元是否能與句中的 word 建立一對一對應。

解法說明

需要雙向唯一:同一個 pattern char 只能對應同一個 word,同一個 word 也只能對應同一個 pattern char。用兩個 hash map 維護雙向映射。

class Solution:
    def wordPattern(self, pattern: str, s: str) -> bool:
        words = s.split()
        if len(pattern) != len(words):
            return False

        char_to_word = {}
        word_to_char = {}

        for ch, word in zip(pattern, words):
            if ch in char_to_word and char_to_word[ch] != word:
                return False
            if word in word_to_char and word_to_char[word] != ch:
                return False

            char_to_word[ch] = word
            word_to_char[word] = ch

        return True
Time Complexity: O(n),n 是 words 數與 pattern 長度。
Space Complexity: O(n),兩個 map。

Interview Explanation Flow

Step 1: Define bijection

"This is not just one-way mapping; it must be a bijection between pattern characters and words."

Step 2: Check both directions

"I keep one map from char to word and another from word to char. Any conflict means the pattern fails."

Example:
pattern = "abba"
s = "dog cat cat dog"

a -> dog
b -> cat
b -> cat
a -> dog

answer = True

Step 3: Edge case

"If the number of pattern characters and words differs, return false immediately."

Step 4: Complexity

"Time and space are both O(n)."

中文:關鍵是雙向一對一映射,而不是只檢查 char 到 word。

13. Word Pattern II

MediumBacktrackingHash Map

題目整理

給 pattern 與字串 s,判斷 pattern 中每個字元是否能對應到一個非空 substring,並且整個 pattern 展開後剛好等於 s。不同 pattern 字元不能對應到相同 substring。

解法說明

用 backtracking 逐一處理 pattern 字元。若字元已有映射,就檢查目前字串位置是否以該 substring 開頭;若沒有映射,就嘗試所有可能的非空 substring,並用 set 確保 substring 沒被其他字元使用。

class Solution:
    def wordPatternMatch(self, pattern: str, s: str) -> bool:
        mapping = {}
        used = set()

        def backtrack(pi: int, si: int) -> bool:
            if pi == len(pattern) and si == len(s):
                return True
            if pi == len(pattern) or si == len(s):
                return False

            ch = pattern[pi]
            if ch in mapping:
                word = mapping[ch]
                if not s.startswith(word, si):
                    return False
                return backtrack(pi + 1, si + len(word))

            for end in range(si + 1, len(s) + 1):
                candidate = s[si:end]
                if candidate in used:
                    continue

                mapping[ch] = candidate
                used.add(candidate)
                if backtrack(pi + 1, end):
                    return True
                used.remove(candidate)
                del mapping[ch]

            return False

        return backtrack(0, 0)
Time Complexity: Exponential,最壞情況需要嘗試多種切分。
Space Complexity: O(p + s),遞迴深度與 mapping / used。

Interview Explanation Flow

Step 1: Frame as backtracking

"Unlike Word Pattern I, the words are not pre-split. I need to decide where each substring starts and ends, so this becomes backtracking."

Step 2: Maintain bijection

"I maintain a char-to-substring map and a used set so two pattern characters cannot share the same substring."

Example:
pattern = "abab"
s = "redblueredblue"

try:
a -> "red"
b -> "blue"
a -> "red"
b -> "blue"

entire string consumed -> True

Step 3: Backtrack on failure

"If a candidate substring does not lead to a full match, I remove it and try the next candidate."

Step 4: Complexity

"The worst-case time is exponential because each unmapped pattern character can try many substring lengths."

Possible follow-ups

  • Can we prune? "Yes, we can stop early if the remaining string is too short for remaining unmatched pattern characters."

中文:這題核心是 backtracking 嘗試 substring 映射,同時維持雙向唯一。

14. Time Needed to Inform All Employees

MediumTreeDFS

題目整理

公司有 n 位員工,每個員工有一位 manager,head 會開始通知直屬下屬,每位 manager 通知所有直屬下屬需要 informTime[i] 分鐘。請回傳通知到所有員工所需的最長時間。

解法說明

manager 關係形成一棵以 head 為 root 的樹。通知時間就是 root 到最深 leaf 的 path sum。先建立 manager 到 subordinates 的 adjacency list,再 DFS 計算最大傳遞時間。

from collections import defaultdict
from typing import List


class Solution:
    def numOfMinutes(self, n: int, headID: int, manager: List[int], informTime: List[int]) -> int:
        graph = defaultdict(list)
        for employee, boss in enumerate(manager):
            if boss != -1:
                graph[boss].append(employee)

        def dfs(employee: int) -> int:
            longest = 0
            for subordinate in graph[employee]:
                longest = max(longest, dfs(subordinate))
            return informTime[employee] + longest

        return dfs(headID)
Time Complexity: O(n),每位員工處理一次。
Space Complexity: O(n),graph 與 DFS stack。

Interview Explanation Flow

Step 1: Interpret as a tree

"The manager array defines a rooted tree where the head is the root."

Step 2: Need the longest path

"Employees are informed in parallel by different managers, so the total time is the longest root-to-leaf notification path."

Example:
n = 6, headID = 2
manager = [2,2,-1,2,2,2]
informTime = [0,0,1,0,0,0]

head 2 informs everyone directly in 1 minute
answer = 1

Step 3: DFS recurrence

"For each employee, the time needed below them is their own inform time plus the maximum time among their subordinates."

Step 4: Complexity

"Time and space are both O(n)."

中文:把管理關係看成樹,答案是從 head 到最慢收到通知員工的最長路徑時間。

15. Minimum Interval to Include Each Query

HardSortHeapIntervals

題目整理

給多個 intervals 與 queries。對每個 query,找出包含該 query 的最短 interval 長度;若沒有 interval 包含它,回傳 -1

解法說明

將 intervals 依 start 排序,queries 也依值排序但保留原 index。對每個 query,把所有 start ≤ query 的 intervals 放入 min-heap,heap key 是 interval length;再移除 end < query 的過期 intervals。heap top 就是目前包含 query 的最短 interval。

from heapq import heappop, heappush
from typing import List


class Solution:
    def minInterval(self, intervals: List[List[int]], queries: List[int]) -> List[int]:
        intervals.sort()
        indexed_queries = sorted((query, i) for i, query in enumerate(queries))
        answer = [-1] * len(queries)
        heap = []
        i = 0

        for query, original_index in indexed_queries:
            while i < len(intervals) and intervals[i][0] <= query:
                start, end = intervals[i]
                heappush(heap, (end - start + 1, end))
                i += 1

            while heap and heap[0][1] < query:
                heappop(heap)

            if heap:
                answer[original_index] = heap[0][0]

        return answer
Time Complexity: O((n + q) log n),intervals 進出 heap 各一次。
Space Complexity: O(n + q),heap 與答案。

Interview Explanation Flow

Step 1: Sort queries offline

"Queries are independent, so I can sort them and process from small to large while preserving original indices."

Step 2: Add candidate intervals

"For a query x, any interval with start ≤ x could be a candidate, so I push those into a min-heap by interval length."

Step 3: Remove invalid intervals

"If the heap top has end < x, it cannot cover the query anymore, so I pop it."

Example:
intervals = [[1,4],[2,4],[3,6],[4,4]]
queries = [2,3,4,5]

query 2 -> candidates [1,4], [2,4] -> shortest length 3
query 3 -> candidates include [3,6] -> shortest length 3
query 4 -> [4,4] length 1
query 5 -> [3,6] length 4

answer = [3, 3, 1, 4]

Step 4: Complexity

"Sorting plus heap operations gives O((n + q) log n)."

Possible follow-ups

  • Why heap? "Among all currently valid intervals, we need the smallest length quickly."

中文:排序 query 後用 heap 維護目前能覆蓋 query 的最短 interval。

16. Roman to Integer

EasyParsing

題目整理

給一個 Roman numeral 字串,將它轉成整數。若較小符號出現在較大符號左邊,代表要相減,例如 IV = 4IX = 9

解法說明

從右往左掃最直覺:如果目前值小於右側已看過的最大值,表示它是 subtractive case,要減掉;否則加上並更新最大值。

class Solution:
    def romanToInt(self, s: str) -> int:
        values = {
            "I": 1, "V": 5, "X": 10, "L": 50,
            "C": 100, "D": 500, "M": 1000,
        }

        total = 0
        max_seen = 0

        for ch in reversed(s):
            value = values[ch]
            if value < max_seen:
                total -= value
            else:
                total += value
                max_seen = value

        return total
Time Complexity: O(n),掃描字串一次。
Space Complexity: O(1),固定 Roman symbol map。

Interview Explanation Flow

Step 1: Explain subtractive notation

"Most symbols are added, but a smaller symbol before a larger one should be subtracted."

Step 2: Scan right to left

"By scanning from right to left, I always know whether there is a larger symbol to the right."

Example:
s = "MCMXCIV"

from right:
V = +5
I before V = -1
C = +100
X before C = -10
M = +1000
C before M = -100
M = +1000

answer = 1994

Step 3: Complexity

"Time is O(n), space is O(1)."

中文:從右往左掃,遇到比右側最大值小的符號就減掉。

17. Merge Intervals

MediumSortIntervals

題目整理

給一組 intervals,合併所有重疊區間,回傳不重疊的區間列表。

解法說明

先依 start 排序。逐一掃描 intervals,若目前區間 start 小於等於結果最後一段的 end,代表重疊,更新 end;否則新增一段。

from typing import List


class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        intervals.sort(key=lambda interval: interval[0])
        merged = []

        for start, end in intervals:
            if not merged or start > merged[-1][1]:
                merged.append([start, end])
            else:
                merged[-1][1] = max(merged[-1][1], end)

        return merged
Time Complexity: O(n log n),排序 dominates。
Space Complexity: O(output),不計排序額外空間。

Interview Explanation Flow

Step 1: Sort by start

"Sorting by start makes overlapping intervals appear next to each other."

Step 2: Compare with last merged interval

"For each interval, I only need to compare it with the last interval in the result."

Example:
intervals = [[1,3],[2,6],[8,10],[15,18]]

[1,3] and [2,6] overlap -> [1,6]
[8,10] no overlap -> append
[15,18] no overlap -> append

answer = [[1,6],[8,10],[15,18]]

Step 3: Complexity

"Time is O(n log n) due to sorting."

中文:排序後只要和最後合併區間比較,就能線性完成 merge。

18. String to Integer (atoi)

MediumParsingSimulation

題目整理

將字串轉成 32-bit signed integer。流程包含忽略前導空白、讀取可選正負號、讀取連續數字、遇到非數字停止,最後將結果限制在 [-2^31, 2^31 - 1]

解法說明

依照規則分階段 parsing:先跳過空白,再處理符號,接著累積數字。累積完成後套用正負號並 clamp 到 32-bit 範圍。

class Solution:
    def myAtoi(self, s: str) -> int:
        i = 0
        n = len(s)
        int_min = -(2 ** 31)
        int_max = 2 ** 31 - 1

        while i < n and s[i] == " ":
            i += 1

        sign = 1
        if i < n and s[i] in "+-":
            sign = -1 if s[i] == "-" else 1
            i += 1

        value = 0
        while i < n and s[i].isdigit():
            value = value * 10 + int(s[i])
            i += 1

        value *= sign
        if value < int_min:
            return int_min
        if value > int_max:
            return int_max
        return value
Time Complexity: O(n),最多掃過字串一次。
Space Complexity: O(1),只使用固定變數。

Interview Explanation Flow

Step 1: Treat it as rule-based parsing

"This is not a math problem first; it is a parsing problem with a strict order of rules."

Step 2: Process in phases

"I first skip leading spaces, then read an optional sign, then read digits until the first non-digit character."

Example:
s = "   -42"

skip spaces -> "-42"
sign = -1
digits = 42
answer = -42
Example:
s = "4193 with words"

digits = 4193
stop at space before "with"
answer = 4193

Step 3: Clamp overflow

"After applying the sign, I clamp the result into the 32-bit signed integer range."

Example:
s = "-91283472332"

parsed value is less than -2^31
answer = -2147483648

Step 4: Complexity

"Time is O(n), and space is O(1)."

Possible follow-ups

  • What if the string starts with letters? "No digits are parsed, so the answer is 0."
  • What if there are multiple signs? "Only the first optional sign is valid; the next non-digit stops parsing."

中文:面試時照規則分階段解析:空白、符號、數字、停止、最後 clamp。