当前位置: 首页 > news >正文

【蓝桥杯速成】| 12.回溯排列N皇后

题目一:全排列

问题描述

46. 全排列 - 力扣(LeetCode)

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

示例 1:

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

示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

示例 3:

输入:nums = [1]
输出:[[1]]

提示:

  • 1 <= nums.length <= 6
  • -10 <= nums[i] <= 10
  • nums 中的所有整数 互不相同

解题步骤

这个题目属于排列问题,那么和我们之前做过的组合问题的区别就在于次序有别

[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]在组合问题中是要被去重的

而在排列问题中则正是我们所要的结果

所以这一题的关键在于抛弃之前的组合去重逻辑,使用startindex缩减范围

改为只要求使用过的数不重复即可

只去重纵向的重复我们可以用used数组来实现

只要给每一个数字打上用过or没用过的标记

我们在循环中就能避免[1,1,1],[1,1,2]这样的答案

回溯三部曲

1.确定参数及返回值

依旧利用两个全局变量path,result分别存放过程结果和最终结果集

所以该函数无需返回值

根据上面的分析参数就应该为nums数组及used数组

void backtracking(vector<int>& nums,vector<bool>& used)

2.确定终止条件

排列问题的结果还是在叶子结点取到,并且这个答案的长度和原数组长度一致

总不能你让小朋友去排队结果弄丢一个吧

所以当path与nums数组长度一致时,把path加入result

if(path.size()==nums.size()){

        result.push_back(path);

 3.单层遍历逻辑

由于我们这里不再使用startindex,每次遍历就应该从0开始,

然后借助used数组排除用过的元素

再把合适的元素加入path,并修改它的使用标志即used数组对应值

递归调用backtracking函数,再对path,used做回溯

for(int i=0;i<nums.size();i++){

        if(used[i]==true){

                continue;

        }

        path.push_back(nums[i]);

        used[i]==true;

        backtracking(nums,used);

        used[i]=false;

        path.pop_back();

}

最后别忘记在主函数中定义used数组并全部初始化为false

vector<bool> used(nums.size(),false); 

整合后的完整代码如下! 

code

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    void backtracking(vector<int>& nums,vector<bool>& used){
        if(path.size()==nums.size()){
            result.push_back(path);
            return;
        }
        for(int i=0;i<nums.size();i++){
            if(used[i]==true){
                continue;
            }
            path.push_back(nums[i]);
            used[i]=true;
            backtracking(nums,used);
            used[i]=false;
            path.pop_back();
        }
    }
    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        backtracking(nums,used);
        return result;
    }
};


题目二:全排列②

问题描述

47. 全排列 II - 力扣(LeetCode)

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

示例 1:

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

示例 2:

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

提示:

  • 1 <= nums.length <= 8
  • -10 <= nums[i] <= 10

解题步骤

这一题是上一题的升级版,区别是nums中可能有重复元素

那么我们需要多做一个树层去重

这个去重逻辑之前也用过

就是要先对nums数组做个排序,方便确认选择数字是否之前就存在

同时在树层间确认used[i-1]==false

即前一个元素是否在这一层被使用过,false是因为用完回溯了

if(i>0 && nums[i]==nums[i-1] && used[i-1]==false){

        continue;

}

树层重复解决完也不能忘记纵向间也要保持去重逻辑

一个数字不能取两遍,还是需要used数组实现

if(used[i]!=true){

        path.push_back(nums[i]);

        used[i]=true;

        backtracking(nums,used);

        used[i]=false;

        path.pop_back();

 其它保持不变,完整代码在下方!

code 

class Solution {
public:
    vector<int> path;
    vector<vector<int>> result;
    void backtracking(vector<int>& nums,vector<bool>& used){
        if(path.size()==nums.size()){
            result.push_back(path);
             return;
        }
        for(int i=0;i<nums.size();i++){
            if(i>0 && nums[i]==nums[i-1] && used[i-1]==false){
                continue;
            }
            if(used[i]!=true){
                path.push_back(nums[i]);
                used[i]=true;
                backtracking(nums,used);
                used[i]=false;
                path.pop_back();
            }
        }
    }
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        vector<bool> used(nums.size(),false);
        sort(nums.begin(),nums.end());
        backtracking(nums,used);
        return result;
    }
};


 题目三:N皇后

