leetcode282 - Expression Add Operators - hard
Given a string that contains only digits 0-9 and a target value, return all possibilities to add binary operators (not unary) +, -, or *between the digits so they evaluate to the target value.
Example 1:
Input: num = "123", target = 6
Output: ["1+2+3", "1*2*3"]
Example 2:
Input: num = "232", target = 8
Output: ["2*3+2", "2+3*2"]
Example 3:
Input: num = "105", target = 5
Output: ["1*0+5","10-5"]
Example 4:
Input: num = "00", target = 0
Output: ["0+0", "0-0", "0*0"]
Example 5:
Input: num = "3456237490", target = 9191
Output: []
DFS。
函數頭:private void dfs(int offset, long cal, long lastFact, String crt, List<String> ans)
遞歸定義:在中間的general狀態下,offset這個index以前的數字被加減乘分割的方式已經定好了放在crt裏,這種方式算出來的到目前的答案也定好了放在cal裏,你接下來隨便試offset和後面的數字分割的方式,等有一天試成功了你放到ans裏。對了同時傳一個額外信息lastFact,表示到目前為止最後一個被+-的因數,給你用來輔助現在嘗試*用。
遞歸拆分:這題能產生不同組合無非依賴於1.長數字怎麽被分割為好幾個小數字,2.分割點插的什麽二元操作符。一次dfs內:首先for循環看substring要從offset開始停到哪裏來取數字產生下一個因數。有了這個因數後看看如果拿前面的結果+-*這個新因數後,會怎麽更新cal和lastFact,從而進一步遞歸。
遞歸出口:當offset指不到新數字後你必須走了。如果該退場的時候發現誒我正好算出來的答案合格了,那把你現在找到的組合方式crt存進ans裏。
細節:
1.本題難點在於解決插入乘法符號*時怎麽快速更新計算結果。比如輸入為1234。在某一狀態,前面已經拼成了12+3,我們當前cal記下15,現在需要我們拼上新數字4。要是填+,更新結果很容易就加上去就好;如果要填*,從12+3變成12+3*4,我們不能只依靠上一個cal信息為15來快速得到答案,因為現在3不是先和12組合了而是先和4。如果記錄了上一個因子lastFact的話事情就簡單很多。先把lastFact從cal中減去得到上上次的答案12,再讓lastFact先和當前數字乘了得到3*4,再加回上上次的結果去。所以乘法時,cal更新為cal - lastFact + lastFact * crtFact,lastFact更新為lastFact * crtFact。
2.中間答案和因數都用Long存儲不要用int。因為很可能你兩個數一乘就超int了,但有時候減一減又可以拿到最後int的target,不能在中途把它們犧牲掉。
3.註意0的corner case。0不可以和其他數組成共同的因子。如果是01234開始繼續分解,第一個0只可以自己獨立做因子,不可以和後面的拉幫結派組成01,012什麽的。所以直接用parseLong還有缺陷,比如你不額外處理的話,000 湊0, parseLong看到00也讀成0,就會給你產生00+0的不合理結果。
4.所有數,甚至第一個數,都可以直接霸占到最後。比如12345,不是說一定要加符號進去從而第一個數最多到1234這樣,如果num = “12345” target = 12345,它自身就成立了,不需要加符號。
5.註意遞歸出口時的必須走了的“必須”。就是說你就算試出來的答案是錯的也要走,不可以繼續跑下面的代碼。本題湊巧下面代碼只有for循環,而且這個for循環在offset跑到最後的時候不會進去,所以不在前面寫return也沒關系。但寫其他dfs的時候還是要小心,盡量check一下最前面出口那裏要不要先寫return以避免編譯錯誤。
實現:
class Solution { private String num; private long target; private List<String> ans; public List<String> addOperators(String num, int target) { this.num = num; this.target = target; this.ans = new ArrayList<>(); dfs(0, 0, 0, ""); return ans; } private void dfs(int offset, long cal, long lastFact, String crt) { if (offset == num.length() && cal == target) { ans.add(crt); } // P2: 數可以直接霸占到最後,甚至第一個數。比如12345,不是說一定要加符號進去從而第一個數最多到1234這樣,如果target也是12345它自身就成立了。 for (int i = offset; i < num.length(); i++) { long fact = Long.parseLong(num.substring(offset, i + 1)); if (offset == 0) { dfs(i + 1, cal + fact, fact, crt + num.substring(offset, i + 1)); } else { dfs(i + 1, cal + fact, fact, crt + "+" + fact); dfs(i + 1, cal - fact, -fact, crt + "-" + fact); dfs(i + 1, cal - lastFact + lastFact * fact, lastFact * fact, crt + "*" + fact); } // P1: 如果是01234開始繼續分解,第一個0只可以自己獨立做因子,不可以和後面的拉幫結派組成01,012什麽的。所有直接用parseLong還有缺陷。 if (fact == 0) { break; } } } }
九章實現:
/** * 本參考程序來自九章算法,由 @老頑童 提供。版權所有,轉發請註明出處。 * - 九章算法致力於幫助更多中國人找到好的工作,教師團隊均來自矽谷和國內的一線大公司在職工程師。 * - 現有的面試培訓課程包括:九章算法班,系統設計班,算法強化班,Java入門與基礎算法班,Android 項目實戰班, * - Big Data 項目實戰班,算法面試高頻題班, 動態規劃專題班 * - 更多詳情請見官方網站:http://www.jiuzhang.com/?source=code */ public class Solution { /** * @param num a string contains only digits 0-9 * @param target an integer * @return return all possibilities */ void dfs(String num, int target, int start, String str, long sum, long lastF, List<String> ans) { if (start == num.length()) { if (sum == target) { ans.add(str); } return; } for (int i = start; i < num.length(); i++) { long x = Long.parseLong(num.substring(start, i + 1)); if (start == 0) { dfs(num, target, i + 1, "" + x, x, x, ans); } else { dfs(num, target, i + 1, str + "*" + x, sum - lastF + lastF * x, lastF * x, ans); dfs(num, target, i + 1, str + "+" + x, sum + x, x, ans); dfs(num, target, i + 1, str + "-" + x, sum - x, -x, ans); } if (x == 0) { break; } } } public List<String> addOperators(String num, int target) { // Write your code here List<String> ans = new ArrayList<>(); dfs(num, target, 0, "", 0, 0, ans); return ans; } }
leetcode282 - Expression Add Operators - hard