算法二刷复盘:LeetCode 79 单词搜索 & 131 分割回文串(Java 回溯精讲)

目录

[一、LeetCode 79 单词搜索(中等)](#一、LeetCode 79 单词搜索(中等))

题目描述

[核心思路:DFS + 回溯 + 标记访问](#核心思路:DFS + 回溯 + 标记访问)

[Java 完整实现](#Java 完整实现)

复杂度分析

[二、LeetCode 131 分割回文串(中等)](#二、LeetCode 131 分割回文串(中等))

题目描述

[核心思路:回溯 + 回文判断](#核心思路:回溯 + 回文判断)

[Java 完整实现](#Java 完整实现)

复杂度分析

三、两道题的回溯模板对比

[四、二刷感悟:回溯题的 "两大核心"](#四、二刷感悟:回溯题的 “两大核心”)


今天复盘两道经典回溯题,它们都是DFS + 回溯剪枝的典型代表,分别是二维矩阵的路径搜索问题和字符串的分割问题,掌握它们能帮你彻底吃透回溯题的核心模板和剪枝技巧。


一、LeetCode 79 单词搜索(中等)

题目描述

给定一个 m x n 二维字符网格 board 和一个字符串单词 word。如果 word 存在于网格中,返回 true;否则,返回 false。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中 "相邻" 单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

核心思路:DFS + 回溯 + 标记访问

这道题的核心是在二维矩阵中进行深度优先搜索,需要注意:

  1. 遍历所有起点:以矩阵中每个单元格为起点,尝试匹配单词的第一个字符。
  2. 标记访问 :为了避免重复访问同一个单元格,访问时将当前单元格标记为特殊字符(如#),回溯时恢复。
  3. 方向搜索:每次搜索四个方向(上下左右),只要有一个方向能匹配后续字符,就继续递归。
  4. 剪枝优化:如果当前字符与单词目标字符不匹配,直接返回。

Java 完整实现

java

运行

复制代码
class Solution {
    public boolean exist(char[][] board, String word) {
        int m = board.length;
        int n = board[0].length;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // 以每个单元格为起点开始搜索
                if (dfs(board, word, i, j, 0)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * DFS 回溯搜索
     * @param board 二维字符网格
     * @param word 目标单词
     * @param i 当前行
     * @param j 当前列
     * @param index 当前匹配到的单词索引
     * @return 是否存在路径
     */
    private boolean dfs(char[][] board, String word, int i, int j, int index) {
        // 终止条件:匹配到单词末尾
        if (index == word.length()) {
            return true;
        }
        // 边界判断或字符不匹配
        if (i < 0 || i >= board.length || j < 0 || j >= board[0].length || board[i][j] != word.charAt(index)) {
            return false;
        }

        // 标记当前单元格为已访问
        char temp = board[i][j];
        board[i][j] = '#';

        // 上下左右四个方向搜索
        boolean found = dfs(board, word, i + 1, j, index + 1)
                || dfs(board, word, i - 1, j, index + 1)
                || dfs(board, word, i, j + 1, index + 1)
                || dfs(board, word, i, j - 1, index + 1);

        // 回溯:恢复单元格字符
        board[i][j] = temp;
        return found;
    }
}

复杂度分析

  • 时间复杂度:O (m×n×3ᴸ),其中 m、n 为矩阵行列数,L 为单词长度。每个单元格最多有 3 个后续方向可走(避免回头)。
  • 空间复杂度:O (L),递归栈深度为单词长度 L。

二、LeetCode 131 分割回文串(中等)

题目描述

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文串 。返回 s 所有可能的分割方案。

核心思路:回溯 + 回文判断

这道题是字符串分割问题,核心是枚举所有可能的分割点,并判断分割出的子串是否为回文串:

  1. 枚举分割点 :从字符串的startIndex位置开始,尝试分割[startIndex, i]区间的子串。
  2. 回文判断:判断当前分割的子串是否为回文串,如果是则加入路径,继续递归处理剩余部分。
  3. 回溯恢复:递归结束后,将当前子串从路径中移除,尝试其他分割方式。

Java 完整实现

java

运行

复制代码
import java.util.ArrayList;
import java.util.List;

class Solution {
    List<List<String>> result = new ArrayList<>();
    List<String> path = new ArrayList<>();

    public List<List<String>> partition(String s) {
        backtrack(s, 0);
        return result;
    }

    /**
     * 回溯分割
     * @param s 目标字符串
     * @param startIndex 起始分割位置
     */
    private void backtrack(String s, int startIndex) {
        // 终止条件:分割到字符串末尾
        if (startIndex == s.length()) {
            result.add(new ArrayList<>(path));
            return;
        }

        // 枚举所有可能的分割终点
        for (int i = startIndex; i < s.length(); i++) {
            // 判断当前子串是否为回文串
            if (isPalindrome(s, startIndex, i)) {
                path.add(s.substring(startIndex, i + 1));
                backtrack(s, i + 1);
                path.remove(path.size() - 1);
            }
        }
    }

    /**
     * 判断子串是否为回文串
     * @param s 字符串
     * @param left 左边界
     * @param right 右边界
     * @return 是否为回文串
     */
    private boolean isPalindrome(String s, int left, int right) {
        while (left < right) {
            if (s.charAt(left) != s.charAt(right)) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

复杂度分析

  • 时间复杂度:O (n×2ⁿ),n 为字符串长度,最坏情况下每个位置都可以分割,且每次分割需要 O (n) 时间判断回文。
  • 空间复杂度:O (n),递归栈深度和路径的最大长度均为 n。

三、两道题的回溯模板对比

表格

对比项 79. 单词搜索 131. 分割回文串
问题类型 二维矩阵路径搜索 字符串分割枚举
关键变量 当前坐标 (i,j)、匹配索引 index 起始分割位置 startIndex
剪枝条件 字符不匹配、越界 子串不是回文串
标记方式 修改原矩阵标记访问 无需额外标记,通过 startIndex 控制
终止条件 匹配到单词末尾 分割到字符串末尾

四、二刷感悟:回溯题的 "两大核心"

  1. 路径标记与恢复:单词搜索中通过修改矩阵标记访问,分割回文串中通过 startIndex 控制分割范围,本质都是为了避免重复访问或重复分割。
  2. 剪枝优化:提前排除不符合条件的分支(如字符不匹配、子串非回文),能大幅减少递归次数,提升效率。

这两道题分别从二维矩阵和字符串两个场景,帮我们巩固了回溯的核心思想,掌握它们后,大部分中等难度的回溯题都能轻松应对。

相关推荐
徐新帅1 小时前
4164:【GESP2512七级】学习⼩组
算法
-凌凌漆-2 小时前
【QML】qml和C++中同时使用单例模式
java·c++·单例模式
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 101. 对称二叉树 | C++ DFS 极简递归模板
c++·leetcode·深度优先
那个失眠的夜2 小时前
RESTful 语法规范 核心注解详解
java·spring·mvc·mybatis
我命由我123452 小时前
Android 开发问题:Unresolved reference: kapt
android·java·java-ee·android studio·android jetpack·android-studio·android runtime
用户298698530142 小时前
Java 实现 ODT 转 PDF:一种简洁的技术实现方案
java·后端
北顾笙9802 小时前
day30-数据结构力扣
数据结构·算法·leetcode
城管不管2 小时前
EasyExcel
java·开发语言·后端·easyexcel
鉴生Eric2 小时前
拉孚BMA系统物联网架构:全面赋能传统楼宇BA系统的数字化转型
java·后端·struts