在这里更新一些回溯的题目吧
题1
给你一个整数 n ,请你生成并返回所有由 n 个节点组成且节点值从 1 到 n 互不相同的不同 二叉搜索树 。可以按 任意顺序 返回答案。
这道题返回的数据结构是List<TreeNode>,也就是多个二叉树的集合。之前做的回溯一般就是返回一个最好的结果或者是方法数,这次返回的数据结构比较复杂,这里埋了一个伏笔。
这道题解法靠递归。递归思路是:先选择x作为这个树的根节点,然后构造左子树:选择比X小的树作为左根节点;构造右子树:选择比X大的树作为右根节点。递归调用。
class Solution {
public List<TreeNode> generateTrees(int n) {
if (n == 0) {
return new LinkedList<TreeNode>();
}
return generateTrees(1, n);
}
public List<TreeNode> generateTrees(int start, int end) {
List<TreeNode> allTrees = new LinkedList<TreeNode>();
if (start > end) {
allTrees.add(null);
return allTrees;
}
// 枚举可行根节点
for (int i = start; i <= end; i++) {
// 获得所有可行的左子树集合
List<TreeNode> leftTrees = generateTrees(start, i - 1);
// 获得所有可行的右子树集合
List<TreeNode> rightTrees = generateTrees(i + 1, end);
// 从左子树集合中选出一棵左子树,从右子树集合中选出一棵右子树,拼接到根节点上
for (TreeNode left : leftTrees) {
for (TreeNode right : rightTrees) {
TreeNode currTree = new TreeNode(i);
currTree.left = left;
currTree.right = right;
allTrees.add(currTree);
}
}
}
return allTrees;
}
}
值得一提的是代码的最后一段用到了一个回溯我没见过的归并思路。原因可能在于,方法返回的是树的集合,而每次调用generateTrees返回的也是集合,也就是说,返回的树不止一颗,所以要for循环左选一颗右选一颗。这种归并方法记录一下。
其实正常来说,我认为的归并方法是像这样:
private void generateTrees(int start, int end, List<TreeNode> ans, TreeNode root, int count) {
if (count == n) {
//复制当前树并且加到结果中
TreeNode newRoot = treeCopy(root);
ans.add(newRoot);
return;
}
.......后面不写了
没有采用这种方法的原因可能是,如果真的不能返回集合,返回void的话,意味着要在每一种情况下实时插入,而且会出现情况重复的情况。详情见:
看第一种回溯法
而且递归结束后还要移除插入的结点(保持原样,懂我意思吧)
注意到这个方法还用了一个值0的哨兵结点。这样不用考虑第一次遍历的时候是空树的情况。(其实也可以手动创建值为1-n的根节点送进递归调用)
题2
给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
放到现在来看这题的思路不难,但要返回的是List<List<Integer>>,也就是所有成功的结果。这种的回溯,我当初写的代码如下:
class Solution {
public List<List<Integer>> pathSum(TreeNode root, int targetSum) {
List<List<Integer>> res = new ArrayList<List<Integer>>();
List<Integer> cur = new ArrayList<Integer>();
judge(root , targetSum , cur ,res);
return res;
}
void judge(TreeNode root, int target,List<Integer> cur,List<List<Integer>> res){
if (root == null) return;
Integer val = root.val;
cur.add(val);
if (val == target &&root.left ==null && root.right ==null) {
res.add(cur);
cur.remove(cur.size() - 1);
return;
}
judge(root.left,target - val,cur,res);
judge(root.right,target - val,cur,res);
cur.remove(cur.size() - 1);
}
}
注意:对于回溯的方法,在遍历之后要执行返回,在这里也就是remove操作。这里还有两个点:
1.对于一些特殊情况(在这题中为找到了路径),它会立刻返回,并不会执行下方的remove,所以我们要在特殊情况后再添加一个remove。
2.list的remove方法有两种形式:一种是删除索引,另外一种是删除list内元素。如果要使用后者方法,注意类型为Integer。
但我提交了代码之后仍是错的,看过正确答案后才知道原来是这句:
res.add(cur);
出了大问题。
正解为:
res.add(new ArrayList(cur));
其实读到这里已经感受到了问题。就是list是引用调用的,它现在的值可能是这样,但是之后就不会这样了。因此需要新建一个list类存现在的值。