文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

十五周算法训练营——回溯算法

2024-11-30 13:23

关注

今天是十五周算法训练营的第十周,主要讲回溯算法专题。

解决一个回溯问题,实际上就是一个决策树的遍历过程,只需要思考三个问题:

  1. 路径:也就是已经做出的选择
  2. 选择列表:也就是你当前可以做的选择
  3. 结束条件:也就是到达决策树底层,无法再做选择的条件

全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以按任意顺序 返回答案。

示例 1:

输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]。

function permute(nums) {
    const result = [];

    
    const backtrack = (track, nums) => {
        // 结束条件
        if (track.length === nums.length) {
            result.push([...track]);
            return;
        }

        for (let i = 0; i < nums.length; i++) {
            if (track.includes(nums[i])) {
                continue;
            }

            // 做选择
            track.push(nums[i]);
            // 进入下一层
            backtrack(track, nums);
            // 取消选择
            track.pop();
        }
    };

    const track = [];

    backtrack(track, nums);

    return result;
}

const nums = [1, 2, 3];
console.log(permute(nums));

全排列II

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

示例 1:

输入:nums = [1,1,2] 输出: [[1,1,2], [1,2,1], [2,1,1]]。

function permuteUnique(nums) {
    const result = [];

    
    const backtrack = (sortNums, track, visits) => {
        if (track.length === sortNums.length) {
            result.push([...track]);
            return;
        }

        for (let i = 0; i < sortNums.length; i++) {
            // i > 0 && nums[i] === nums[i - 1] && !visits[i - 1]
            // 上述一行的判断是当前值和前一个值相等,但是前一个值没有被访问过,表示前一个值不在该值路径的上几层,表示在同一层前面出现过,所以该值要跳过
            if (visits[i] || (i > 0 && sortNums[i] === sortNums[i - 1] && !visits[i - 1])) {
                continue;
            }

            // 做选择
            track.push(sortNums[i]);
            visits[i] = 1;

            // 选择下一层
            backtrack(sortNums, track, visits);

            // 取消选择
            track.pop();
            visits[i] = 0;
        }
    };

    const track = [];

    nums.sort((a, b) => a - b);

    const visits = (new Array(nums.length)).fill(0);

    backtrack(nums, track, visits);

    return result;
}

const nums = [1, 1, 2];
console.log(permuteUnique(nums));

N皇后

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

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

给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。

每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。

示例 1:

输入:n = 4 输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]] 解释:如上图所示,4 皇后问题存在两个不同的解法。

// 该问题本质上和全排列问题差不多,决策树上的每一层表示棋盘上的每一行,每个节点可以做出的选择是在该行的任意一列放置一个皇后
function solveNQueens(n) {
    const result = [];

    // 判断是否可以在[row, col]放置皇后
    const isValid = (board, row, col) => {
        // 判断该列是否存在皇后
        for (let i = 0; i < row; i++) {
            if (board[i][col] === 'Q') {
                return false;
            }
        }

        // 判断左上方是否存在皇后冲突
        let leftRow = row - 1;
        let leftCol = col - 1;
        while (leftRow >= 0 && leftCol >= 0) {
            if (board[leftRow][leftCol] === 'Q') {
                return false;
            }
            leftRow--;
            leftCol--;
        }

        // 判断右上是否存在皇后冲突
        let rightRow = row - 1;
        let rightCol = col + 1;
        while (rightRow >= 0 && rightCol < n) {
            if (board[rightRow][rightCol] === 'Q') {
                return false;
            }
            rightRow--;
            rightCol++;
        }

        return true;
    };
    

    const backtrack = (board, row) => {
        // 触发结束条件
        if (row === board.length) {
            result.push(board.map(item => {
                return item.join('');
            }));
            return;
        }

        for (let col = 0; col < n; col++) {
            // 排除不合法的选择
            if (!isValid(board, row, col)) {
                continue;
            }

            // 做选择
            board[row][col] = 'Q';

            // 进行下一层
            backtrack(board, row + 1);

            // 取消选择
            board[row][col] = '.';
        }
    };

    const board = new Array(n);
    for (let i = 0; i < n; i++) {
        board[i] = (new Array(n)).fill('.');
    }

    backtrack(board, 0);

    return result;
}

const n = 4;
console.log(solveNQueens(n));

组合总和

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

输入:candidates = [2,3,6,7], target = 7 输出:[[2,2,3],[7]] 解释: 2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。 7 也是一个候选, 7 = 7 。 仅有这两种组合。

function combinationSum(candidates, target) {
    const result = [];

    
    const backtrack = (sortNums, track, trackNum, index) => {
        if (trackNum === target) {
            result.push([...track]);
            return;
        } else if (trackNum > target) {
            return;
        }

        for (let i = index; i < sortNums.length; i++) {
            // 做选择
            track.push(sortNums[i]);
            trackNum += sortNums[i];

            // 进入下一层
            backtrack(sortNums, track, trackNum, i);

            // 取消选择
            track.pop();
            trackNum -= sortNums[i];
        }
    };

    const track = [];
    let trackNum = 0;

    candidates.sort((a, b) => a - b);

    backtrack(candidates, track, trackNum, 0);

    return result;
}

const candidates = [2,3,6,7];
const target = 7;
console.log(combinationSum(candidates, target));

子集II

给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

示例 1:

输入:nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]。

function subsetsWithDup(nums) {
    const result = [];

    
    const backtrack = (sortNums, track = [], index = 0) => {
        result.push([...track]);

        for (let i = index; i < sortNums.length; i++) {
            // 对于排序后相同的元素应该跳过
            if (i > index && sortNums[i] === sortNums[i - 1]) {
                continue;
            }
            // 做选择
            track.push(sortNums[i]);
            // 进入下一层
            backtrack(sortNums, track, i + 1);
            // 取消选择
            track.pop();
        }
    };

    const track = [];
    nums.sort((a, b) => a - b);
    backtrack(nums, track, 0);

    return result;
}

const nums = [1, 2, 2];
console.log(subsetsWithDup(nums));

递增子序列

给你一个整数数组 nums ,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。

数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。

示例 1:

输入:nums = [4,6,7,7] 输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]。

function findSubsequences(nums) {
    const result = [];

    const backtrack = (track, start) => {
        // 如果其长度大于等于2,则算一个子序列
        if (track.length >= 2) {
            result.push([...track]);
        }

        const map = new Map();
        for (let i = start; i < nums.length; i++) {
            // 判断是否合法
            // 同一层只能用一次
            if (map.has(nums[i])) {
                continue;
            }
            // 后一层比前一层大
            if (nums[i] < track[track.length - 1]) {
                continue;
            }
            map.set(nums[i], true);
            // 做选择
            track.push(nums[i]);
            // 进入下一层
            backtrack(track, i + 1);
            // 取消选择
            track.pop();
        }
    };

    const track = [];
    backtrack(track, 0);

    return result;
}

const nums = [1, 2, 3 ,1,1,1,1];
console.log(findSubsequences(nums));
来源:前端点线面内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