化繁為簡: 映射化簡的演算法技巧

閱讀時間約 16 分鐘
raw-image

演算法映射化簡的核心觀念

在面對新題目的時候,除了重頭想一個新的演算法之外;
還有另一個方法,想看看有沒有核心觀念彼此相同的問題與演算法
如果有,就可以把新的題目映射化簡到已知解法的問題,用已知的演算法去解開

接著,我們會介紹幾個範例,並且使用映射化簡的技巧來解題,透過化簡框架的復現,幫助讀者掌握這個知識點。


組合數之和 Combination Sum

題目給定目標值target,和一群整數candidates。
請問從中挑任意個數字做組合,允許重複選,有多少種方法可以讓組合數之和= target ?

把所有可能的組合方法列出來。

這題常見的方法是用DFS+回溯法去解,之前在專欄文章也解析過。


還有沒有別的解法呢? 其實有喔!

化簡到找零錢模型 Coin Change II

仔細觀察,用一群整數去湊出目標值target
這個動作就相當於,用銅板去湊出目標金額amount!
整數就相當於銅板,每個整數對應到銅板的面額,例如一元、五元、十元。
目標值target就相當於目標金額amount
那麼,這題組合數之和的問題,就被我們映射化簡到找零錢問題II Coin Change II
只要把所有能湊出target元的找零組合列出來,就是最終答案


原本已知的Coin Change II的找零錢演算法和解題程式碼

class Solution:

def change(self, amount: int, coins: List[int]) -> int:

# base case:
# amount 0's method count = 1 (by taking no coins)

dp = [1] + [ 0 for _ in range(amount)]

# make change with current coin, from small coin to large coin
# 請注意看,等等我們會用 找零錢的演算法 去解開 組合數之和!
for cur_coin in coins:

# update change method count from small amount to large amount
for small_amount in range(cur_coin, amount+1):

# current small amount can make changed with current coin
dp[small_amount] += dp[small_amount - cur_coin]

return dp[amount]


用已知 Coin Change II的演算法,去解開組合數之和。

唯一的小差異就是原本只需要計算方法數,所以用int去累加方法數即可。

現在需要知道方法的具體組合情況,因此,我們用list [] 去記錄具體的組合數

class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:

dp = defaultdict(list)

## Base case:
# $0 is reached by taking nothing
dp[0] = [ [ ] ]

coins = candidates

## General cases:
# 請注意看,剛剛介紹的找零錢演算法就出現在這裡!
for coin in coins:
for amount in range(coin, target+1):

# satisfy $amount from ($amount-$coin) + $coin
for prev_coin_change in dp[ amount - coin ]:
dp[amount] += [ prev_coin_change + [coin] ]

return dp[ target ]

幾乎九成像,對吧!


最長回文子序列 Longest Palindromic Subsequence

給定一個字串s,請計算s的最長回文子序列的長度

這題常見的方法是用DP動態規劃配合回文框架去解,之前在專欄文章也解析過。


還有沒有別的解法呢? 其實有喔!

化簡到最長共同子序列 Longest Common Subsequence, aka LCS


仔細觀察,s的最長回文子序列
這個動作就相當於,s 和 逆序的s 的最長共同子序列!
s逆序的s 找共同序列,相當於共同子序列
s 和 逆序的s 子序列如果有相同,那一定是回文的形式(因為彼此頭尾順序相反)
那麼,這題最長回文子序列的問題,就被我們映射化簡到最長共同子序列LCS
只要計算s 和 逆序的s 的最長共同子序列的長度,就是最終答案


原本已知的LCS的最長共同子序列演算法和解題程式碼

class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:

# 請注意看,等等我們會用 最長共同子序列的演算法 去解開 最長回文子序列!
dp = {}
def LCS(i, j):

if (i, j) in dp:
return dp[i, j]

if i == -1 or j == -1:
dp[i, j] = 0
return 0

