news 2026/4/3 8:02:43

从零开始写算法——回溯篇3:括号生成 + 单词搜索

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零开始写算法——回溯篇3:括号生成 + 单词搜索

回溯算法(DFS)是算法面试中的重难点。很多同学觉得它难,是因为分不清什么时候该“恢复现场”,什么时候该“标记状态”。

今天我们通过两道经典的 LeetCode 题目——括号生成单词搜索,来对比分析回溯算法的两种不同模式:路径构造模式网格搜索模式

一、 路径构造模式:括号生成 (Generate Parentheses)

这道题是典型的“做选择”问题。你可以把它想象成手里拿着 n 个左括号和 n 个右括号,在 $2n$ 个空位上做填空题。

核心逻辑

我们在递归时需要时刻遵守两个规则,才能保证生成的括号是合法的:

  1. 手里还有存货才能放:只要左括号没用完 (left < n),就可以尝试放左括号。

  2. 不能欠债才能还:只有当前放下的左括号多于右括号时 (right < left),也就是有未闭合的左括号时,才能放右括号。

代码实现

这里使用了标准的push_backpop_back写法。这种写法的核心在于我们是在构造参数vals),每次递归前把东西放进去,递归回来后必须把它拿出来,恢复成原来的样子,以便进行下一次选择。

C++代码实现:

class Solution { // 手里拿着 n 个左括号和 n 个右括号,做填空题,时刻遵守两个规则 // 规则1 : left < n 规则2 : right < left vector<string> ans; void dfs(string& vals, int left, int right, int n) { // Base Case: 左右都用完了,说明构造完成 if (right == n) { ans.push_back(vals); return; } // 尝试放左括号 if (left < n) { vals.push_back('('); // 做选择 dfs(vals, left + 1, right, n); // 进入下一层 vals.pop_back(); // 撤销选择(恢复现场) } // 尝试放右括号 if (right < left) { vals.push_back(')'); // 做选择 dfs(vals, left, right + 1, n); // 进入下一层 vals.pop_back(); // 撤销选择(恢复现场) } } public: vector<string> generateParenthesis(int n) { string vals = ""; dfs(vals, 0, 0, n); return ans; } };

复杂度分析

  • 时间复杂度:O(4^n / sqrt(n))。

    • 这个复杂度与卡特兰数(Catalan Number)有关。简单理解,每个位置有两种选择,共有 2n 个位置,但因为有剪枝(规则限制),实际数量远小于 2^(2n)。

  • 空间复杂度:O(n)。

    • 主要消耗在递归调用栈和存储当前路径的vals字符串上,最大深度为 2n。


二、 网格搜索模式:单词搜索 (Word Search)

这道题属于“图/网格遍历”。与上一题不同,这里的“恢复现场”不是为了构造字符串,而是为了防止走回头路

核心逻辑

我们把每一个点作为起点,执行 DFS 搜索。

这里有一个非常关键的细节:什么时候标记节点?

  • 错误做法:在进入下一层递归前,标记下一个位置 (nx, ny)。这会导致起点无法被标记,以及逻辑判断复杂化。

  • 正确做法标记当前位置 (x, y)。也就是“进门后再锁门”。一旦进入 DFS 函数,先判断当前点是否匹配,匹配的话就标记为已访问(比如改为.),递归结束后再改回来。

代码实现

注意看代码中的注释,关于恢复现场和循环变量的处理是易错点。

C++代码实现:

class Solution { // 思路: 把每一个点作为起点然后对他执行dfs去遍历搜索是否存在这样的单词 // 每次上下左右找之前,把自己当前位置xy标记为. 注意是当前位置不是下一个位置 如果改的是nxny,那么下一步进去就无法通过borad[x][y] 和 word判断了 // 注意: 既然要恢复现场就要提前记录w,注意for里面不要用变量i会冲突 int dx[4] = {0, 1, 0, -1}; int dy[4] = {-1, 0, 1, 0}; bool ans; // i 代表当前匹配到了 word 的第几个字符 bool dfs(vector<vector<char>>& board, string word, int n, int i, int x, int y) { // 1. 判断当前格子字符是否匹配 if (word[i] != board[x][y]){ return false; } // 2. 匹配完成 if (i == n - 1) return true; // 3. 标记当前节点 (Backtracking 核心) // 提前记录便于恢复现场 char w = board[x][y]; // 避免重复使用到,标记为 '.' board[x][y] = '.'; // 4. 遍历四个方向 // 注意这里不能用 i 做为变量名(会与参数 i 冲突) for (int j = 0; j < 4; ++j) { int nx = x + dx[j]; int ny = y + dy[j]; // 越界检查及是否已访问检查 if (nx < 0 || nx >= board.size() || ny < 0 || ny >= board[0].size() || board[nx][ny] == '.') { continue; } // 只要有一条路走通了,就直接返回 true if (dfs(board, word, n, i + 1, nx, ny)) return true; } // 5. 恢复现场 board[x][y] = w; return false; } public: bool exist(vector<vector<char>>& board, string word) { int n = word.size(); for (int i = 0; i < board.size(); ++i) { for (int j = 0; j < board[0].size(); ++j) { // 以每个格子为起点尝试 ans = dfs(board, word, n, 0, i, j); if (ans == true) return ans; } } return ans; } };

复杂度分析

