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

从零开始写算法——回溯篇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 函数只负责管理自己脚下的节点(标记和恢复),不要试图去管理下一层节点的标记,否则容易出现逻辑死循环。

http://www.jsqmd.com/news/275828/

相关文章:

  • 2026年阜阳沙发供货厂家综合评估:甄选3家实力厂商,赋能企业高效采购
  • 自动化毕设 stm32的火灾监控与可视化系统(源码+硬件+论文)
  • LangChain多智能体系统详解:5种架构模式与实战案例实现
  • 【快速EI检索 | 海外高校主办丨EI稳定检索 | 征稿范围广 】2026年生成式人工智能与教育国际学术会议(GAIE 2026)
  • 网易企业邮箱珠海服务商:这5个关键优势你必须知道!
  • 【快速EI检索 | 高录用 | EI检索稳定 | 对学生友好会议 | JPCS出版有ISSN号,高录用,见刊快】2026年航空航天、智能感知与控制国际学术会议
  • SpringBoot+Vue 夕阳红公寓管理系统平台完整项目源码+SQL脚本+接口文档【Java Web毕设】
  • SpringBoot+Vue 宠物领养系统管理平台源码【适合毕设/课设/学习】Java+MySQL
  • 大厂Java岗面试复盘实录!
  • 打破协议壁垒:疆鸿智能DEVICENET与EtherCAT在新能源产线中的毫秒级协同
  • 吃透这 5 个 C/C++ 就业方向,应届生也能拿高薪 Offer
  • 华强北商城二手手机管理系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
  • 高并发经验:所有Java程序员必备!
  • IT就业寒冬,程序员还有必要死磕技术吗?
  • 【2025最新】基于SpringBoot+Vue的mvc高校办公室行政事务管理系统管理系统源码+MyBatis+MySQL
  • 计算机毕业设计springboot酒店管理系统 基于SpringBoot的宾馆业务综合管理平台 融合SpringBoot框架的智慧旅店运营系统
  • 赋能工作与生活:2026 年 7 大就绪 AI 能力汇总
  • 夕阳红公寓管理系统信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
  • 在线家具商城设计与实现信息管理系统源码-SpringBoot后端+Vue前端+MySQL【可直接运行】
  • 零碳工厂怎么建?从 2026 指导意见到企业微电网的一条落地路径
  • 企业级在线问卷调查系统管理系统源码|SpringBoot+Vue+MyBatis架构+MySQL数据库【完整版】
  • 【2025最新】基于SpringBoot+Vue的在线问卷调查系统管理系统源码+MyBatis+MySQL
  • SpringBoot+Vue 欢迪迈手机商城设计与开发平台完整项目源码+SQL脚本+接口文档【Java Web毕设】
  • 传统酒业遇上排队免单:成义烧坊的线上营销突围之路
  • 企业级在线家具商城设计与实现管理系统源码|SpringBoot+Vue+MyBatis架构+MySQL数据库【完整版】
  • 可信AI--去中心化RAG技术探索
  • 传统AI提示设计vs用户行为预测:提示工程架构师该选哪条路?(深度对比)
  • 偏远地区市场营销应届生就业难?远程工作+自我提升,打破地域限制
  • 掌握大数据领域 OLAP,实现数据驱动决策
  • 当AI面试进入“深水区”:核心竞争力聚焦打分准确性与候选人体验