🌟 回溯算法原来这么简单: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. 回溯:撤销选择,尝试其他可能性
相关推荐
冻感糕人~9 分钟前
【珍藏必备】ReAct框架实战指南:从零开始构建AI智能体,让大模型学会思考与行动
java·前端·人工智能·react.js·大模型·就业·大模型学习
程序员agions13 分钟前
2026年,“配置工程师“终于死绝了
前端·程序人生
驱动探索者14 分钟前
linux mailbox 学习
linux·学习·算法
ringking12317 分钟前
autoware-1:安装环境cuda/cudnn/tensorRT库函数的判断
人工智能·算法·机器学习
alice--小文子19 分钟前
cursor-mcp工具使用
java·服务器·前端
晚霞的不甘28 分钟前
揭秘 CANN 内存管理:如何让大模型在小设备上“轻装上阵”?
前端·数据库·经验分享·flutter·3d
小迷糊的学习记录37 分钟前
0.1 + 0.2 不等于 0.3
前端·javascript·面试
大闲在人1 小时前
8. 供应链与制造过程术语:产能
算法·制造·供应链管理·智能制造·工业工程
一只小小的芙厨1 小时前
寒假集训笔记·以点为对象的树形DP
c++·算法
历程里程碑1 小时前
普通数组----合并区间
java·数据结构·python·算法·leetcode·职场和发展·tornado