挑战刷leetcode第22天(回溯-括号生成-22)

144 阅读4分钟

哪吒大战括号怪:递归的奇妙冒险

大家好,我是哪吒,今天我要带大家进入一个神奇的世界——括号生成的世界。你可能觉得括号有什么好玩的?别急,听我慢慢道来。

1. 问题描述

有一天,我接到了玉帝的任务:生成所有可能的、有效的括号组合。听起来很简单,对吧?但当我看到任务要求时,我傻眼了:数字 n 代表生成括号的对数,比如 n=3 时,要生成所有可能的、有效的 3 对括号组合。

示例:

  • 输入:n = 3
  • 输出:["((()))","(()())","(())()","()(())","()()()"]

看起来挺简单的,但当我开始动手时,发现这可不是一件容易的事。于是,我决定用我的法宝——递归来解决这个问题。

2. 递归的奇妙之处

递归,就像是我手中的混天绫,看似简单,但威力无穷。它的核心思想是:把大问题分解成小问题,直到小问题简单到可以直接解决

在这个问题中,我们可以把生成括号的过程看作是一个决策树。每一步,我们都有两个选择:加一个左括号,或者加一个右括号。但要注意,右括号的数量不能超过左括号,否则就会生成无效的括号组合。

image.png

image.png

3. 递归的实现思路

让我们来看看如何用递归来实现这个功能。

Java 实现:

public List<String> generateParenthesis(int n) {
    List<String> res = new ArrayList<>();
    if (n == 0) {
        return res;
    }
    dfs(res, "", 0, 0, n);
    return res;
}

public void dfs(List<String> res, String sb, int left, int right, int n) {
    // 如果左括号或右括号超过n,或者右括号超过左括号,直接返回
    if (left > n || right > left) {
        return;
    }
    // 如果当前字符串长度达到2*n,说明找到一个有效的括号组合
    if (sb.length() == 2 * n) {
        res.add(sb.toString());
        return;
    }
    // 尝试加一个左括号
    dfs(res, sb + "(", left + 1, right, n);
    // 尝试加一个右括号
    dfs(res, sb + ")", left, right + 1, n);
}

C++ 实现:

vector<string> generateParenthesis(int n) {
    vector<string> vector;
    dfs(vector, "", 0, 0, n);
    return vector;
}

void dfs(vector<string>& vector, string s, int left, int right, int n) {
    // 如果左括号或右括号超过n,或者右括号超过左括号,直接返回
    if (left > n || right > left) {
        return;
    }
    // 如果当前字符串长度达到2*n,说明找到一个有效的括号组合
    if (s.size() == 2 * n) {
        vector.emplace_back(s);
        return;
    }

    // 尝试加一个左括号
    dfs(vector, s + "(", left + 1, right, n);
    // 尝试加一个右括号
    dfs(vector, s + ")", left, right + 1, n);
}

4. 递归的详细解释

让我们一步一步地分析这个递归函数。

  1. 递归终止条件

    • 如果左括号的数量超过了 n,或者右括号的数量超过了左括号,那么当前的括号组合是无效的,直接返回。
    • 如果当前字符串的长度达到了 2 * n,说明我们已经生成了一个有效的括号组合,将其加入结果列表中。
  2. 递归调用

    • 每次递归调用时,我们有两个选择:加一个左括号,或者加一个右括号
    • 如果选择加左括号,那么左括号的数量加 1。
    • 如果选择加右括号,那么右括号的数量加 1。
  3. 回溯

    • 每次递归调用结束后,程序会自动回到上一层,继续尝试其他可能的组合。

5. 递归的幽默比喻

想象一下,你正在玩一个游戏,每次你可以选择向左走或者向右走。但有一个规则:你不能向右走太多,否则会掉进深渊。这个规则就像是括号生成中的右括号不能超过左括号

每次你做出选择后,游戏会自动保存你的进度。如果你走错了,游戏会让你回到上一个选择点,重新尝试。这就是递归回溯的精髓。

6. 坚持的意义

在这个过程中,我遇到了很多困难。有时候,我会生成无效的括号组合,有时候,我会陷入无限递归的深渊。但每次失败后,我都会回到上一个选择点,重新尝试。这让我明白了一个道理:坚持是成功的关键

无论你是在编程,还是在生活中,遇到困难时,不要轻易放弃。每一次失败,都是通往成功的一步。

7. 总结

通过这次括号生成的冒险,我不仅学会了如何使用递归解决问题,还深刻体会到了坚持的重要性。希望我的经历能对你有所启发,让你在编程的道路上越走越远。

最后,如果你觉得这篇文章对你有帮助,别忘了点赞和分享哦!我是哪吒,我们下次再见!


思考题:你能用递归的方法解决其他类似的问题吗?比如生成所有可能的二叉树结构?欢迎在评论区分享你的想法!