回溯法-获取path set,一般采用树结构解题

    回溯实际上就是遍历的变种,不符合条件时,本次遍历向上回退。一般来说,回溯算法都可以将决策路径画成树的形状,成为一棵搜索树。回溯法执行的过程实际上就是在这棵树上做遍历。使用回溯法的题目,为什么不能用递归法,因为回溯法中记录路径的栈只有一个。

1、回溯算法的基本思想

    回溯算法的定义:回溯法采用试错的思想,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。—— 回溯法 - 维基百科[3]

    从字面意思上来看,回溯(backtracking) 实际上就是“撤回一步”的意思。而在二叉树的 DFS 遍历中,从一个结点退出就是一种回溯。回溯法和 DFS 是息息相关的。

    根据回溯操作的特性,我们使用栈记录遍历时的当前路径。当进入一个结点时,做 push 操作;当退出一个结点时,做 pop 操作,进行回溯。

2、案例1

    给定一个二叉树和一个目标和,找到所有从根结点到叶结点的路径,使得路径上所有结点值相加等于目标和。

public List<List<Integer>> pathSum(TreeNode root, int sum) {

    List<List<Integer>> res = new ArrayList<>();

    Deque<Integer> path = new ArrayDeque<>();

    traverse(root, sum, path, res);

    return res;

}

void traverse(TreeNode root, int sum, Deque<Integer> path, List<List<Integer>> res) {

    if (root == null) {

        return;

    }

    path.addLast(root.val);

    if (root.left == null && root.right == null) {

        if (root.val == sum) {

            res.add(new ArrayList<>(path));

        }

    }

    int target = sum - root.val;

    traverse(root.left, target, path, res);

    traverse(root.right, target, path, res);

    path.removeLast();

}

    代码的整体结构和上期例题题解类似,只是加上了栈 path 记录当前路径。关于栈的 push 和 pop 操作,有两个需要注意的地方:

            * 保证刚进入结点就 push,最后退出结点之前才 pop,这样才能使当前路径和遍历的进度对应;

            * 在叶结点判断后,不能进行 return,否则会跳过后面的 pop 操作而出错。

    这两点都需要做题来体验,建议亲自做一遍例题来体会。

3、案例2

    题目:给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

    Subsets 问题就是要枚举出集合的所有子集。生成子集有一个很简单的策略,一个子集可以选择使用或不使用第一个元素,选好之后,再对第二个元素进行选择,以此类推。这就是一种回溯的思想。这又是一个树的结构。一般来说,回溯算法都可以将决策路径画成树的形状,成为一棵搜索树。回溯法执行的过程实际上就是在这棵树上做遍历。刚好这还是一棵二叉树,这又联系上了二叉树的遍历。

    那么,我们可以尝试用遍历树的思路写出回溯法的代码。这里的栈是当前子集里的元素,push 操作是往子集里加元素,pop 操作是从子集中删除元素(撤销选择)。

    最终我们得到完整的代码:

public List<List<Integer>> subsets(int[] nums) {

    Deque<Integer> current = new ArrayDeque<>(nums.length);

    List<List<Integer>> res = new ArrayList<>();

    backtrack(nums, 0, current, res);

    return res;

}

void backtrack(int[] nums, int k, Deque<Integer> current, List<List<Integer>> res) {

    if (k == nums.length) {

        res.add(new ArrayList<>(current));

        return;

    }

    // 不选择第 k 个元素

    backtrack(nums, k+1, current, res);

    // 选择第 k 个元素

    current.addLast(nums[k]);

    backtrack(nums, k+1, current, res);

    current.removeLast();

}

    这份代码看起来和 Path Sum II 的代码非常类似,例如都使用了一个栈,递归的参数也很像。但是递归调用和 push/pop 的操作方式有一些微妙的地方。

    现在,我们是在调用递归函数之前和之后进行 push/pop,这是因为数组本身并没有递归结构,我们需要用 push/pop 操作来营造出不同的选择。两个递归函数的调用其实都是一样的,但因为 current 中的内容不一样,所以其实是两个决策路径。

4、时间复杂度

    回溯算法的复杂度一般都会很高。以 Subsets 问题为例,从搜索树的规模可以看出算法的时间复杂度是非常高的 。不过,回溯法写成这样的复杂度是可接受的,一般的回溯法题目也没有更高效的解法。

5、总结

    通过这两个例题我们看到了回溯算法和二叉树遍历的相似关系。在求解回溯算法的时候,我们可以先构造一个搜索树,在这个树上遍历进行递归求解。

    需要注意的是,例题 Subsets 中的搜索树是二叉树,这只是个巧合。实际上搜索树完全可以是多叉树,而且多叉树才更常见。

    本篇讲解的是比较基础的回溯法思想。回溯法还有很多技巧,例如 Permutation 和 Combination 系列题目,后续还会有文章进行讲解。

6、相关题目

    二叉树遍历的题目(理解遍历思想):

            * 129 - Sum Root to Leaf Numbers[4]

            * 257 - Binary Tree Paths[5]

    回溯法题目(这里只列出比较简单的两道,更多的题目可以在 LeetCode 上寻找 backtracking 标签):

            * 22 - Generate Parentheses[6]

            * 39 - Combination Sum[7]

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,826评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,968评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,234评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,562评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,611评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,482评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,271评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,166评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,608评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,814评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,926评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,644评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,249评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,866评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,991评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,063评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,871评论 2 354

推荐阅读更多精彩内容

  • 单例定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点。饿汉模式public class Singleto...
    小杨不想努力了阅读 375评论 0 4
  • 回溯backtracking 回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜...
    sylvainwang阅读 2,255评论 0 51
  • Remove time complexity: remove from a set is O(1), remove...
    云端漫步_b5aa阅读 632评论 0 0
  • 似乎,,,,返回结果的空间不会算在空间复杂度上 思维的全面性(各种边界条件) 索引与中点的关系:设最后元素的索引为...
    大海一滴写字的地方阅读 519评论 1 0
  • day4 33.二叉搜索树的后序遍历序列 思路:运用递归,不断判断左右子树的后序遍历序列(最后一个数字是根节点,前...
    IAmKepler阅读 232评论 0 0