🌟 回溯算法原来这么简单:10道经典题,一看就明白!

LeetCode JavaScript 回溯算法 10 大经典案例

目录

  1. 全排列
  2. 组合总和
  3. 子集
  4. 括号生成
  5. N皇后
  6. 单词搜索
  7. 路径总和
  8. 分割回文串
  9. 解数独
  10. 电话号码的字母组合

1. 全排列

题目描述

给定一个不含重复数字的数组 nums,返回其所有可能的全排列。

解题思路

使用回溯算法,通过交换元素位置来生成所有排列。

JavaScript 实现

js 复制代码
function permute(nums) {
    const result = [];
    
    function backtrack(start) {
        // 递归终止条件
        if (start === nums.length) {
            result.push([...nums]);
            return;
        }
        
        // 尝试每个位置的元素
        for (let i = start; i < nums.length; i++) {
            // 交换元素
            [nums[start], nums[i]] = [nums[i], nums[start]];
            
            // 递归处理下一个位置
            backtrack(start + 1);
            
            // 回溯:恢复交换前的状态
            [nums[start], nums[i]] = [nums[i], nums[start]];
        }
    }
    
    backtrack(0);
    return result;
}


// 示例
console.log(permute([1, 2, 3])); 
// 输出: [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

2. 组合总和

题目描述

给定一个无重复元素的数组 candidates 和一个目标数 target,找出 candidates 中所有可以使数字和为 target 的组合。

解题思路

使用回溯算法,通过递归尝试每个数字,注意避免重复组合。

JavaScript 实现

js 复制代码
function combinationSum(candidates, target) {
    const result = [];
    
    function backtrack(start, currentCombination, remainingTarget) {
        // 递归终止条件
        if (remainingTarget === 0) {
            result.push([...currentCombination]);
            return;
        }
        
        if (remainingTarget < 0) {
            return;
        }
        
        // 从start开始遍历,避免重复
        for (let i = start; i < candidates.length; i++) {
            currentCombination.push(candidates[i]);
            // 可以重复使用,所以传入i而不是i+1
            backtrack(i, currentCombination, remainingTarget - candidates[i]);
            currentCombination.pop(); // 回溯
        }
    }
    
    backtrack(0, [], target);
    return result;
}


// 示例
console.log(combinationSum([2, 3, 6, 7], 7)); 
// 输出: [[2,2,3],[7]]

3. 子集

题目描述

给定一个整数数组 nums,返回该数组所有可能的子集(幂集)。

解题思路

对每个元素都有选择或不选择两种状态,使用回溯算法生成所有组合。

JavaScript 实现

js 复制代码
function subsets(nums) {
    const result = [];
    
    function backtrack(start, currentSubset) {
        // 每个节点都是一个解
        result.push([...currentSubset]);
        
        // 从start开始遍历
        for (let i = start; i < nums.length; i++) {
            currentSubset.push(nums[i]);
            backtrack(i + 1, currentSubset);
            currentSubset.pop(); // 回溯
        }
    }
    
    backtrack(0, []);
    return result;
}


// 示例
console.log(subsets([1, 2, 3])); 
// 输出: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

4. 括号生成

题目描述

数字 n 代表生成括号的对数,请你设计一个函数,用于生成所有可能的并且有效的括号组合。

解题思路

使用回溯算法,维护左括号和右括号的数量,确保括号有效性。

JavaScript 实现

js 复制代码
function generateParenthesis(n) {
    const result = [];
    
    function backtrack(current, leftCount, rightCount) {
        // 递归终止条件
        if (current.length === 2 * n) {
            result.push(current);
            return;
        }
        
        // 添加左括号的条件
        if (leftCount < n) {
            backtrack(current + '(', leftCount + 1, rightCount);
        }
        
        // 添加右括号的条件
        if (rightCount < leftCount) {
            backtrack(current + ')', leftCount, rightCount + 1);
        }
    }
    
    backtrack('', 0, 0);
    return result;
}


// 示例
console.log(generateParenthesis(3)); 
// 输出: ["((()))","(()())","(())()","()(())","()()()"]

u5. N皇后

题目描述

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

解题思路

逐行放置皇后,使用回溯算法检查每种放置方案的有效性。

JavaScript 实现

