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; } }