当前位置: 首页 > article >正文

力扣面试题 08.09. 括号 C语言解法 回溯递归动态规划字符串

题目:

括号。设计一种算法,打印n对括号的所有合法的(例如,开闭一一对应)组合。

说明:解集不能包含重复的子集。

例如,给出 n = 3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

思路:

  • 此题首先想到回溯算法。根据我前几篇的联系,已经对回溯有一定了解了。不了解回溯的自行csdn搜索了解。
  • 回溯算法首先明确终止条件:当临时结果字符串的长度等于2n时,表示括号已添加完毕。将这次的结果存入结果数组。
  • 什么时候应该添加(  呢?当左括号的数量小于n时,可以添加(  
  • 什么时候应该添加)呢?当左括号的数量大于右括号时,可以添加右括号。这样添加的括号总是有效的。
  • 将以上步骤写出回溯函数递归调用,即得解。

C语言代码:

/**
 * Note: The returned array must be malloced, assume caller calls free().
 */
int retSize = 0;
void backtrack(char* temp, char** ret, int n, int len, int lcnt, int rcnt) {
    if(len == 2 * n) {
        temp[len] = '\0';
        ret[retSize] = (char*)malloc((2 * n + 1) * sizeof(char));
        strcpy(ret[retSize], temp);
        retSize++;
        return;
    }
    if(lcnt < n) {
        temp[len] = '(';
        backtrack(temp, ret, n, len + 1, lcnt + 1, rcnt);
    }
    if(lcnt > rcnt) {
        temp[len] = ')';
        backtrack(temp, ret, n, len + 1, lcnt, rcnt + 1);
    }
}

char** generateParenthesis(int n, int* returnSize) {
    retSize = 0;
    char* temp = (char*)malloc((2 * n + 1) * sizeof(char));
    char** ret = (char**)malloc((1 << (2 * n)) * sizeof(char*));
    backtrack(temp, ret, n, 0, 0, 0);
    *returnSize = retSize;
    free(temp);
    return ret;
}

按照回溯模板代码来说,递归之后应该写回溯操作,为什么该题递归操作后面没有回溯操作?

假设我们写了回溯操作,如下:

if (lcnt < n) {
    temp[len] = '(';
    len++;
    backtrack(temp, ret, n, len, lcnt + 1, rcnt);
    len--;  // 回溯
}
if (rcnt < lcnt) {
    temp[len] = ')';
    len++;
    backtrack(temp, ret, n, len, lcnt, rcnt + 1);
    len--;  // 回溯
}

这样的len--是多此一举的。为什么呢?

你之所以在递归后使用 len--,是想恢复 len 的值。但是注意,len 是在递归时通过参数传递的,而不是通过全局变量或指针引用来共享的,因此每次递归调用返回后,len 会自动恢复到调用之前的值,无需手动 len--

具体来说:

  • 在递归调用 backtrack(temp, ret, n, len + 1, lcnt + 1, rcnt) 时,len + 1 被传递给下一层递归函数,当前函数的 len 并未改变。
  • 递归结束返回时,len 仍然保持原来的值,因此不需要手动回溯 len--

回溯操作的重点

在回溯中,我们通常需要撤销上一步的更改,例如:

  • 如果是 动态修改的全局变量或状态(如数组内容、路径列表),我们需要显式撤销这些更改。
  • 如果是通过 参数传递的局部变量(如 len),则递归返回时会自动恢复,不需要显式回溯。

我的代码中 temp[len] 是局部数组,len 是局部变量,因此递归返回后,len 会恢复到递归前的值,len-- 是多余的。

什么时候必须使用 len--

在以下情况下,len-- 是必要的:

  1. len 是全局变量,在所有递归层中共享,递归返回后需要显式回溯。
  2. 修改局部状态不会自动恢复,例如递归前对一个数组 temp 进行修改,但返回后希望撤销这些修改并尝试其他路径。

http://www.kler.cn/a/488616.html

相关文章:

  • (五)ROS通信编程——参数服务器
  • StarRocks Awards 2024 年度贡献人物
  • pytest 参数介绍
  • DEV C++软件下载
  • oracle闪回表
  • 自创“九转化形”算法设计,禁止抄袭
  • 当Elasticsearch索引数据量过多时,可以采取以下措施进行优化和部署
  • Django后端相应类设计
  • Flask----前后端不分离-登录
  • mysql实现对字符列第一个汉字首字母拼音进行A-Z顺序排序,使用gbk编码
  • 计算机网络之---静态路由与动态路由
  • 图像分类、目标定位与目标检测的区别详解:定义、工作原理、应用场景
  • 车联网安全--TLS握手过程详解
  • php命名空间
  • 运维安全中心(堡垒机)
  • Ubuntu 22.04 桥接配置
  • Clisoft SOS设置Server和Project
  • 【JAVA面试】自动装箱和自动拆箱
  • c++程序设计(第3版)系列教程
  • rk3568平台Buildroot编译实践:内核rootfs定制 及常见编译问题
  • 【模型训练】在AutoDL上使用LLamaFactory进行模型训练
  • 思维转换:突破思维桎梏,创造更高效的工作与生活
  • MPI 在深度学习中的应用与分布式训练优化
  • VS2015 + OpenCV + OnnxRuntime-Cpp + YOLOv8 部署
  • 【Java项目】基于SpringBoot的【校园新闻系统】
  • Java面试题~~