那么,第一层一定有4个选项,分别为1,2,3,4。从1向下,即在1的基础上增加元素,则有12,13,14。从2向下,为了避免重复,只允许选择2之后的元素,则有23和24,以此类推。
如果以涂满红色表示节点为满足要求的结果,则整个树的所有节点都为结果,因为每个节点都代表数组的一个子集。
如果明白了subset问题的决策树设计,则subsets w/ duplicates问题与之大致类似,只是要避免重复的情况,即在subset问题的代码基础上加入一些条件判断。
permutation问题(问题3、4)permutation问题实际上更为简单。对于数组[1,2,3],想象三个有顺序的盒子,每个盒子可以放一个不同的数字。顺着回溯法的增量找寻答案的思路,可以这么设计:树的第零层(根节点)三个盒子全为空,第一层填入盒子1,第二层在盒子1填入某数的基础上填入盒子2,第三层填入盒子3。
在这个决策树中,所有叶节点为想要找寻的结果。
与问题2相似,permutations w/ duplicates也需要在permutation问题的基础上加入判断条件,防止计入重复的排列。
combination sum问题(问题5、6)combination问题与subset同为组合性质的问题,所以解法类似。在树的每一层增量选择一个元素构成答案的一部分。不同的是,由于有sum == target这个要求,在枚举过程中需要作出选择,抛弃不符合要求的选项。
在上图的决策树中,Root为空,括号中表示还需要求和的数,如第一层中的第一个节点2表示在组合中加入2,则剩余8-2=6。由于允许将同一个元素选择多次,2的选项中可以包含2自己。在3的选项中,为了防止重复,规定只能从3开始往后选择。可在回溯前对数组进行排序,则当发现括号中的数小于选项本身时(如5(3)),则该节点没有可行的选项,可以将该节点抛弃。
问题6在问题5的基础上加入条件判断。
palindrome partition(问题7)与以上问题类似,用决策树对分割进行增量选择,在每一层对余下的字符串进行分割。当察觉到新的分割不是回文时可以立即舍弃。
看第一层: 对String "ababa",先从头分割出一块,这一块的长度可以是1-5,于是我们有了图中第一层的5个节点,括号中表示余下的字符串。ab和abab都不是回文,因而可以立即舍去。ababa已经是一个完全分割了,因而可以加入答案集。再对其它节点括号中的元素进行分割,不再赘述。
本文附录中总结了问题1-7的决策树,以供参考。
实现首先记住回溯法的一般普遍实现,对于不同的问题只要往这个框框上套即可。
1 boolean solve(Node n) { 2 if n is a leaf node { 3 if the leaf is a goal node, return true 4 else return false 5 } else { 6 for each child c of n { 7 if solve(c) succeeds, return true 8 } 9 return false 10 } 11 }