時間複雜度的理解以及常見的演算法

更新於 2024/12/08閱讀時間約 25 分鐘


New Zealand - Deer Park Heights Queenstown

New Zealand - Deer Park Heights Queenstown

前言


常在寫 leet code 的朋友應該都知道,除了解出題目很重要,還有時間複雜度以及空間複雜度的問題,若能更理解時間複雜度,然後優化解法,就可以讓寫程式的基本底子更上一層樓,以下簡單介紹常見的時間複雜度以及演算法。

除了時間複雜度,也會順便簡單說明相關東西,如下:

相關演算法:

  • 二元搜尋(Binary search)
  • 快速排序(Quick sort)
  • 合併排序(Merge sort)
  • 氣泡排序(Bubble sort)
  • 插入排序(Insertion sort)
  • 動態規劃(Dynamic Programming)
  • 貪婪演算法(Greedy Algorithm)


經典問題:

  • 子集問題(Subset problem)
  • 背包問題(Knapsack problem)
  • 旅行推銷員問題(Travelling Salesman Problem, TSP)

其他相關知識:

  • 費氏數列(Fibonacci Sequence)

時間複雜度


O(n) — 線性時間複雜度

演算法的執行時間隨輸入資料規模成正比。例如,for-loop 一個長度為 n 的陣列,每個值都訪問一次,這就是 O(n)。


O(1) — 常數時間複雜度

這種複雜度表示演算法的執行時間不會隨輸入資料規模改變。例如,從陣列中取得某個值

let array = [5, 2, 10]

array[1] // 2

array[index] 的操作是 O(1)。 還有像是 hash table 的讀取,平均起來也是 O(1),只不過在最壞的情況,設計不良,導致大量碰撞,那麼所有的值可能被存放在同一個桶(bucket)中,哈希表的時間複雜度就會退化到 O(n),這與線性結構(如鏈表)相似。

Hash Table(哈希表)

簡單說明什麼 Hash Table(哈希表),它是一種用來快速查找數據的資料結構。它能夠在幾乎時間(O(1))內完成插入、刪除和查找操作,這讓它非常高效,特別是在需要頻繁存取的情況下。 如何運作?

  • 鍵(Key)和值(Value):Hash Table 使用一個「鍵」來對應到一個「值」。比如說,你可以用「名字」作為鍵,然後將「電話號碼」作為值存入 Hash Table 裡。
  • 哈希函數(Hash Function):為了快速找到某個鍵對應的值,Hash Table 使用一個哈希函數來將鍵轉換成一個「哈希值」。這個哈希值是一個數字,用來決定這個鍵應該存放在哪裡。
  • 儲存結構:Hash Table 本質上是一個陣列,哈希函數會將鍵轉換成一個索引值,然後將值儲存在這個位置。

所以可以想像,假設今天想在電話簿搜尋某個人的電話,一個一個查找一定是最慢的,假設我們直接輸入一些名字關鍵字,例如要找的人叫做 Tim,我們搜尋 T,就會馬上篩選出 T 開頭的人,這樣查找下來一定是比一個一個找還快。


O(log n) — 對數時間複雜度

這種複雜度表示每次迭代或操作都能將問題規模縮小一半,常見於二分搜尋演算法。例如,搜尋一個已排序的陣列中的某個值就是 O(log n)。

  • 二分搜尋法

raw-image

假設你有一個已經按大小排序的陣列,比如說 [2, 4, 6, 8, 10, 12, 14],你想要找出 10 的位置。二分搜尋的步驟如下:

  1. 設定起點和終點:一開始,設定兩個指標:左邊界 指向數列的起始位置,右邊界 指向數列的結尾位置。
  2. 找中點:計算中點的位置 mid = (左邊界 + 右邊界) / 2,然後檢查中點的值。
  3. 比較中點的值
  • 如果中點的值等於你要找的目標值,搜尋成功,返回中點的位置。
  • 如果中點的值比目標值小,目標值一定在中點的右邊。因此,你將 左邊界 移到 mid + 1
  • 如果中點的值比目標值大,目標值一定在中點的左邊。因此,你將 右邊界 移到 mid - 1

4. 重複步驟 2 和 3,直到找到目標值或搜尋範圍變空(即 左邊界 超過 右邊界),代表目標值不在數列中。