js 复制代码
function solveNQueens(n) {
    const result = [];
    const board = new Array(n).fill().map(() => new Array(n).fill('.'));
    
    function isValid(row, col) {
        // 检查列
        for (let i = 0; i < row; i++) {
            if (board[i][col] === 'Q') return false;
        }
        
        // 检查左上对角线
        for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
            if (board[i][j] === 'Q') return false;
        }
        
        // 检查右上对角线
        for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
            if (board[i][j] === 'Q') return false;
        }
        
        return true;
    }
    
    function backtrack(row) {
        if (row === n) {
            // 转换为字符串数组
            result.push(board.map(row => row.join('')));
            return;
        }
        
        for (let col = 0; col < n; col++) {
            if (isValid(row, col)) {
                board[row][col] = 'Q';
                backtrack(row + 1);
                board[row][col] = '.'; // 回溯
            }
        }
    }
    
    backtrack(0);
    return result;
}


// 示例
console.log(solveNQueens(4));

6. 单词搜索

题目描述

给定一个二维网格和一个单词,找出该单词是否存在于网格中。

解题思路

对每个位置进行深度优先搜索,使用回溯算法标记已访问的格子。

JavaScript 实现

js 复制代码
function exist(board, word) {
    if (!board || !board.length || !word) return false;
    
    const rows = board.length;
    const cols = board[0].length;
    
    function dfs(row, col, index) {
        // 边界检查
        if (row < 0 || row >= rows || col < 0 || col >= cols) {
            return false;
        }
        
        // 字符不匹配
        if (board[row][col] !== word[index]) {
            return false;
        }
        
        // 找到完整单词
        if (index === word.length - 1) {
            return true;
        }
        
        // 标记当前格子为已访问
        const temp = board[row][col];
        board[row][col] = '#';
        
        // 四个方向搜索
        const found = dfs(row + 1, col, index + 1) ||
                     dfs(row - 1, col, index + 1) ||
                     dfs(row, col + 1, index + 1) ||
                     dfs(row, col - 1, index + 1);
        
        // 回溯:恢复原字符
        board[row][col] = temp;
        
        return found;
    }
    
    // 从每个位置开始搜索
    for (let i = 0; i < rows; i++) {
        for (let j = 0; j < cols; j++) {
            if (dfs(i, j, 0)) {
                return true;
            }
        }
    }
    
    return false;
}


// 示例
const board = [
    ['A','B','C','E'],
    ['S','F','C','S'],
    ['A','D','E','E']
];
console.log(exist(board, "ABCCED")); // true

7. 路径总和

题目描述

给定一个二叉树和一个目标和,判断该树中是否存在从根节点到叶子节点的路径,使得路径上所有节点值之和等于目标和。

解题思路

使用深度优先搜索,回溯算法记录路径和。

JavaScript 实现

js 复制代码
// 定义二叉树节点
class TreeNode {
    constructor(val, left = null, right = null) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}


function hasPathSum(root, targetSum) {
    if (!root) return false;
    
    function dfs(node, currentSum) {
        if (!node) return false;
        
        // 更新当前路径和
        currentSum += node.val;
        
        // 到达叶子节点
        if (!node.left && !node.right) {
            return currentSum === targetSum;
        }
        
        // 递归左右子树
        return dfs(node.left, currentSum) || dfs(node.right, currentSum);
    }
    
    return dfs(root, 0);
}


// 示例
const root = new TreeNode(5);
root.left = new TreeNode(4);
root.right = new TreeNode(8);
root.left.left = new TreeNode(11);
root.left.left.left = new TreeNode(7);
root.left.left.right = new TreeNode(2);


console.log(hasPathSum(root, 22)); // true

8. 分割回文串

题目描述

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

解题思路

使用回溯算法,先判断当前子串是否为回文串,然后继续分割剩余部分。

JavaScript 实现

js 复制代码
function partition(s) {
    const result = [];
    
    function isPalindrome(str) {
        let left = 0, right = str.length - 1;
        while (left < right) {
            if (str[left] !== str[right]) return false;
            left++;
            right--;
        }
        return true;
    }
    
    function backtrack(start, currentPartition) {
        // 递归终止条件
        if (start === s.length) {
            result.push([...currentPartition]);
            return;
        }
        
        // 尝试所有可能的分割点
        for (let i = start; i < s.length; i++) {
            const substring = s.substring(start, i + 1);
            
            // 如果是回文串,则继续分割
            if (isPalindrome(substring)) {
                currentPartition.push(substring);
                backtrack(i + 1, currentPartition);
                currentPartition.pop(); // 回溯
            }
        }
    }
    
    backtrack(0, []);
    return result;
}


