拼字遊戲 拼出最高分的單字組合 (DFS回溯法應用) Leetcode #1255

小松鼠-avatar-img
發佈於演算法題目解析 個房間
更新於 發佈於 閱讀時間約 12 分鐘

題目敘述

輸入會給定一組限量供應的英文字母、每個英文字母對應的分數單字庫

要求我們從給定的英文字母去拚出單字庫中的單字,盡可能地拼出最高分的單字組合

請問最高分數是多少?

每個單字最多只能用一次,不可以重複使用


題目的原文敘述


測試範例

Example 1:

Input: words = ["dog","cat","dad","good"], letters = ["a","a","c","d","d","d","g","o","o"], score = [1,0,9,5,0,0,3,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0]
Output: 23
Explanation:
Score a=1, c=9, d=5, g=3, o=2
Given letters, we can form the words "dad" (5+1+5) and "good" (3+2+2+5) with a score of 23.
Words "dad" and "dog" only get a score of 21.

Example 2:

Input: words = ["xxxz","ax","bx","cx"], letters = ["z","a","b","c","x","x","x"], score = [4,4,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,5,0,10]
Output: 27
Explanation:
Score a=4, b=4, c=4, x=5, z=10
Given letters, we can form the words "ax" (4+5), "bx" (4+5) and "cx" (4+5) with a score of 27.
Word "xxxz" only get a score of 25.

Example 3:

Input: words = ["leetcode"], letters = ["l","e","t","c","o","d"], score = [0,0,1,1,1,0,0,0,0,0,0,1,0,0,1,0,0,0,0,1,0,0,0,0,0,0]
Output: 0
Explanation:
Letter "e" can only be used once.

約束條件

Constraints:

  • 1 <= words.length <= 14

單字庫words裡最少一個單字,最多14個單字

  • 1 <= words[i].length <= 15

每個單字words[i]的長度最少一個字母,最長15個字母。

  • 1 <= letters.length <= 100

letters最少提供一個英文字母,最多提供100個會重複的英文字母。

  • letters[i].length == 1

letters提供的都是長度為1的英文字母。

  • score.length == 26

分數陣列score裡面儲存的整數分別代表a~z每個字母對應的分數。

  • 0 <= score[i] <= 10

每個字母分數最低0分,最高10分

  • words[i]letters[i] contains only lower case English letters.

題目用到的都只會是小寫英文字母。


觀察

基本上有兩種思路,

第一種挑限量的英文字母去拼單字,看最高能拚幾分?

第二種挑單字去消耗提供的英文字母,看最高能湊出幾分?


第二種比較好,為什麼?

從題目敘述和約束條件可以知道

英文字母最多可能會供應100個有重複的英文字母;

但是,單字庫裡最多也才14個單字


同樣是展開,從單字庫裡去展開,分支情況就會少非常多

而且,我們還可以用剪枝Pruning的技巧,排除掉那些不可能產生最高分樹的組合


演算法 DFS + 回溯法 + 剪枝優化

比較敏銳的同學,應該已經選到用DFS+回溯法去列舉所有可能的單字組合情況,去找出擁有最高分數的單字組合。

不免俗,這邊再幫讀者快速複習一次,鞏固知識點。

(還沒看過的讀者,可以看這篇文章來學習這種對於枚舉類的場景很實用的演算法架構)

合縱連橫: DFS+回溯法框架_理解背後的本質


DFS + 回溯法 演算法框架

用途:

展開所有可能的路徑(或者說狀態),並且把符合條件的狀態加入到最終的結果

def backtrack( parameter ):

# 終止條件
if stop condition:
save result if needed # 有需要的話,儲存當下的狀態作為結果​
return

# 通則​
for each possible next selection
make a selection # 做出一個選擇​
backtrack( updated patameter ) # 遞回展開下一層​
undo selection # 撤銷選擇,回到原始狀態,準備做下一個選擇​

return


在這題枚舉對像是什麼?

枚舉單字庫裡的每個單字。

所以start index從0開始拜訪到最後一個。


怎樣叫做合法的單字枚舉?

當供應的字典dictionary 還足夠拼出當下這個單字words[i]時
(對應需要的字母數量紀錄在cur_spell ),就是合法的枚舉。


如何更新最大分數?

每次拚出一個單字時,就加入對應的分數 word_score[i],

每層遞迴都隨時更新分數的最大值。


