🌟 回溯算法原来这么简单: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. 回溯:撤销选择,尝试其他可能性
相关推荐
sulikey9 分钟前
一文彻底理解:如何判断单链表是否成环(含原理推导与环入口推算)
c++·算法·leetcode·链表·floyd·快慢指针·floyd判圈算法
Swift社区16 分钟前
LeetCode 402 - 移掉 K 位数字
算法·leetcode·职场和发展
huangql52019 分钟前
截图功能技术详解:从原理到实现的完整指南
前端·html5
长空任鸟飞_阿康34 分钟前
Node.js 核心模块详解:fs 模块原理与应用
前端·人工智能·ai·node.js
_码力全开_40 分钟前
P1005 [NOIP 2007 提高组] 矩阵取数游戏
java·c语言·c++·python·算法·矩阵·go
墨染点香44 分钟前
LeetCode 刷题【124. 二叉树中的最大路径和、125. 验证回文串】
算法·leetcode·职场和发展
Camel卡蒙44 分钟前
红黑树详细介绍(五大规则、保持平衡操作、Java实现)
java·开发语言·算法
这儿有一堆花1 小时前
网站链接重定向原理
前端