轉換成程式碼的話

func binarySearch(array: [Int], target: Int) -> Int? {

var left = 0

var right = array.count - 1

while left <= right {

let mid = (left + right) / 2

let midValue = array[mid]

if midValue == target {

return mid // 找到目標值,返回索引

} else if midValue < target {

left = mid + 1 // 目標在右邊,縮小搜尋範圍

} else {

right = mid - 1 // 目標在左邊,縮小搜尋範圍

} } return nil // 找不到目標值

}// 使用範例

let numbers = [2, 4, 6, 8, 10, 12, 14]

if let index = binarySearch(array: numbers, target: 10) {

print("找到了!數字 10 在索引 \(index) 上。")

} else {

print("找不到目標數字。")

}


O(n log n) — 線性對數時間複雜度

常見於高效排序演算法,如快速排序(Quick Sort)和合併排序(Merge Sort)。這種複雜度表示執行的操作數量大約是 n 個值進行 log n 次處理。這兩個排序法都分而治之(Divide and Conquer) 的策略來解決排序問題。

  • 快速排序(Quick Sort):

raw-image

原理

快速排序通過選擇一個「基準點(pivot)」來將陣列劃分成兩個部分:

  • 所有小於基準點的值移到基準點的左邊
  • 所有大於基準點的值移到基準點的右邊

這樣將陣列分割成較小的部分後,對左右部分遞迴地進行快速排序,直到每個部分只有一個或零個值,排序完成。

步驟

  1. 從陣列中選擇一個基準點(通常可以選擇第一個、最後一個或中間的值,或者隨機選擇)。
  2. 將陣列分成兩部分:一部分的所有值都小於基準點,另一部分的所有值都大於基準點。
  3. 對兩個部分分別遞迴地應用快速排序。
  4. 合併結果,最終得到排序好的陣列。

時間複雜度

  • 最壞情況: O(n²),當基準點選擇不佳(例如,已排序或逆序陣列中每次選擇的基準點都是最大或最小值)。
  • 平均情況: O(n log n),當基準點將陣列合理地劃分成大致相等的兩部分時。
  • 最好情況: O(n log n),當每次分割都能將陣列對半分時。

優點

  • 快速排序在一般情況下比其他 O(n log n) 排序演算法更快,因為其內部處理過程優化了內存使用,並且常數因子較小。
  • 原地排序(in-place),不需要額外的空間來存儲數據。

缺點

  • 不穩定排序演算法(不保留相同值之間的相對順序)。
  • 對於基準點選擇不佳的情況,效率會顯著降低。


  • 合併排序(Merge Sort):

raw-image

原理

合併排序將陣列分成兩個部分,遞迴地將每個部分繼續分割,直到每個部分只有一個值。然後將這些部分進行合併,將兩個已排序的小陣列合併成一個較大的已排序陣列,直到合併完成。

步驟

  1. 將陣列對半分割,直到每個子陣列只有一個值。
  2. 將兩個已排序的子陣列合併成一個排序好的陣列,直到合併成最終排序好的陣列。
  3. 遞迴重複以上步驟。

時間複雜度

  • 最壞情況: O(n log n)
  • 平均情況: O(n log n)
  • 最好情況: O(n log n)

優點

  • 穩定排序演算法(相同值的順序保持不變)。
  • 對於大型陣列具有穩定的性能,無論初始數據排列方式如何,時間複雜度始終是 O(n log n)。

缺點

  • 需要額外的空間來存儲臨時數據,空間複雜度為 O(n)。
  • 相較於原地排序演算法,對內存使用較多,尤其在處理大型數據集時。


比較兩個排序法

空間複雜度:

  • 快速排序: O(1)(原地排序,不需要額外空間)
  • 合併排序: O(n)(需要額外空間來合併子陣列)

穩定性:

  • 快速排序: 不穩定
  • 合併排序: 穩定

性能:

  • 快速排序通常比合併排序更快,但在最壞情況下可能表現不佳。
  • 合併排序性能穩定,適合大型數據集。



O(n²) — 平方時間複雜度

這種複雜度通常來自巢狀迴圈(nested loops)。例如,對一個 n 值的陣列執行兩層巢狀迴圈,每個值都與其他值配對,這就是 O(n²)。像是氣泡排序(Bubble Sort)和插入排序(Insertion Sort)這類排序演算法。

  • 氣泡排序(Bubble Sort)