elif text1[i] == text2[j]:
dp[i, j] = LCS(i-1, j-1)+1
return dp[i, j]

else:
dp[i, j] = max( LCS(i, j-1), LCS(i-1, j) )
return dp[i, j]

# ------------------------------
return LCS( len(text1)-1, len(text2)-1 )


用已知 LCS最長共同子序列的演算法,去解開 最長回文子序列。

唯一的小差異就是傳入的參數,一個是原本的字串s,另一個是逆序的s

class Solution:
def longestPalindromeSubseq(self, s: str) -> int:

# -------------------------------------------------
# 請注意看,剛剛介紹的 最長共同子序列 就出現在這裡!
dp = {}
def LCS(i, j):

if (i, j) in dp:
return dp[i, j]

if i == -1 or j == -1:
dp[i, j] = 0
return 0

elif text1[i] == text2[j]:
dp[i, j] = LCS(i-1, j-1)+1
return dp[i, j]

else:
dp[i, j] = max( LCS(i, j-1), LCS(i-1, j) )
return dp[i, j]

# -------------------------------------------------

# Original s as text1
text1 = s

# Reversed s as text2
text2 = s[::-1]

# Longest palindromic subsequence of s = Longest common subsequence(s, s[::-1])
return LCS( len(text1)-1, len(text2)-1 )

幾乎九成像,對吧! 我們只要傳入 正序的s 和 逆序的s 即可。


合併k條已排序的Linked list Merge k Sorted Lists

給定k條已排序的串列,要求我們把k條串列合併成一條已排序的串列

這題常見的方法是動態維護最小堆minHeap去解。


還有沒有別的解法呢? 其實有喔!

化簡到合併兩條已排序的串列 Merge Two Sorted Lists


仔細觀察,合併k條已排序的串列
這個動作就相當於,前面k/2條的串列 和 後面k/2條的串列做合併!
前面和後面又可以如此反覆分割,一直到剩下一條串列或者空串列為止。
兩條已排序的串列合併我們已經會做了。
那麼,這題合併k條已排序串列的問題,就被我們映射化簡到合併兩條已排序串列。
只要不斷分割,倆倆先合併成一條,中間再不斷倆倆合併成一條,最後合併結果就是最終答案


原本已知的合併兩條已排序串列的演算法和解題程式碼

class Solution:

# 請注意看,等等我們會用 合併兩條已排序串列的演算法 去解開 合併k條已排序串列!
def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]:

# List 1 is empty, then result is decided by List 2
if not list1:
return list2

# List 2 is empty, then result is decided by List 1
if not list2:
return list1

# General cases: compare node value, and update linkage
if list1.val < list2.val:
list1.next = self.mergeTwoLists(list1.next, list2)
return list1

else:
list2.next = self.mergeTwoLists(list1, list2.next)
return list2


用已知 合併兩條已排序串列的演算法,去解開 合併k條已排序串列。

唯一的小差異就是多了k條之間,兩兩先分割再合併的動作。

class Solution:
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
#----------------------------------
# 請注意看,剛剛介紹的 合併兩條已排序串列的演算法 就出現在這裡!
def merge(l1, l2) -> ListNode:
'''
input: two sorted list l1, l2
output: merged sorted list
'''

dummy_head = ListNode('#')
cur = dummy_head

while l1 and l2:
if l1.val <= l2.val:
cur.next = l1
cur, l1 = cur.next, l1.next
else:
cur.next = l2
cur, l2 = cur.next, l2.next

if l1:
cur.next = l1
else:
cur.next = l2

return dummy_head.next
#----------------------------------

if not lists:
# base case:
# empty list
return None

elif len(lists) == 1:
# base case:
# only one list
return lists[0]

# general case:
# divide-and-conquer

# divide:
mid = len(lists)//2
left = self.mergeKLists( lists[:mid] )
right = self.mergeKLists( lists[mid:] )

# conquer:
return merge(left,right)

核心相同,只是多了兩兩分割和合併的外衣而已。


