🌟 回溯算法原来这么简单: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. 回溯:撤销选择,尝试其他可能性
相关推荐
豆瓣鸡7 分钟前
算法日记 - Day3
java·开发语言·算法
白白白小纯20 分钟前
算法篇—反转链表
c语言·数据结构·算法·leetcode
不在逃避q24 分钟前
Trae/Vs Code/Cursor命令行无法跑npm命令
前端·arcgis·npm
Achou.Wang35 分钟前
深入理解go语言-第5章 并发编程——Go的灵魂
大数据·算法·golang
夏殇之殁38 分钟前
包中创建自定义列表项。 . 使用自定义列表项进行数据绑定 . 将天气预报数据保存到本地内存表,通过LiveBindings进行显示。 ...
服务器·前端·javascript
The Chosen One98543 分钟前
高进度算法模板速记(待完善)
java·前端·算法
陈随易1 小时前
MoonBit抓包模块pcap,查看电脑的每一次联网通信
前端·后端·程序员
新中地GIS开发老师2 小时前
WebGIS开发学生作品|低空航天管理与航线规划系统
前端·javascript·webgis·三维gis开发
用户059540174462 小时前
大模型对话记忆持久化踩坑实录:用 Playwright 自动化测了 300 次,终于揪出会话丢失的真凶
前端·css
丙氨酸長鏈2 小时前
Web前端入门第 问:JavaScript 一个简单的 IndexedDB 数据库入门示例
前端·javascript·数据库