raw-image

原理

氣泡排序透過不斷比較和交換相鄰的值,將較大的值「冒泡」到陣列的末端。這個過程會多次重複,直到整個陣列排序完成。

步驟

  1. 從陣列的第一個值開始,與相鄰的值比較大小。
  2. 如果前一個值大於後一個值,交換它們的位置。
  3. 繼續向後比較並交換,直到陣列的末尾。這樣最大或最小的值會被移動到末尾位置。
  4. 重複這個過程 n-1 次(n 是陣列長度),直到沒有需要交換的值為止。

時間複雜度

  • 最壞情況: O(n²),當陣列為逆序排列時,需進行大量交換操作。
  • 平均情況: O(n²)
  • 最好情況: O(n),當陣列已排序時,只需一輪比較即可。

優點

  • 實現簡單,適合教學和小型資料集。
  • 若陣列已大部分排序,則可以進行優化(例如,設置一個標誌變數來檢測是否發生交換,若沒有交換則排序完成)。

缺點

  • 效率低,當資料規模增大時表現不佳,時間複雜度較高。
  • 無法有效應對大數據集,因為它的交換次數較多。


  • 插入排序(Insertion Sort)

raw-image

原理

插入排序通過逐個處理每個值,將其插入到前面已排序部分的適當位置。每次處理一個新的值時,會從後往前逐一比較,將該值插入到正確位置。

步驟

  1. 將第一個值視為已排序部分,從第二個值開始處理每個值。
  2. 將當前值與前面已排序部分的值逐一比較,找到適合插入的位置,並將較大的值向後移動。
  3. 重複這個過程,直到所有值都被插入到正確的位置。

時間複雜度

  • 最壞情況: O(n²),當陣列為逆序排列時,需進行大量移動操作。
  • 平均情況: O(n²)
  • 最好情況: O(n),當陣列已排序或接近排序時,插入排序會非常高效。

優點

  • 實現簡單,適合小型資料集或部分排序的資料集。
  • 穩定排序演算法,保持相同值的相對順序不變。
  • 當資料幾乎排序時,插入排序表現非常好,效率高。

缺點

  • 效率低,尤其在大規模資料集上,時間複雜度較高。
  • 對於未排序或亂序程度高的陣列,表現不如其他高效演算法(如快速排序或合併排序)。

比較兩個排序法

簡單性:

  • 氣泡排序和插入排序都非常容易理解和實現,適合教學使用。

效率:

  • 氣泡排序的效率較低,因為交換操作多,較少應用於實際問題。
  • 插入排序在小型資料集或部分排序的資料上表現良好,效率較高。

穩定性:

  • 氣泡排序和插入排序都是穩定的排序演算法。

什麼時候使用?

  • 氣泡排序 幾乎不會在實際應用中使用,只適合教學目的。
  • 插入排序 適合小型資料集,或用於已部分排序的資料集進行微調。

 

O(2^n) — 指數時間複雜度

這種複雜度非常高,代表問題的規模每增加一個單位,執行時間會指數增長。例如,解決所有可能的子集問題(Subset Problem)、背包問題(Knapsack Problem)、費氏數列(Fibonacci Sequence)或某些遞迴演算法,在實際應用中,通常需要使用更高效的演算法,例如,動態規劃(Dynamic Programming)或貪婪演算法(Greedy Algorithm)來替代指數時間複雜度的解法,以便處理更大規模的問題。

  • 子集問題(Subset Problem)

假設你有一個包含 n 個值的集合,並且你想找到這個集合的所有子集。每個值都有兩種狀態:包含在子集中或不包含在子集中。這樣就有 2^n 個可能的子集。當集合的大小增長時,計算所有子集的時間將以 2^n 的速度增長。

舉例來說,假設有集合 {A, B, C},我們希望找出這個集合的所有子集。

  • 空集合 {} → 1 種
  • 單值子集 {A}, {B}, {C} → 3 種
  • 雙值子集 {A, B}, {A, C}, {B, C} → 3 種
  • 全集合 {A, B, C} → 1 種

這樣等於 1 + 3 + 3 + 1 = 8 ,也就是有 2³ = 8,共有8種。

  • 背包問題(Knapsack Problem)