什麼時候遞迴終止?

當供應的字典dictionary一直被消耗,已經無法拼出任何一個單字庫中的單字時,

遞迴終止,自然結束。


剪枝的優化策略是?

每層遞迴會檢查,如果剩下的單字分數加起來(假設的最好情況) 還比目前已知的分數最大值還小,那麼可以提早結束這條搜索路徑,因為不可能產生更好的結果


OK,到這邊已經釐清遞迴參數、遞迴通則、終止條件、還有剪枝的優化策略。

接下來轉成對應的程式碼即可。


程式碼 DFS + 回溯法 + 剪枝優化


class Solution():
def maxScoreWords(self, words, letters, score):

# key: word
# value: total score for specific word
word_score = [ sum(score[ord(char)-ord('a')] for char in word) for word in words]

# key: word
# value: character needed to spell specific word
word_spell = [ Counter(word) for word in words ]

# Initial letters we have in input array: letters
offering = Counter(letters)

# Try to pick word as many as possible to reach highest score
def pickWord( start, points, dictionary ):

# Early stop those branches which cannot yield better score
if points + sum(word_score[start: ]) < pickWord.max_score :
return

# Update highest score during enumeration in DFS
pickWord.max_score = max(pickWord.max_score, points)

# Pick a word which we can spell, then adding score
for i, cur_spell in enumerate(word_spell[start:], start):

# Check we still have enough letters to spell current word
if all( cur_spell[char] <= dictionary[char] for char in cur_spell):

pickWord( i+1, points+ word_score[i], dictionary - cur_spell )

return
#------------------------------
# Since this is a maximal value optimization, we initialize to relative low value 0
pickWord.max_score = 0

# Start picking words from first word to last word
pickWord( start=0, points=0, dictionary=offering )

# Final best result = maximal score
return pickWord.max_score


複雜度分析

w = letter陣列的長度。

n = words單字庫陣列的長度 = 單字總數。

s = 單字庫裡單字的平均長度。

時間複雜度 O(w+ns+s * 2^n ) ~ O( s *2^n )

建造供應字母的字典、分數表格、每個單字的字母表格耗費O(w+ns)

DFS遞迴耗費O(s * 2^n)

每個單字選或不選,總共有2^n總情況,每個情況需要耗費O(s)去檢查是否還能用字典剩餘的英文字母拼出來。


空間複雜度 O(n)

建造分數表格、每個單字的字母表格需耗費空間O(n)

DFS遞迴 run-time call stack depth也是O(n)


關鍵知識點

遇到枚舉類的應用場警,記得聯想到很實用的DFS+回溯法演算法框架合縱連橫: DFS+回溯法框架_理解背後的本質

枚舉時,記得先思考一下,從哪個對象開始枚舉,分支情況會比較少,比較少的那個代表run-time所需時間也比較短,效率更好


Reference

[1] Maximum Score Words Formed by Letters - LeetCode