问题描述

51. N 皇后 - 力扣(LeetCode)

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

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

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

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

解题步骤

这一题与之前所有回溯算法的区别在于它是一个二维的

但实际上我们的解题思路还是类似

结果改为用vector<vector<string>> result存储(实际上相当于3维,string也算一个维度)

每一个答案是这个棋盘的皇后摆放情况,那么这个就是我们的过程量

原来用path存放,此处为了方便解题我们可以设置为vector<string> chessboard

这里需要注意不能初始化为

vector<string> chessboard(n,".");

这是创建一个包含n个元素的vector,每个元素是一个string

每个string被初始化为"."(即长度为1的字符串,内容是'.')。

因此,chessboard是一个n行的棋盘,但每行只有1个字符'.'

例如,n=4时:

chessboard = {
    ".",
    ".",
    ".",
    "."
};

这与我们想要的

chessboard = {
    "....",
    "....",
    "....",
    "...."
};

完全不符,所以需要定义棋盘并将其初始化为:

 std::vector<std::string> chessboard(n,std::string(n,'.'));

 当然也可以定义为vector<vector<char>>,该版本代码附在最后

 下面就是使用回溯三部曲,写出主要代码

1.明确参数及返回值

result数组被定义为全部变量,故依旧无需返回值

chessboard作为我们要操作的过程量肯定是参数的一员

同时n作为棋盘大小是我们的边界

那么还需要一个索引指向处理的层数,故使用 int row

这样既可以通过层数了解,定位处理的位置

也可以作为终止条件的参考

void backtracking(vector<vector<string>>& chessboard,int n,int row)

2.确定终止条件

我们需要填好整个棋盘才算做一个答案,故结果在叶子结点处取到

也就是row指向最后一层n

if(row==n){

        result.push_back(chessboard);

        return;

3.单层遍历操作

在这个部分我们要遍历当前行所有格子,把合法的皇后放进棋盘,

并递归调用函数填满整个棋盘

每次调用后需要回溯

 for(int col=0;col<n;col++){

        if(isValid....){//检查函数等会再写!

                chessboard[row][col]='Q';

                backtracking(chessboard,n,row+1);

                chessboard[row][col]='.';

        }

}

 4.判断合法性函数

按照规则,我们要确保放入皇后的位置正确,

那么需要用到的参数肯定有当前位置的row和col,棋盘内容chessboard,棋盘大小n

返回值应该是bool型的

那么函数返回值和参数列表应该为

bool isValid(vector<string>& chessboard,int n,int row,int col)

在函数中我们需要进行三个检查

1)列间检查

确保每一列只有一个皇后,

那么需要遍历当前行之前的所有行,去查看[col]这个位置是否有Q

for(int i=0;i<row;i++){

        if(chessboard[i][col]=='Q'){

                return false;

        }

}

 2)45度角检查

确保左上斜线处不存在Q

映照棋盘关系,左上处坐标就是当前格横坐标减一(row-1),纵坐标减一(col-1)

需要一直往左上查找所以不断减减,直到边界

for(int i=row-1, j=col-1; i>=0&&j>=0; i--, j--){

        if(chessboard[i][j]=='Q')

                return false;

}

 3)135度角检查

原理和上面的类似,不过135度指右上斜线

for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {//右边上面一个!

            if (chessboard[i][j] == 'Q') {

                return false;

            }

        }

都没有违反规则则返回true

然后需要在backtracking函数中正确传参调用即可

完整代码如下!

code

vector<string>版