背包問題是一個經典的優化問題,問題是這樣的:

假設你有一個背包,這個背包有一個最大容量(如重量限制)。

你有 n 個物品,每個物品都有自己的重量和價值。

目標是選擇一些物品放入背包,使得在不超過背包容量的情況下,選中的物品總價值最大。

假設你有一個背包,最大承重為 4 公斤,你有以下物品可以選擇:

  1. 餅乾:重量 1 公斤,價值 3
  2. 蘋果:重量 3 公斤,價值 4
  3. 水:重量 4 公斤,價值 6

你需要找到一個選擇方案,使得放入背包的物品總價值最大且不超過 4 公斤。例如,選擇餅乾和 蘋果可以獲得總價值 3 + 4 = 7,而總重量剛好是 1 + 3 = 4 公斤。

raw-image

我們先用圖表來呈現,會比較好理解,用二維分別代表物品和重量,程式裡我們常會用 dp[i][j] ,i 通常代表物品而 j 代表重量,當然想用其他的詞代替也可以。

raw-image

我們先把最簡單的填完,假設背包只有0公斤的時候,所有物品都不能裝,所以0公斤那行全部為價值0。


raw-image

再來想像現在就只有餅乾這個物品,所以餅乾那列全部只能得到價值3,就算背包有2公斤以上都一樣,因為現在能選的物品只有餅乾。


raw-image

再來就有趣了,因為現在出現了新的物品可以選擇,所以我們就一格一格講解

  • [1, 1] 這格,背包有1公斤,所以只能選擇餅乾,所以是價值3
  • [1, 2] 這格,背包有2公斤,目前也只能選擇餅乾裝,蘋果3公斤還無法選擇
  • [1, 3] 這格,背包有3公斤,我們可以選擇蘋果了,很明顯3公斤時,選擇蘋果是最佳解,他有價值4,大於餅乾的價值3,所以這格填4。
  • [1, 4] 這格,背包有4公斤,我們很容易知道,這時剛好可以放兩個東西,把餅乾跟蘋果都裝進來,剛好是4公斤,所以價值是 3 + 4 = 7,用程式表示就是 dp[1][4] 。


raw-image

最後是加入了水這個物品,它有4公斤價值6,但我相信這列一定很好填,背包4公斤前都和上面那列一樣,因為水太重了,不能裝,而只有到4公斤的時候要考慮是否要裝水還是選上列 (dp[1][4]) ,但很明顯只選水只有6的價值,所以要選 dp[1][4] 價值7才是最佳解。

所以當背包有4公斤時最佳選擇就是選餅乾和蘋果,價值最大化7,用程式表示就是 dp[1][4] 。

因為這邊物品很少,所以要推理出來很快,假如遇到物品很多的時候可能就很難那麼直覺知道答案,所以可以用推理的方式,要取得 dp[1][4] ,有兩個情況:

  1. 不放蘋果
  2. 放蘋果

如果選 1 不放蘋果,表示背包價值是 dp[0][4] 價值3,因為只能放餅乾了。

如果選 2 放蘋果,表示要把背包的重量扣掉蘋果的重量,那就是 4kg — 3kg = 1kg,表示還有一公斤的重量可以放東西,那麼我們前面就推算出來剩一公斤時且只能選餅乾的 dp[0][1] 的最大價值是3,所以最後就變成放入蘋果的情況: dp[0][1] + 蘋果的價值。

因為我們要取得最大值,最後會變成這樣:

dp[1][4] = max(dp[0][4], dp[0][1] + 蘋果的價值)

以上過程在抽象化會變成:

  • 不放物品 i:背包容量為 j,裡面不放物品 i 的最大價值是 dp[i - 1][j]
  • 放物品 i:背包空出物品 i 的空間後,背包重量為 j - weight[i] 。 dp[i - 1][j - weight[i]] 為背包重量為 j - weight[i] 且不放物品 i 的最大價值,那麼 dp[i - 1][j - weight[i]] + value[i] (物品 i 的價值),就是背包放物品 i 得到的最大價值。

最後就會得到 dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])

這問題的優化方法就是使用 動態規劃(Dynamic Programming) 可以將問題的時間複雜度降到 O(n * W),其中 W 是背包的容量。這個方法更高效,適用於大多數實際情況,後續會再說明清楚如何用動態規劃解決這類型問題。


  • 費氏數列(Fibonacci Sequence)