留言
avatar-img
留言分享你的想法!
🤤🤤🤤
小松鼠-avatar-img
發文者
2024/05/24
林燃(創作小說家) 想去傳龍麵館吃東西了🤤🤤🤤
avatar-img
小松鼠的演算法樂園
96會員
427內容數
由有業界實戰經驗的演算法工程師, 手把手教你建立解題的框架, 一步步寫出高效、清晰易懂的解題答案。 著重在讓讀者啟發思考、理解演算法,熟悉常見的演算法模板。 深入淺出地介紹題目背後所使用的演算法意義,融會貫通演算法與資料結構的應用。 在幾個經典的題目融入一道題目的多種解法,或者同一招解不同的題目,擴展廣度,並加深印象。
2024/09/26
Leetcode 729. My Calendar I 給定一個行事曆的class定義和行程安排的介面interface。 請完成下列function 1.建構子MyCalendar() 初始化MyCalendar物件 2.boolean book(int start, int end) 插入新行程
Thumbnail
2024/09/26
Leetcode 729. My Calendar I 給定一個行事曆的class定義和行程安排的介面interface。 請完成下列function 1.建構子MyCalendar() 初始化MyCalendar物件 2.boolean book(int start, int end) 插入新行程
Thumbnail
2024/09/10
Insert Greatest Common Divisors in Linked List 題目給定一個鏈結串列, 請在兩兩節點之間加入一個新節點,新節點的值為兩者之間的最大公因數。 最後返回新串列的head node作為答案。
Thumbnail
2024/09/10
Insert Greatest Common Divisors in Linked List 題目給定一個鏈結串列, 請在兩兩節點之間加入一個新節點,新節點的值為兩者之間的最大公因數。 最後返回新串列的head node作為答案。
Thumbnail
2024/09/09
2326. Spiral Matrix IV 題目給定一個Linked list和對應的矩陣高度m、寬度n。 請依照順時針的拜訪順序, 從左上角出發,依照次序把Linked List的內容填到矩陣裡。 如果有剩餘不足的空位,就填補-1。 最後將填補好的矩陣返回作為答案。
Thumbnail
2024/09/09
2326. Spiral Matrix IV 題目給定一個Linked list和對應的矩陣高度m、寬度n。 請依照順時針的拜訪順序, 從左上角出發,依照次序把Linked List的內容填到矩陣裡。 如果有剩餘不足的空位,就填補-1。 最後將填補好的矩陣返回作為答案。
Thumbnail
看更多
你可能也想看
Thumbnail
孩子寫功課時瞇眼?小心近視!這款喜光全光譜TIONE⁺光健康智慧檯燈,獲眼科院長推薦,網路好評不斷!全光譜LED、180cm大照明範圍、5段亮度及色溫調整、350度萬向旋轉,讓孩子學習更舒適、保護眼睛!
Thumbnail
孩子寫功課時瞇眼?小心近視!這款喜光全光譜TIONE⁺光健康智慧檯燈,獲眼科院長推薦,網路好評不斷!全光譜LED、180cm大照明範圍、5段亮度及色溫調整、350度萬向旋轉,讓孩子學習更舒適、保護眼睛!
Thumbnail
創作者營運專員/經理(Operations Specialist/Manager)將負責對平台成長及收入至關重要的 Partnership 夥伴創作者開發及營運。你將發揮對知識與內容變現、影響力變現的精準判斷力,找到你心中的潛力新星或有聲量的中大型創作者加入 vocus。
Thumbnail
創作者營運專員/經理(Operations Specialist/Manager)將負責對平台成長及收入至關重要的 Partnership 夥伴創作者開發及營運。你將發揮對知識與內容變現、影響力變現的精準判斷力,找到你心中的潛力新星或有聲量的中大型創作者加入 vocus。
Thumbnail
題目敘述 Combination Sum II_Leetcode #40 給定一個整數陣列candidates 和 目標值target。 每個陣列元素只能選擇一次,請問有多少種組合方法,可以使得組合數總和 = target? 請把滿足組合數總和 = target的組合方法以陣列的形式返回答案
Thumbnail
題目敘述 Combination Sum II_Leetcode #40 給定一個整數陣列candidates 和 目標值target。 每個陣列元素只能選擇一次,請問有多少種組合方法,可以使得組合數總和 = target? 請把滿足組合數總和 = target的組合方法以陣列的形式返回答案
Thumbnail
給定一個字串陣列,請把它們所共有的字元伴隨著出現次數輸出。這篇文章介紹如何使用字典統計出現次數,和字典取交集的方法來解決此問題。並提供了複雜度分析和關鍵知識點。
Thumbnail
給定一個字串陣列,請把它們所共有的字元伴隨著出現次數輸出。這篇文章介紹如何使用字典統計出現次數,和字典取交集的方法來解決此問題。並提供了複雜度分析和關鍵知識點。
Thumbnail
探討如何使用DP動態規劃的方法來進行單字串接,包含了DP遞迴關係式、狀態定義、優化技巧和程式碼示例。同時分析了時間複雜度、空間複雜度和關鍵知識點。這是LeetCode的一個應用題,類似於Word Break I的延伸。
Thumbnail
探討如何使用DP動態規劃的方法來進行單字串接,包含了DP遞迴關係式、狀態定義、優化技巧和程式碼示例。同時分析了時間複雜度、空間複雜度和關鍵知識點。這是LeetCode的一個應用題,類似於Word Break I的延伸。
Thumbnail
知道如何從一組給定的英文字母和單字庫中的單字拼出最高分的單字組合。使用DFS + 回溯法 + 剪枝優化的演算法,詳細分析瞭如何展開所有可能的路徑,並且找出符合條件的狀態,協助讀者理解演算法背後的思維和方法。
Thumbnail
知道如何從一組給定的英文字母和單字庫中的單字拼出最高分的單字組合。使用DFS + 回溯法 + 剪枝優化的演算法,詳細分析瞭如何展開所有可能的路徑,並且找出符合條件的狀態,協助讀者理解演算法背後的思維和方法。
Thumbnail
題目敘述 輸入給定一個二元的二維矩陣grid 每次可以翻轉一條row,讓每個元素的01反相。 也可以翻轉一條column,讓每個元素的01反相。 可以操作任意多次。 最後把每條row視為一條二進位表達式的數字,並且進行加總,得到最後的分數。 請問分數的最大值是多少? 原本的英文題目敘
Thumbnail
題目敘述 輸入給定一個二元的二維矩陣grid 每次可以翻轉一條row,讓每個元素的01反相。 也可以翻轉一條column,讓每個元素的01反相。 可以操作任意多次。 最後把每條row視為一條二進位表達式的數字,並且進行加總,得到最後的分數。 請問分數的最大值是多少? 原本的英文題目敘
Thumbnail
題目會給定一個陣列nums和一個目標值goal。計算子陣列總和=goal的數目有多少。演算法包含前綴和和字典的技巧,時間複雜度為O(n),空間複雜度為O(n)。
Thumbnail
題目會給定一個陣列nums和一個目標值goal。計算子陣列總和=goal的數目有多少。演算法包含前綴和和字典的技巧,時間複雜度為O(n),空間複雜度為O(n)。
Thumbnail
題目敘述 題目會給定我們兩個字串。 第一個是指定順序的字串order。 第二個是輸入字串s。 要求我們依據order給定的順序,重新排列s。 如果出現order中沒有出現的字母,任意位置皆可。 合法答案可能不只一組,輸出其中一種即可。 題目的原文敘述 測試範例 Example
Thumbnail
題目敘述 題目會給定我們兩個字串。 第一個是指定順序的字串order。 第二個是輸入字串s。 要求我們依據order給定的順序,重新排列s。 如果出現order中沒有出現的字母,任意位置皆可。 合法答案可能不只一組,輸出其中一種即可。 題目的原文敘述 測試範例 Example
Thumbnail
題目敘述 題目會給定一個輸入字串s和一套編碼規則,要求我們針對字串s進行解碼,並且以字串的形式返回答案。 編碼規則: 數字[字串] -> []內的字串以對應倍數做展開,而且允許巢狀編碼。 例如: 3[a] 解碼完就是 aaa 2[bc] 解碼完就是 bcbc 2[a2[b]] = 2
Thumbnail
題目敘述 題目會給定一個輸入字串s和一套編碼規則,要求我們針對字串s進行解碼,並且以字串的形式返回答案。 編碼規則: 數字[字串] -> []內的字串以對應倍數做展開,而且允許巢狀編碼。 例如: 3[a] 解碼完就是 aaa 2[bc] 解碼完就是 bcbc 2[a2[b]] = 2
Thumbnail
題目敘述 題目會給兩個陣列nums1和nums2。 題目要求我們從中同步選擇長度為k的子序列,並且最大化子序列的分數, 回傳最高的分數值。 分數的定義: 分數 = (nums1[i0] + nums1[i1] +...+ nums1[ik - 1]) * min(nums2[i0] ,
Thumbnail
題目敘述 題目會給兩個陣列nums1和nums2。 題目要求我們從中同步選擇長度為k的子序列,並且最大化子序列的分數, 回傳最高的分數值。 分數的定義: 分數 = (nums1[i0] + nums1[i1] +...+ nums1[ik - 1]) * min(nums2[i0] ,
Thumbnail
這題也是滿經典的DP動態規劃教學案例和題目,就順便複習一下吧。 題目敘述 題目會給我們兩個字串text1, text2。 要求我們找出兩個字串的最長共同子序列,並且返回最長共同子序列的長度。 如果彼此沒有共同子序列,則返回0。 題目的原文敘述 測試範例 Example 1: In
Thumbnail
這題也是滿經典的DP動態規劃教學案例和題目,就順便複習一下吧。 題目敘述 題目會給我們兩個字串text1, text2。 要求我們找出兩個字串的最長共同子序列,並且返回最長共同子序列的長度。 如果彼此沒有共同子序列,則返回0。 題目的原文敘述 測試範例 Example 1: In
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News