8.6 練習
基礎難度
168. Excel Sheet Column Title
7 進制轉換的變種題,需要注意的一點是從 1 開始而不是 0。
題解
問題描述
給定一個正整數 columnNumber
,將其轉換為 Excel 標題字母格式,如:
1 → "A"
2 → "B"
...
26 → "Z"
27 → "AA"
28 → "AB"
...
52 → "AZ"
53 → "BA"
...
701 → "ZY"
702 → "ZZ"
703 → "AAA"
這類似於 26 進位(但 A
代表 1,而不是 0)。
解題思路
這個問題本質上是 進制轉換,但 不像正常的 26 進制,因為:
A
代表1
,Z
代表26
,而不是0~25
。columnNumber
從 1 開始(而不是從 0)。- 這意味著每次計算時,我們需要調整索引,讓
Z
對應26
而不是0
。
核心公式
每次迭代:
index = (columnNumber - 1) % 26
columnNumber = (columnNumber - 1) // 26
columnNumber - 1
確保A=0
,Z=25
,符合數學運算的 0-indexed 編碼。index
用來找對應的字母a[index]
(a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
)。columnNumber = (columnNumber - 1) // 26
用來更新下一輪的數字。
Python 範例程式碼
class Solution:
def convertToTitle(self, columnNumber: int) -> str:
a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
res = ''
while columnNumber > 0:
columnNumber -= 1 # 調整後再處理
res = a[columnNumber % 26] + res # 取得對應字母,插入字串前方
columnNumber //= 26 # 繼續處理更高位數
return res
時間與空間複雜度
- 時間複雜度:,因為每次
columnNumber
除以26
,類似於進制轉換。 - 空間複雜度:,儲存結果的字母數量。
67. Add Binary
字符串加法的變種題。
題解
問題描述
給定兩個二進位字串 a
和 b
,計算它們的和,並以二進位字串的形式返回:
輸入: a = "11", b = "1"
輸出: "100"
輸入: a = "1010", b = "1011"
輸出: "10101"
要求:
a
和b
不含前導0
(除非0
本身)。- 不能使用內建二進位運算(如
int(a, 2) + int(b, 2)
)。
解題思路
這題類似「手算二進位加法」,我們可以從最低位(右側)開始相加,然後記錄進位(carry)。
核心觀察
-
二進位加法規則:
0 + 0 = 0
0 + 1 = 1
1 + 0 = 1
1 + 1 = 10 (0,進位 1)
1 + 1 + 1 = 11 (1,進位 1) -
從右到左遍歷
a
和b
- 使用 雙指針 遍歷
a
和b
,並維護carry
(進位)。 - 若
a[i] + b[j] + carry = 2
,則當前位為0
,進位1
。 - 若
a[i] + b[j] + carry = 3
,則當前位為1
,進位1
。
- 使用 雙指針 遍歷
-
邊界處理
a
和b
可能長度不同,需分別處理較短的數字補0
。- 若最後還有
carry = 1
,則補1
。
Python 範例程式碼
class Solution:
def addBinary(self, a: str, b: str) -> str:
i, j = len(a) - 1, len(b) - 1 # 從最後一位開始
carry = 0
res = []
while i >= 0 or j >= 0 or carry:
bit_a = int(a[i]) if i >= 0 else 0 # 若 i < 0 則當作 0
bit_b = int(b[j]) if j >= 0 else 0 # 若 j < 0 則當作 0
total = bit_a + bit_b + carry
res.append(str(total % 2)) # 記錄當前位
carry = total // 2 # 更新進位
i -= 1
j -= 1
return ''.join(res[::-1]) # 反轉結果得到正確順序
時間與空間複雜度
- 時間複雜度:,
m
和n
為a
和b
的長度,每位數字加法最多遍歷一次。 - 空間複雜度:,儲存結果的陣列。
238. Product of Array Except Self
你可以不使用除法做這道題嗎?我們很早之前講過的題目 135 或許能給你一些思路。
題解
問題描述
給定一個整數陣列 nums
,返回一個新的陣列 answer
,其中 answer[i]
等於 nums
陣列中所有數字的乘積,除了 nums[i]
本身。
要求:
- 不能使用除法。
- 時間複雜度為
O(n)
。
解題思路
這是一道 前綴積(Prefix Product)+ 後綴積(Suffix Product) 的問題,可以用 兩次遍歷(不額外使用除法) 來解決。
核心觀察
- 直接暴力解法:計算每個
i
位置時,遍歷整個陣列nums
來計算乘積,這樣會導致 時間複雜度,不符合要求。 - 使用
prefix
和suffix
:prefix[i]
:表示nums[0]
到nums[i-1]
的乘積(不包含nums[i]
)。suffix[i]
:表示nums[i+1]
到nums[n-1]
的乘積(不包含nums[i]
)。- 最終結果
answer[i] = prefix[i] * suffix[i]
。
步驟
- 先計算前綴積(從左到右遍歷):
prefix[i] = prefix[i-1] * nums[i-1]
- 再計算後綴積並同時更新答案(從右到左遍歷):
suffix
變數記錄從右側開始的乘積,並與prefix
相乘得出answer[i]
。
Python 範例程式碼
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
answer = [1] * n # 初始化結果陣列
# 計算 prefix(前綴積)
prefix = 1
for i in range(n):
answer[i] = prefix # 存前綴乘積
prefix *= nums[i] # 更新前綴積
# 計算 suffix(後綴積)並直接更新 answer
suffix = 1
for i in range(n - 1, -1, -1):
answer[i] *= suffix # 用 suffix 更新 answer
suffix *= nums[i] # 更新後綴積
return answer
時間與空間複雜度
- 時間複雜度:,兩次遍歷(前綴 + 後綴)。
- 空間複雜度:(只使用
answer
來存結果,額外變數prefix
和suffix
只佔 空間)。
進階難度
462. Minimum Moves to Equal Array Elements II
這道題是筆者最喜歡的 LeetCode 題目之一,需要先推理出怎麼樣移動是最優的,再考慮如何進行移動。你或許需要一些前些章節講過的算法。
題解
問題描述
給定一個整數陣列 nums
,你可以對任意元素加 1 或減 1,問至少需要多少步,使所有數字相等。
範例
輸入: nums = [1,2,3]
輸出: 2
解釋: 讓所有數變成 2
(1 → 2, 3 → 2),共需要 2 步。
輸入: nums = [1,10,2,9]
輸出: 16
解題思路
這是一個 數學 + 貪心 的問題,本質上是要 讓所有數字趨近於某個最佳值,使得總變動步數最少。
核心觀察
-
最佳值是中位數
- 假設
nums
有n
個數字,最優方案是讓所有數變成中位數median
,這樣變動的總步數最少。 - 這是因為「中位數」能最小化距離總和,這也是「L1 距離(曼哈頓距離)」的特性。
- 假設
-
為什麼不是平均數?
- 平均數適合平方誤差最小化(L2 距離),但這題要求的是「步數最少」,應該最小化「絕對誤差總和」。
- 最小化「絕對誤差總和」的最佳點就是中位數。
-
求解步驟
- 先對
nums
排序。 - 找出 中位數
median
(若n
為奇數,取中間數;若n
為偶數,取靠左的中間數)。 - 計算 所有數變成
median
的總步數。
- 先對
Python 範例程式碼
class Solution:
def minMoves2(self, nums: List[int]) -> int:
nums.sort() # 先排序
median = nums[len(nums) // 2] # 找中位數
return sum(abs(num - median) for num in nums) # 計算總步數
時間與空間複雜度
- 時間複雜度:(排序
nums
)。 - 空間複雜度:(只使用了變數
median
和sum
計算)。
169. Majority Element
如果想不出簡單的解決方法,搜尋一下 Boyer-Moore Majority Vote 算法吧。
題解
問題描述
給定一個長度為 n
的陣列 nums
,找出出現超過 ⌊ n/2 ⌋
次的眾數(Majority Element)。
保證一定存在這樣的數字。
解題思路
這是一道經典的 眾數問題(Majority Element Problem),最佳解法是 Boyer-Moore 投票算法。
方法 1:Boyer-Moore 投票算法
-
核心概念:
- 既然眾數出現的次數超過一半,我們可以使用「候選人 + 投票機制」來篩選它。
- 遍歷
nums
時,維護一個count
計數變數:- 當
count = 0
時,設當前數num
為候選眾數。 - 遇到相同數時
count +1
,遇到不同數時count -1
。
- 當
- 最終候選數就是眾數,因為眾數一定超過 50%,所以它不會被完全抵消。
-
時間複雜度:(只遍歷一次)。
-
空間複雜度:(只用變數
candidate
和count
)。
Python 範例程式碼
class Solution:
def majorityElement(self, nums: List[int]) -> int:
candidate, count = None, 0
for num in nums:
if count == 0:
candidate = num
count += 1 if num == candidate else -1
return candidate
470. Implement Rand10() Using Rand7()
如何用一個隨機數生成器生成另一個隨機數生成器?你可能需要利用原來的生成器多次。
題解
問題描述
已知函數 rand7()
可均勻隨機返回 1 ~ 7 之間的整數,請實現 rand10()
,使其均勻隨機返回 1 ~ 10 之間的整數。
不允許直接使用系統內建的隨機函數。
解題思路
這是一道 隨機數生成與拒絕採樣(Rejection Sampling) 的經典問題,關鍵是:
- 如何從
rand7()
得到rand10()
? - 如何確保數字是均勻分佈的?
關鍵步驟
- 將
rand7()
轉換為更大的均勻分佈範圍rand7()
只能產生 1 ~ 7 的隨機數。- 我們可以呼叫兩次
rand7()
,構造一個 1 ~ 49 均勻分佈的數:index = (rand7() - 1) * 7 + rand7() # 生成範圍 [1, 49]
- 利用
49
的數據範圍轉換成1 ~ 10
49
是7 × 7
,我們可以只取前 40 個數(剛好是10 × 4
),然後mod 10 + 1
。- 如果
index > 40
,則丟棄並重新生成(拒絕採樣),這樣可以保證均勻性:如果 index ≤ 40,則 return index % 10 + 1
否則,重新取樣
- 為什麼丟棄
41 ~ 49
?1 ~ 40
剛好可以平均分成10
組,每組4
個數。41 ~ 49
無法均勻分組,所以 要丟棄,否則rand10()
會偏向某些數。
Python 範例程式碼
import random
def rand7():
return random.randint(1, 7)
class Solution:
def rand10(self) -> int:
while True:
index = (rand7() - 1) * 7 + rand7() # 生成範圍 [1, 49]
if index <= 40: # 只取前 40 個,確保均勻分佈
return index % 10 + 1
時間與空間複雜度
- 時間複雜度:期望
rand7()
生成1 ~ 49
的機率均等,超過40
的數字被拒絕,期望迴圈次數為49/40 ≈ 1.225
,即 期望常數次數內可完成。
- 空間複雜度:(只使用了變數
index
)。
202. Happy Number
你可以簡單的用一個 while
循環解決這道題,但是有沒有更好的解決辦法?如果我們把每個數字想像成一個節點,是否可以轉化為環路檢測?
題解
問題描述
給定一個正整數 n
,如果按照以下規則最終會變成 1
,則稱為「快樂數(Happy Number)」:
- 取
n
每個數字的平方和,得到新的n
。 - 重複這個過程,直到:
n
變成1
(快樂數)。- 或者進入循環(不是快樂數)。
範例
輸入: n = 19
計算過程:
19 → 1² + 9² = 82
82 → 8² + 2² = 68
68 → 6² + 8² = 100
100 → 1² + 0² + 0² = 1 ✅
輸出: True
輸入: n = 2
計算過程:
2 → 2² = 4
4 → 4² = 16
16 → 1² + 6² = 37
37 → 3² + 7² = 58
58 → 5² + 8² = 89
89 → 8² + 9² = 145
145 → 1² + 4² + 5² = 42
42 → 4² + 2² = 20
20 → 2² + 0² = 4(重複循環 ❌)
輸出: False
解題思路
這是一道典型的 循環檢測 + 數位拆解運算 的問題,可以用 快慢指針法(Floyd Cycle Detection) 來高效解決。
方法 1:快慢指針(Floyd Cycle Detection)
核心思想:
- 如果
n
是「快樂數」,它最終會變成1
。 - 如果
n
不是快樂數,則會進入循環(類似 Linked List 環檢測)。 - 用 快慢指針(slow & fast) 來檢測循環:
- slow 每次執行
square_sum(n)
一次。 - fast 每次執行
square_sum(n)
兩次。 - 若
slow == fast
(但不是1
),代表進入循環,回傳False
。 - 若
fast == 1
,則n
是快樂數,回傳True
。
- slow 每次執行
Python 範例程式碼
class Solution:
def isHappy(self, n: int) -> bool:
def square_sum(num):
return sum(int(digit) ** 2 for digit in str(num))
slow, fast = n, square_sum(n)
while fast != 1 and slow != fast:
slow = square_sum(slow)
fast = square_sum(square_sum(fast))
return fast == 1
時間與空間複雜度
- 時間複雜度:
- 每次計算
square_sum(n)
會讓數字縮小(最終 ≤ 81),執行次數有限。
- 每次計算
- 空間複雜度:
- 只使用了
slow
和fast
指針,沒有額外的記錄。
- 只使用了