費氏數列是一個著名的數學序列,定義如下:

  • 第一個數字是 0,第二個數字是 1。
  • 從第三個數字開始,每個數字都是前兩個數字的和。

數列的表達式可以用遞迴公式表示為:

  • F(0) = 0
  • F(1) = 1
  • F(n) = F(n-1) + F(n-2),當 n ≥ 2

這樣,我們得到費氏數列的前幾個數字:

  • 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, …

最簡單但效率不高的方法是用遞迴實作費氏數列。

遞迴解法直接依照定義來計算:

func fibonacci(_ n: Int) -> Int {

if n <= 1 {

return n

} return fibonacci(n - 1) + fibonacci(n - 2)

}
  • 例如,計算 fibonacci(5) 時,函數會展開成:
fibonacci(5) = fibonacci(4) + fibonacci(3)

fibonacci(4) = fibonacci(3) + fibonacci(2)

fibonacci(3) = fibonacci(2) + fibonacci(1)

fibonacci(2) = fibonacci(1) + fibonacci(0)

因為每次都會重複計算,導致非常多的冗餘運算,像 fibonacci(3) 會被計算多次。這種遞迴方法的時間複雜度是 O(2^n),因為每次呼叫都會分裂成兩個新的遞迴呼叫。隨著 n 增加,遞迴樹的節點數量以指數速度增長,導致計算非常低效。


  • 動態規劃(Dynamic Programming)

動態規劃是一種將問題拆解成重複子問題,並通過儲存每個子問題的解來避免重複計算的技術。這種方法非常適合解決具有 重疊子問題(Overlapping Subproblems) 和 最優子結構(Optimal Substructure) 性質的問題。

  • 重疊子問題:指的是一個大問題可以被分解成許多重複的小問題。例如,剛剛上述說的費氏數列的遞迴計算中,就是有大量重複的子問題,假如每次把算過的資料存起來,就不用一直重複做一樣的事情。
  • 最優子結構:指的是一個問題的最優解可以由其子問題的最優解構成。例如,最短路徑問題的解可以由多個更小的最短路徑構成。

解決思路

動態規劃通常使用一個表格來儲存子問題的解。可以有兩種方式來實作動態規劃:

  1. 自頂向下(Top-Down Approach,記憶化):使用遞迴來分解問題,但在每次計算子問題時,會先檢查是否已經計算過並儲存在表格中。如果有,直接返回儲存的結果,否則計算並儲存。
  2. 自底向上(Bottom-Up Approach):從最小的子問題開始解決,逐步計算出更大的問題,並將結果儲存在表格中。

常見例子

  1. 費氏數列:用動態規劃可以將時間複雜度從 O(2^n) 降到 O(n)。
  2. 最長公共子序列問題(Longest Common Subsequence, LCS):找出兩個序列的最長公共子序列。
  3. 背包問題(Knapsack Problem):求在容量限制下能夠獲得的最大價值。

用費氏數列來舉例,態規劃方法可以儲存之前計算過的結果,避免重複計算,從而顯著提高效率。

func fibonacci(_ n: Int) -> Int {

if n <= 1 {

return n

} var fib = [0, 1]

for i in 2...n {

fib.append(fib[i - 1] + fib[i - 2])

} return fib[n]

}


  • 貪婪演算法(Greedy Algorithm)

概念介紹

貪婪演算法是一種在每一步選擇中都作出當前看起來最好的選擇,希望這樣能夠導致全局最優解。貪婪演算法不會回溯或考慮所有可能的情況,它依賴於當前的選擇,因此通常更高效但不一定總能得到最優解。

關鍵點:貪婪演算法的應用場景通常要求問題具有 貪婪選擇性質(Greedy Choice Property),也就是說,局部最優選擇能導致全局最優解。

  • 最優子結構:問題的最優解可以分解成子問題的最優解。

解決思路

  1. 從問題的初始狀態開始,依次選擇當前的最佳選擇。
  2. 一旦做出選擇,就不可改變(即不回溯)。

常見例子

  1. 找零問題:使用最少數量的硬幣找零,例如用 1 元、5 元、10 元硬幣找出某個金額。
  2. 最小生成樹問題(Minimum Spanning Tree, MST):Kruskal 演算法和 Prim 演算法都是貪婪演算法。
  3. 活動選擇問題(Activity Selection Problem):選擇最多數量的不相互重疊的活動。