// 示例
console.log(partition("aab")); 
// 输出: [["a","a","b"],["aa","b"]]

9. 解数独

题目描述

编写一个程序,通过填充空格来解决数独问题。

解题思路

使用回溯算法,对每个空格尝试1-9的数字,检查是否满足数独规则。

JavaScript 实现

js 复制代码
function solveSudoku(board) {
    function isValid(board, row, col, num) {
        // 检查行
        for (let i = 0; i < 9; i++) {
            if (board[row][i] === num) return false;
        }
        
        // 检查列
        for (let i = 0; i < 9; i++) {
            if (board[i][col] === num) return false;
        }
        
        // 检查3x3宫格
        const boxRow = Math.floor(row / 3) * 3;
        const boxCol = Math.floor(col / 3) * 3;
        for (let i = 0; i < 3; i++) {
            for (let j = 0; j < 3; j++) {
                if (board[boxRow + i][boxCol + j] === num) return false;
            }
        }
        
        return true;
    }
    
    function backtrack() {
        for (let row = 0; row < 9; row++) {
            for (let col = 0; col < 9; col++) {
                if (board[row][col] === '.') {
                    for (let num = 1; num <= 9; num++) {
                        const charNum = num.toString();
                        if (isValid(board, row, col, charNum)) {
                            board[row][col] = charNum;
                            if (backtrack()) return true;
                            board[row][col] = '.'; // 回溯
                        }
                    }
                    return false;
                }
            }
        }
        return true;
    }
    
    backtrack();
    return board;
}

10. 电话号码的字母组合

题目描述

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

解题思路

使用回溯算法,将每个数字对应的字母依次组合。

JavaScript 实现

js 复制代码
function letterCombinations(digits) {
    if (!digits) return [];
    
    const phoneMap = {
        '2': 'abc',
        '3': 'def',
        '4': 'ghi',
        '5': 'jkl',
        '6': 'mno',
        '7': 'pqrs',
        '8': 'tuv',
        '9': 'wxyz'
    };
    
    const result = [];
    
    function backtrack(index, currentCombination) {
        // 递归终止条件
        if (index === digits.length) {
            result.push(currentCombination);
            return;
        }
        
        const digit = digits[index];
        const letters = phoneMap[digit];
        
        for (let i = 0; i < letters.length; i++) {
            backtrack(index + 1, currentCombination + letters[i]);
        }
    }
    
    backtrack(0, '');
    return result;
}


// 示例
console.log(letterCombinations("23")); 
// 输出: ["ad","ae","af","bd","be","bf","cd","ce","cf"]

小结

回溯算法的核心思想是:

  1. 选择:在每一步做出选择
  2. 约束:检查选择是否符合约束条件
  3. 目标:达到目标状态时记录结果
  4. 回溯:撤销选择,尝试其他可能性
相关推荐
huashengzsj21 分钟前
Shopify个人建站如何搭建独立站:从零开始的完整指南
前端·网络·人工智能
程序猿小泓32 分钟前
从 Claude Code 学 Agent Harness:一个前端工程师的 AI Agent 学习笔记
前端·人工智能·学习
旖-旎1 小时前
《LeetCode 53 最大子数组和 || LeetCode 918 环形子数组的最大和》
c++·算法·leetcode·动态规划
变量未定义~1 小时前
单调栈+倍增思想 皇家守卫【算法赛】、单调队列 附近最小
算法
QN1幻化引擎1 小时前
给 AI 做一次「意识体检」——基于 QN1 幻化引擎的灵鉴意识识别框架与 DalinX V5 实测
大数据·数据结构·人工智能·算法·架构
拂拉氏1 小时前
【知识讲解】 AVL树从基本成员的介绍到核心接口的实现(插入、判断、删除等等)
数据结构·算法·avl树
柒和远方1 小时前
从审计日志到可下载证据包:事务型 Outbox、租约 fencing 与保留水位
前端·后端·架构
minglie12 小时前
npm ali-oss库依赖冲突
前端·npm
CryptoPP2 小时前
BSE股票K线数据接入实战:从接口调用到前端图表展示
大数据·前端·网络·人工智能·websocket·网络协议
可靠的仙人掌2 小时前
SAC(Soft Actor-Critic)算法底座
开发语言·算法·php