  • 时间复杂度:O(M * N * 3^L)。

    • M 和 N 是网格的长宽,我们需要遍历每一个格子作为起点(M * N)。

    • L 是字符串word的长度。在 DFS 函数中,除了第一次有 4 个分支,之后因为不能走回头路,最多只有 3 个分支。所以是 3^L。

  • 空间复杂度:O(L)。

    • 空间消耗主要来自递归栈的深度,最大深度也就是单词的长度 L。


总结

通过这两道题,我们可以总结出 DFS 回溯的两个黄金法则:

  1. 构造类回溯(括号生成):目的是拼凑出一个结果。我们通过push_back添加元素进入下一层,出来后pop_back撤销。

  2. 搜索类回溯(单词搜索):目的是在图中寻找路径。我们通过修改board[x][y]为特殊字符来标记“当前正在访问”,递归结束后还原字符,以免影响其他路径的搜索。

记住:每一个 DFS 函数只负责管理自己脚下的节点(标记和恢复),不要试图去管理下一层节点的标记,否则容易出现逻辑死循环。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/3/24 1:12:04

全栈突围:智谱GLM-Image × 昇腾·昇思携手走出“无人区”

技术只有变得足够“便宜”&#xff0c;才能实现真正“普及”&#xff0c;从而深度融入到工作与生活的方方面面。所以&#xff0c;当GLM-Image在API调用模式下生成一张图片只需0.1元时&#xff0c;价格仅为海外同类产品的1/10至1/3&#xff0c;全球AI市场都为之震撼。GLM-Image是…

作者头像 李华
网站建设 2026/3/21 4:20:54

【快速EI检索 | 高录用 | EI检索稳定 | 对学生友好会议 | JPCS出版有ISSN号,高录用,见刊快】2026年航空航天、智能感知与控制国际学术会议

2026年航空航天、智能感知与控制国际学术会议 2026 International Conference on Aerospace, Intelligent Perception and Control (AIPC 2026) 2026年2月6日-2月8日 &#xff5c;中国-昆明 大会官网&#xff1a;www.icaipc.org 截稿时间&#xff1a;见官网&#xff08;早投…

作者头像 李华
网站建设 2026/3/22 1:53:45

吃透这 5 个 C/C++ 就业方向,应届生也能拿高薪 Offer

如果你现在正为 C 开发方向感到迷茫&#xff0c;不知道该往哪走&#xff0c;那这篇内容或许能给你一些思路。 相信我&#xff0c;几年后再回头看&#xff0c;你一定会感激此刻为方向努力探索的自己。 C 作为当下依然主流的面向对象语言&#xff0c;应用场景非常广泛 —— 总有…

作者头像 李华
网站建设 2026/3/19 18:42:59

PostgreSQL 性能:云端与本地的延迟分析

PostgreSQL 在各行各业的关键应用中具有极高适用性。尽管 PostgreSQL 提供了良好的性能&#xff0c;但仍存在一些用户不太关注但对整体效率与速度至关重要的问题。多数人认为增加 CPU 核数、更快的存储、更大内存即可提升性能&#xff0c;但还有同样重要的因素需要关注——那就…

作者头像 李华
网站建设 2026/4/3 2:44:51

计算机毕业设计springboot餐厅菜品评价系统设计与实现基于 SpringBoot 的餐厅菜品口碑管理与可视化平台 SpringBoot 驱动的智能菜品反馈与推荐系统

计算机毕业设计springboot餐厅菜品评价系统设计与实现_4d5g9 &#xff08;配套有源码 程序 mysql数据库 论文&#xff09; 本套源码可以在文本联xi,先看具体系统功能演示视频领取&#xff0c;可分享源码参考。外卖平台把“吃”这件事从线下搬到线上&#xff0c;却也让“好不好吃…

作者头像 李华
网站建设 2026/3/28 20:29:58

2026年项目管理软件测评:10款主流项目管理工具对比与推荐

本文用“计划—执行—可视化—度量—集成—落地治理”六个维度&#xff0c;测评 10 款项目管理软件&#xff1a;ONES、Jira、Asana、monday.com、ClickUp、Smartsheet、Azure Boards、GitLab、Linear、OpenProject&#xff0c;帮你在不同管理模式与团队文化下做更稳的选择。我印…

作者头像 李华