O(n!) — 階乘時間複雜度

階乘複雜度意味著所有排列組合都必須被計算和處理,常見於暴力解法解決排列問題。例如,求解一個 n 個城市的旅行推銷員問題。

階乘時間複雜度是指演算法的執行時間隨輸入大小 n 的階乘(n!)數量級增長。數學上,階乘 n! 定義為:

n!=n×(n−1)×(n−2)×⋯×2×1

對於較小的 n,n! 的增長速度非常快。例如:

  • 3!=3×2×1=63!=3×2×1=6
  • 5!=5×4×3×2×1=1205!=5×4×3×2×1=120
  • 10!=3,628,80010!=3,628,800

旅行推銷員問題(Travelling Salesman Problem, TSP)

是這樣一個問題:假設你是一個推銷員,必須去不同的城市做生意。你的任務是設計一條路線,從某個城市出發,拜訪所有的城市一次,最後回到出發城市,而且希望總行程最短。

關鍵點

  • 你有很多城市要走,但每個城市只能去一次。
  • 你想找到最短的路線,節省旅途時間和成本。
  • 這個問題變得很難,因為當城市數量增加,可能的路線數量會爆炸式增長。

解法

  • 暴力解法(Brute Force Approach):枚舉所有可能的城市訪問順序,計算每條路徑的總距離,然後選擇距離最短的路徑。這種方法需要考慮所有 n! 種排列,時間複雜度為 O(n!)。由於 O(n!) 的增長速度極快,當 n 稍微大一些時,暴力解法在現實中是不可行的。
  • 動態規劃 — Held-Karp 演算法
  • 分支界限法(Branch and Bound)

一個小結


  • 低複雜度(如 O(1)、O(log n) 和 O(n))通常代表演算法較為高效,適合大規模資料。
  • 高複雜度(如 O(n²)、O(2^n) 和 O(n!))則代表演算法隨著輸入規模增加,性能會急劇下降。
