🌟 回溯算法原来这么简单: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. 回溯:撤销选择,尝试其他可能性
相关推荐
Up九五小庞13 分钟前
开源埋点分析平台 ClkLog 本地部署 + Web JS 埋点测试实战--九五小庞
前端·javascript·开源
yyy(十一月限定版)1 小时前
寒假集训4——二分排序
算法
星火开发设计1 小时前
类型别名 typedef:让复杂类型更简洁
开发语言·c++·学习·算法·函数·知识
醉颜凉1 小时前
【LeetCode】打家劫舍III
c语言·算法·leetcode·树 深度优先搜索·动态规划 二叉树
qq_177767371 小时前
React Native鸿蒙跨平台数据使用监控应用技术,通过setInterval每5秒更新一次数据使用情况和套餐使用情况,模拟了真实应用中的数据监控场景
开发语言·前端·javascript·react native·react.js·ecmascript·harmonyos
达文汐1 小时前
【困难】力扣算法题解析LeetCode332:重新安排行程
java·数据结构·经验分享·算法·leetcode·力扣
一匹电信狗1 小时前
【LeetCode_21】合并两个有序链表
c语言·开发语言·数据结构·c++·算法·leetcode·stl
User_芊芊君子1 小时前
【LeetCode经典题解】搞定二叉树最近公共祖先:递归法+栈存路径法,附代码实现
算法·leetcode·职场和发展
培风图南以星河揽胜1 小时前
Java版LeetCode热题100之零钱兑换:动态规划经典问题深度解析
java·leetcode·动态规划
烬头88211 小时前
React Native鸿蒙跨平台应用实现了onCategoryPress等核心函数,用于处理用户交互和状态更新,通过计算已支出和剩余预算
前端·javascript·react native·react.js·ecmascript·交互·harmonyos