class Solution {
public:
    vector<vector<string>> result;
    bool isValid( vector<string>& chessboard, int n,int row, int col) {
        // 检查列
        for (int i = 0; i < row; i++) { //该列在当前行之前是否出现过Q
            if (chessboard[i][col] == 'Q') {
                return false;
            }
        }
        // 检查 45度角是否有皇后
        for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) {//就是左边上面一个!
            if (chessboard[i][j] == 'Q') {
                return false;
            }
        }
        // 检查 135度角是否有皇后
        for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {//右边上面一个!
            if (chessboard[i][j] == 'Q') {
                return false;
            }
        }
        return true;
    }
    void backtracking(vector<string>& chessboard,int n,int row){
        if(row==n){
            result.push_back(chessboard);
            return;
        }
        for(int col=0;col<n;col++){
            if(isValid(chessboard,n,row,col)){
                chessboard[row][col]='Q';
                backtracking(chessboard,n,row+1);
                chessboard[row][col]='.';
            }
        }
    }
    vector<vector<string>> solveNQueens(int n) {
        //vector<string> chessboard(n,".");
        std::vector<std::string> chessboard(n, std::string(n, '.'));
        backtracking(chessboard,n,0);
        return result;
    }
};

 vector<vector<char>> chessboard版

class Solution {
public:
    vector<vector<string>> result;
    bool isValid(vector<vector<char>>& chessboard, int n, int row, int col) {
        for (int i = 0; i < row; i++) {
            if (chessboard[i][col] == 'Q') return false;
        }
        for (int i = row - 1, j = col - 1; i >=0 && j >= 0; i--, j--) {
            if (chessboard[i][j] == 'Q') return false;
        }
        for(int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
            if (chessboard[i][j] == 'Q') return false;
        }
        return true;
    }
    void backtracking(vector<vector<char>>& chessboard, int n, int row) {
        if (row == n) {
            vector<string> board;
            for (const auto& row : chessboard) {
                board.push_back(string(row.begin(), row.end()));
            }
            result.push_back(board);
            return;
        }
        for (int col = 0; col < n; col++) {
            if (isValid(chessboard, n, row, col)) {
                chessboard[row][col] = 'Q';
                backtracking(chessboard, n, row + 1);
                chessboard[row][col] = '.';
            }
        }
    }
    vector<vector<string>> solveNQueens(int n) {
        vector<vector<char>> chessboard(n, vector<char>(n, '.'));
        backtracking(chessboard, n, 0);
        return result;
    }
};

相关文章:

  • leetcode day30 134+135+860
  • 深挖增长内核:好产品驱动增长的全方位解析
  • 深度解析:TOML、XML、YAML及其他配置/数据格式对比
  • 用YoloV8训练一个图片检测的模型,我接下来要做哪些事
  • 《深入Linux内核架构》读书笔记--第三章 内存管理
  • 【含文档+PPT+源码】基于Python的全国景区数据分析以及可视化实现
  • 【例3.5】位数问题(信息学奥赛一本通-1313)
  • BertTokenizer.from_pretrained的讲解和使用
  • golang编写UT:applyFunc和applyMethod区别
  • Oracle数据库服务器地址变更与监听配置修改完整指南
  • websocket结合promise的通信协议
  • 短期趋势动量策略思路
  • Thales靶机攻略
  • 鸿蒙移动应用开发--UI组件布局
  • 批量优化与压缩 PPT,减少 PPT 文件的大小
  • 【CSS3】01-初始CSS + 引入 + 选择器 + div盒子 + 字体修饰
  • Sar: 1靶场渗透
  • MoManipVLA:将视觉-语言-动作模型迁移到通用移动操作
  • 自然语言处理(13:RNN的实现)
  • 接口测试是什么
  • 韶关一企业将消防安装工程肢解发包,广东住建厅:罚款逾五万
  • 舱位已排到月底,跨境电商忙补货!美线订单大增面临爆舱,6月运价或翻倍
  • 韧性十足的中国外贸企业:“不倒翁”被摁下去,还会再弹起来
  • 透视社会组织创新实践中的花开岭现象:与乡村发展的融合共进
  • 商务部新闻发言人就出口管制管控名单答记者问
  • 七旬男子驾“老头乐”酒驾被查,曾有两次酒驾两次肇事记录