avatar-img
0會員
5內容數
留言0
查看全部
avatar-img
發表第一個留言支持創作者!
CHENGYANG的沙龍 的其他內容
Why macros? 最早能從一些 protocol 發現這個特性(Derived protocol conformance),例如 Codable 這樣就可以避免寫重複樣版的程式碼 其實目前在 Swift 中已經有大量的例子,我們開發者只需要寫一些簡單的語法,編譯器就會自動生成一段複
觀看 Create parametric 3D room scans with RoomPlan 筆記 Recap 21 年推出 Object Capture,拍攝真實世界的物體照片,透過 RealityKit 的 Photogrammetry API,合成 App 用的 3D 模型
Why macros? 最早能從一些 protocol 發現這個特性(Derived protocol conformance),例如 Codable 這樣就可以避免寫重複樣版的程式碼 其實目前在 Swift 中已經有大量的例子,我們開發者只需要寫一些簡單的語法,編譯器就會自動生成一段複
觀看 Create parametric 3D room scans with RoomPlan 筆記 Recap 21 年推出 Object Capture,拍攝真實世界的物體照片,透過 RealityKit 的 Photogrammetry API,合成 App 用的 3D 模型
你可能也想看
Google News 追蹤
Thumbnail
本文探討了複利效應的重要性,並藉由巴菲特的投資理念,說明如何選擇穩定產生正報酬的資產及長期持有的核心理念。透過定期定額的投資方式,不僅能減少情緒影響,還能持續參與全球股市的發展。此外,文中介紹了使用國泰 Cube App 的便利性及低手續費,幫助投資者簡化投資流程,達成長期穩定增長的財務目標。
Thumbnail
題目敘述 Sort Array by Increasing Frequency Leetcode #1636 給定一個輸入陣列,請依照出現頻率的多寡從低頻到高頻排列陣列元素。 如果有兩個元素的出現頻率相同,依照元素大小從大到小排列。 測試範例 Example 1: Input: nums
Thumbnail
LeetCode 是一個程式語言版的線上題庫平臺,提供題目描述、程式碼區塊、解題者分享的解法和疑問討論。藉由這篇文章分享我在 LeetCode 上的使用經驗和觀點,包括刷題的重要性、解題心態和練習目標。
Thumbnail
題目敘述 Patching Array 題目給定一個整數陣列, 請問還要補上多少個數字,才能用這些數字的和拼湊出所有1~n的整數。
Thumbnail
題目敘述 Longest Arithmetic Subsequence of Given Difference 給定一個整數陣列nums,請找出給定公差difference的最長的等差數列的長度是多少?
Thumbnail
題目敘述 Solving Questions With Brainpower 給定一個測驗題陣列,每個欄位都是一個pair, 分別記錄測驗題做完可以得到的分數,和需要的冷卻時間 (也就是會有一段時間不能作答接下來的題目)。 請問在最佳的答題策略下,最多可以獲得多少分數?
Thumbnail
給定一個字串陣列,請把它們所共有的字元伴隨著出現次數輸出。這篇文章介紹如何使用字典統計出現次數,和字典取交集的方法來解決此問題。並提供了複雜度分析和關鍵知識點。
Thumbnail
給定一個字串s,以s擁有的字元製造迴文字串。要能製造出的迴文字串長度最長是多少,觀察迴文字串不外乎兩種模式對稱部分 + 核心字元 + 對稱部分,其中,核心字元在正中央出現一次,或者 對稱部分 + 對稱部分。使用演算法統計出現次數並推理出盡可能充分利用每個字元的迴文字串製造方法。
Thumbnail
題目敘述 題目會給定一個整數陣列arr,要求我們判斷是否每個元素的出現次數都不同? 題目的原文敘述 測試範例 Example 1: Input: arr = [1,2,2,1,1,3] Output: true Explanation: The value 1 has 3 occurre
Thumbnail
本文探討了複利效應的重要性,並藉由巴菲特的投資理念,說明如何選擇穩定產生正報酬的資產及長期持有的核心理念。透過定期定額的投資方式,不僅能減少情緒影響,還能持續參與全球股市的發展。此外,文中介紹了使用國泰 Cube App 的便利性及低手續費,幫助投資者簡化投資流程,達成長期穩定增長的財務目標。
Thumbnail
題目敘述 Sort Array by Increasing Frequency Leetcode #1636 給定一個輸入陣列,請依照出現頻率的多寡從低頻到高頻排列陣列元素。 如果有兩個元素的出現頻率相同,依照元素大小從大到小排列。 測試範例 Example 1: Input: nums
Thumbnail
LeetCode 是一個程式語言版的線上題庫平臺,提供題目描述、程式碼區塊、解題者分享的解法和疑問討論。藉由這篇文章分享我在 LeetCode 上的使用經驗和觀點,包括刷題的重要性、解題心態和練習目標。
Thumbnail
題目敘述 Patching Array 題目給定一個整數陣列, 請問還要補上多少個數字,才能用這些數字的和拼湊出所有1~n的整數。
Thumbnail
題目敘述 Longest Arithmetic Subsequence of Given Difference 給定一個整數陣列nums,請找出給定公差difference的最長的等差數列的長度是多少?
Thumbnail
題目敘述 Solving Questions With Brainpower 給定一個測驗題陣列,每個欄位都是一個pair, 分別記錄測驗題做完可以得到的分數,和需要的冷卻時間 (也就是會有一段時間不能作答接下來的題目)。 請問在最佳的答題策略下,最多可以獲得多少分數?
Thumbnail
給定一個字串陣列,請把它們所共有的字元伴隨著出現次數輸出。這篇文章介紹如何使用字典統計出現次數,和字典取交集的方法來解決此問題。並提供了複雜度分析和關鍵知識點。
Thumbnail
給定一個字串s,以s擁有的字元製造迴文字串。要能製造出的迴文字串長度最長是多少,觀察迴文字串不外乎兩種模式對稱部分 + 核心字元 + 對稱部分,其中,核心字元在正中央出現一次,或者 對稱部分 + 對稱部分。使用演算法統計出現次數並推理出盡可能充分利用每個字元的迴文字串製造方法。
Thumbnail
題目敘述 題目會給定一個整數陣列arr,要求我們判斷是否每個元素的出現次數都不同? 題目的原文敘述 測試範例 Example 1: Input: arr = [1,2,2,1,1,3] Output: true Explanation: The value 1 has 3 occurre