重點回顧: 化簡的關鍵步驟

找出新問題的核心是在問什麼?

觀察新問題已知會解的問題、我們已經會的演算法,有沒有共通之處?

如果有,建立映射關係,把新問題化簡到已經會解的問題

由已經會的演算法框架,構建出新問題的演算法與答案!

(通常只要很少量的修改即可,相當於搭橋、映射的動作)


結語

今天介紹了很靈活的演算法化簡技巧,
希望藉由思考流程、演算法化簡框架、經典例題的復現,
幫助讀者掌握這個重要的知識點。

我們下篇文章再見囉!

86會員
425內容數
由有業界實戰經驗的演算法工程師, 手把手教你建立解題的框架, 一步步寫出高效、清晰易懂的解題答案。 著重在讓讀者啟發思考、理解演算法,熟悉常見的演算法模板。 深入淺出地介紹題目背後所使用的演算法意義,融會貫通演算法與資料結構的應用。 在幾個經典的題目融入一道題目的多種解法,或者同一招解不同的題目,擴展廣度,並加深印象。
留言0
查看全部
發表第一個留言支持創作者!
給定一個Linked list鏈結串列的Head node, 請判斷這條Linked list是否存在環路(Cycle)? 如果有環路,回傳True。 如果沒有,回傳False。
這篇文章,會帶大家快速回顧DFS+回溯法框架(還沒看過或想複習的可以點連結進去)。 用DFS+回溯法框架,解開 組合數之和 Combination Sum 的全系列題目。 幫助讀者鞏固DFS+回溯法框架這個重要的知識點。 回顧 DFS+回溯法框架 白話的意思 # 列舉所以可能的情況,
這篇文章,會帶著大家複習以前學過的 格子點DP框架, 並且以最小成本的下降路徑的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 最小成本下降路徑的形式 每個格子點的值代表經過的成本。 要求從最上面那排往下方走,落到最下一排的最小成本的下降路徑。
這篇文章,會帶著大家複習以前學過的數列DP框架, 並且以費式數列、爬樓梯、骨牌拚接的應用與遞迴數列概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 數列DP與遞迴數列常見的形式 如果是遞迴數列,常常看到以函數型式表達
這篇文章,會帶著大家複習以前學過的二進位DP框架, 並且以0~N的整數有幾個bit1,有幾個bit0的概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 常見的考法 請問整數k有幾個bit1? 有幾個bit0? 請問整數0到整數N分別各有幾個bit1? 有幾個
今天的一魚三吃系列是透過 兩點之間是否存在一條路徑的題目,來回顧以前學過的DFS、BFS和Disjoint Set,鞏固圖論演算法的知識點。 英文的題目敘述在這裡 題目敘述 給定我們已知n個節點的圖,和圖上的每一條無向邊edges。 請問給定的起點start和終點end是否存在一條路徑可
給定一個Linked list鏈結串列的Head node, 請判斷這條Linked list是否存在環路(Cycle)? 如果有環路,回傳True。 如果沒有,回傳False。
這篇文章,會帶大家快速回顧DFS+回溯法框架(還沒看過或想複習的可以點連結進去)。 用DFS+回溯法框架,解開 組合數之和 Combination Sum 的全系列題目。 幫助讀者鞏固DFS+回溯法框架這個重要的知識點。 回顧 DFS+回溯法框架 白話的意思 # 列舉所以可能的情況,
這篇文章,會帶著大家複習以前學過的 格子點DP框架, 並且以最小成本的下降路徑的應用題與概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 最小成本下降路徑的形式 每個格子點的值代表經過的成本。 要求從最上面那排往下方走,落到最下一排的最小成本的下降路徑。
這篇文章,會帶著大家複習以前學過的數列DP框架, 並且以費式數列、爬樓梯、骨牌拚接的應用與遞迴數列概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 數列DP與遞迴數列常見的形式 如果是遞迴數列,常常看到以函數型式表達
這篇文章,會帶著大家複習以前學過的二進位DP框架, 並且以0~N的整數有幾個bit1,有幾個bit0的概念為核心, 貫穿一些相關聯的題目,透過框架複現來幫助讀者理解這個演算法框架。 常見的考法 請問整數k有幾個bit1? 有幾個bit0? 請問整數0到整數N分別各有幾個bit1? 有幾個
今天的一魚三吃系列是透過 兩點之間是否存在一條路徑的題目,來回顧以前學過的DFS、BFS和Disjoint Set,鞏固圖論演算法的知識點。 英文的題目敘述在這裡 題目敘述 給定我們已知n個節點的圖,和圖上的每一條無向邊edges。 請問給定的起點start和終點end是否存在一條路徑可
你可能也想看
Google News 追蹤
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
化繁為簡是在追尋華麗繁複的生產與輸出後應該討求的能力
Thumbnail
工作只是讓妳實現自我的路上做的其中一件事而已 ,不要因為他人無心評論而抹滅自己的光芒。
Thumbnail
當我們持續的學習成長,在某個領域當中越來越專業, 因此被邀請擔任講師或是公司當中的教導角色, 那一刻,我們從一個學習者變成了教導者, 這時可能會開始意識到,專業知識的傳達與教學其實充滿挑戰。
小女孩的主張是~錢非萬能,但糖果是萬能的,早點睡,注意日益成長的兒童玩具市場。 神鵰俠侶有一個橋段~周伯通對小龍女說,你看,黃蓉丫頭聰明絕頂,反之郭靖這傻小子資質平平,怎麼看都不是一個練功奇才,怎麼就怪了,偏偏郭靖卻有辦法練成奇功,蓉丫頭卻怎麼學也學不來。小龍女,你會一手畫圓,一手畫方嗎?小龍女馬上
Thumbnail
4 步驟讓你學會如何將吸收的資訊轉化為產出!本週一口氣整理了5本和產出相關的書籍,包含了:《output最高學以致用法》、《1枝筆+1張紙,說服各種人》、《韓國企劃女神CCC思考整理術》、《高產出的本事》及《15分鐘寫出爆紅千字文》。身為一個文字及內容行銷及產出工作者,先前買了不少相關的書,延續著前
Thumbnail
Hi親愛的大家,「化繁為簡」的功夫你自認為如何? 「In character, in manner, in style, in all things, the supreme excellence is simplicity.」 Henry Wadsworth Longfellow 在性格、方式、風
Thumbnail
「錢真的就是從台股流出去了。」 這句話是個人三月升息循環以來一直觀察的最重點跟提醒,在三月升息前就一直提醒的,要觀察升息跟縮表的實際影響狀況,才能決定未來操作應對的大方向。 事實上呢? 三月的大盤日均量是3266億。 四月的大盤日均量是2709億。 五月的大盤日均量是2380億。 要承認這件事實。
Thumbnail
高爾夫是一個對自己的挑戰,也是一條漫漫長路,打到後面、你會有自己的解釋和定義!對你而言什麼是一個句號?每個人的答案都不同!這才是最屌的地方,也符合世界的基本原理。什麼是答案?本來就因人而異。為什麼一定要追求桿數呢?我又不打職業,就是運動和交朋友、喇賽而已!更多在釋放平時的壓力。 先說對工具,20年
Thumbnail
簡報講求溝通效率,無論使用言語或文字說明,在溝通過程中都仰賴聽眾在腦中多一層的轉換功夫。「一張圖勝過千言萬語,」根據研究發現,訊息圖像化可減少 25%~40% 的溝通時間,因此若能在簡報中善加運用圖表、表格、圖片等視覺要素,維持「圖像 80% 、文字 20% 」的比例,將有助觀眾在黃金 10 秒內
Thumbnail
這個秋,Chill 嗨嗨!穿搭美美去賞楓,裝備款款去露營⋯⋯你的秋天怎麼過?秋日 To Do List 等你分享! 秋季全站徵文,我們準備了五個創作主題,參賽還有機會獲得「火烤兩用鍋」,一起來看看如何參加吧~
Thumbnail
美國總統大選只剩下三天, 我們觀察一整週民調與金融市場的變化(包含賭局), 到本週五下午3:00前為止, 誰是美國總統幾乎大概可以猜到60-70%的機率, 本篇文章就是以大選結局為主軸來討論近期甚至到未來四年美股可能的改變
Thumbnail
Faker昨天真的太扯了,中國主播王多多點評的話更是精妙,分享給各位 王多多的點評 「Faker是我們的處境,他是LPL永遠繞不開的一個人和話題,所以我們特別渴望在決賽跟他相遇,去直面我們的處境。 我們曾經稱他為最高的山,最長的河,以為山海就是盡頭,可是Faker用他28歲的年齡...
化繁為簡是在追尋華麗繁複的生產與輸出後應該討求的能力
Thumbnail
工作只是讓妳實現自我的路上做的其中一件事而已 ,不要因為他人無心評論而抹滅自己的光芒。
Thumbnail
當我們持續的學習成長,在某個領域當中越來越專業, 因此被邀請擔任講師或是公司當中的教導角色, 那一刻,我們從一個學習者變成了教導者, 這時可能會開始意識到,專業知識的傳達與教學其實充滿挑戰。
小女孩的主張是~錢非萬能,但糖果是萬能的,早點睡,注意日益成長的兒童玩具市場。 神鵰俠侶有一個橋段~周伯通對小龍女說,你看,黃蓉丫頭聰明絕頂,反之郭靖這傻小子資質平平,怎麼看都不是一個練功奇才,怎麼就怪了,偏偏郭靖卻有辦法練成奇功,蓉丫頭卻怎麼學也學不來。小龍女,你會一手畫圓,一手畫方嗎?小龍女馬上
Thumbnail
4 步驟讓你學會如何將吸收的資訊轉化為產出!本週一口氣整理了5本和產出相關的書籍,包含了:《output最高學以致用法》、《1枝筆+1張紙,說服各種人》、《韓國企劃女神CCC思考整理術》、《高產出的本事》及《15分鐘寫出爆紅千字文》。身為一個文字及內容行銷及產出工作者,先前買了不少相關的書,延續著前
Thumbnail
Hi親愛的大家,「化繁為簡」的功夫你自認為如何? 「In character, in manner, in style, in all things, the supreme excellence is simplicity.」 Henry Wadsworth Longfellow 在性格、方式、風
Thumbnail
「錢真的就是從台股流出去了。」 這句話是個人三月升息循環以來一直觀察的最重點跟提醒,在三月升息前就一直提醒的,要觀察升息跟縮表的實際影響狀況,才能決定未來操作應對的大方向。 事實上呢? 三月的大盤日均量是3266億。 四月的大盤日均量是2709億。 五月的大盤日均量是2380億。 要承認這件事實。
Thumbnail
高爾夫是一個對自己的挑戰,也是一條漫漫長路,打到後面、你會有自己的解釋和定義!對你而言什麼是一個句號?每個人的答案都不同!這才是最屌的地方,也符合世界的基本原理。什麼是答案?本來就因人而異。為什麼一定要追求桿數呢?我又不打職業,就是運動和交朋友、喇賽而已!更多在釋放平時的壓力。 先說對工具,20年
Thumbnail
簡報講求溝通效率,無論使用言語或文字說明,在溝通過程中都仰賴聽眾在腦中多一層的轉換功夫。「一張圖勝過千言萬語,」根據研究發現,訊息圖像化可減少 25%~40% 的溝通時間,因此若能在簡報中善加運用圖表、表格、圖片等視覺要素,維持「圖像 80% 、文字 20% 」的比例,將有助觀眾在黃金 10 秒內