回溯算法练习题(2024/6/18)

news2024/11/26 20:25:58

1全排列 II

给定一个可包含重复数字的序列 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

思路:

这道题目和46.全排列 的区别在与给定一个可包含重复数字的序列,要返回所有不重复的全排列

这里又涉及到去重了。

1. 递归函数的返回值以及参数

返回值和参数:

  • void backtracking(vector<int>& nums, vector<bool>& used)
    • 返回值为 void,因为每次调用只需要修改全局变量 path 和 result,不需要从函数中返回特定值。
    • 参数 nums 是输入的原始数组,used 是一个标记数组,用于记录每个位置的元素是否已经被使用过。

2. 回溯函数的终止条件

终止条件:

  • if (path.size() == nums.size())
    • 当 path 的长度等于 nums 的长度时,表示已经形成了一个完整的排列,将 path 加入到 result 中,并返回。

3. 单层搜索的过程

解题思路:

  • 选择路径: 每次递归调用时,在未使用过的元素中选择一个加入到 path 中。
  • 判断条件: 使用 used 数组来判断当前元素是否已经被使用过,以及是否需要去重。
  • 标记和递归:
    • 将当前未使用的元素加入 path 中,并标记为已使用。
    • 递归调用 backtracking,继续向下一层搜索。
    • 在递归返回后,执行回溯操作:撤销选择,恢复标记状态,以便进行下一次选择。

去重部分

去重条件:

  • if (i > 0 && nums[i - 1] == nums[i] && used[i - 1] == true)
    • 当前元素与上一个元素相同,并且上一个元素已经被使用过时,跳过当前元素,避免重复选择相同的元素。

代码:

#include <vector>
#include <algorithm>  // 包含排序函数 sort
using namespace std;

class Solution {
private:
    vector<int> path;  // 存储当前路径的一维向量
    vector<vector<int>> result;  // 存储最终结果的二维向量

    // 回溯函数,参数为原始数组nums和标记数组used
    void backtracking(vector<int>& nums, vector<bool>& used) {
        // 当路径长度等于数组长度时,将当前路径加入结果集合
        if (path.size() == nums.size()) {
            result.push_back(path);
            return;
        }
        // 遍历数组nums
        for (int i = 0; i < nums.size(); i++) {
            // 如果元素已经被使用过,则跳过
            if (i > 0 && nums[i - 1] == nums[i] && used[i - 1] == true) {
                continue;  // 去重部分:跳过重复的元素
            }
            if (used[i] == true) {
                continue;  // 如果元素已经被使用过,则跳过
            }
            // 标记当前元素为已使用
            used[i] = true;
            // 将当前元素加入路径中
            path.push_back(nums[i]);
            // 递归进入下一层决策树
            backtracking(nums, used);
            // 回溯操作,撤销选择
            used[i] = false;
            path.pop_back();
        }
    }

public:
    // 主函数,生成所有排列的入口函数
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        result.clear();  // 清空结果集合
        path.clear();  // 清空当前路径
        sort(nums.begin(), nums.end());  // 排序输入数组,确保重复元素相邻
        vector<bool> used(nums.size(), false);  // 标记数组,记录每个位置的元素是否被使用过
        // 调用回溯函数,从第一个位置开始生成排列
        backtracking(nums, used);
        // 返回最终的结果集合
        return result;
    }
};

2 N 皇后

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

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

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

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

示例 1:

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

示例 2:

输入:n = 1
输出:[["Q"]]

提示:

  • 1 <= n <= 9

思路:

1. 递归函数的返回值以及参数

递归函数 backtracking 的目的是在棋盘上逐行放置皇后,并检查每一步是否符合规则。其参数包括 int n(棋盘大小),int row(当前处理的行数),vector<string>& chessboard(当前棋盘状态)。它没有显式的返回值,而是通过修改全局变量 result 来存储所有合法的解。

2. 回溯函数终止条件

在 backtracking 函数中,终止条件是当 row 等于 n 时,即所有行都处理完毕,此时找到了一个合法的皇后布局,将其加入 result 数组中。

if (row == n) {
    result.push_back(chessboard);  // 找到一种解法,存入结果
    return;
}

3. 单层搜索的过程

在每一层递归中,通过循环尝试将皇后放置在当前行的每一个列上,然后递归处理下一行。在尝试放置之前,通过 isValid 函数检查当前位置是否合法,避免皇后之间的冲突。

for (int col = 0; col < n; col++) {
    if (isValid(row, col, chessboard, n)) {
        chessboard[row][col] = 'Q';         // 放置皇后
        backtracking(n, row + 1, chessboard);  // 递归处理下一行
        chessboard[row][col] = '.';         // 回溯,撤销皇后
    }
}

这种方法通过逐行放置皇后,并通过回溯撤销不符合条件的放置,最终找到所有合法的N皇后布局。

代码:

class Solution {
private:
    vector<vector<string>> result;  // 存放最终的结果

    // 回溯算法核心函数
    // n 是棋盘大小,row 是当前递归到的行数,chessboard 是当前的棋盘状态
    void backtracking(int n, int row, vector<string>& chessboard) {
        // 如果已经递归到最后一行,说明找到了一种解法,将其存入结果集合中
        if (row == n) {
            result.push_back(chessboard);
            return;
        }
        // 遍历当前行的每一列,尝试放置皇后
        for (int col = 0; col < n; col++) {
            // 检查当前位置是否可以放置皇后
            if (isValid(row, col, chessboard, n)) { // 验证合法就可以放
                chessboard[row][col] = 'Q'; // 放置皇后
                backtracking(n, row + 1, chessboard); // 递归处理下一行
                chessboard[row][col] = '.'; // 回溯,撤销皇后
            }
        }
    }

    // 检查当前位置是否可以放置皇后
    bool isValid(int row, int col, vector<string>& chessboard, int n) {
        // 检查列是否有皇后冲突
        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;
    }

public:
    vector<vector<string>> solveNQueens(int n) {
        result.clear(); // 清空结果集
        vector<string> chessboard(n, string(n, '.')); // 初始化棋盘,全部用'.'表示空
        backtracking(n, 0, chessboard); // 从第一行开始递归求解
        return result; // 返回所有解法
    }
};

3. 解数独

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 1:

输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:


提示:

  • board.length == 9
  • board[i].length == 9
  • board[i][j] 是一位数字或者 '.'
  • 题目数据 保证 输入数独仅有一个解

思路:

1. 递归函数的返回值与参数

递归函数 backtracking 的目标是填充数独中的空白格子(用’.'表示),使得每个数字都符合数独的规则(每行、每列、每个3x3子数独内都不能有重复的数字)。具体来说:

  • 参数: 函数接受一个二维字符数组 board,即数独的当前状态。
  • 返回值: 返回一个布尔值,表示是否找到了符合规则的解(找到解返回 true,否则返回 false)。

2. 回溯函数的终止条件

在 backtracking 函数中,回溯的终止条件包括:

  • 当找到一个空白格子(‘.’)时,尝试填入数字’1’到’9’。
  • 对于每个尝试的数字,使用 isValid 函数检查其在当前位置是否符合数独规则。
  • 如果找到一个有效的数字,则将其放入当前格子中,并递归地调用 backtracking 继续填充下一个空白格子。
  • 如果当前尝试的数字不能使得数独的解合法,则撤销当前的选择(回溯),尝试下一个数字。

3. 单层搜索的过程(解题思路)

在单层搜索的过程中:

  • 遍历数独的每个格子,对于每个空白格子尝试填入数字’1’到’9’。
  • 使用 isValid 函数来检查填入的数字是否在当前行、当前列和当前3x3子数独内都没有重复出现。
  • 如果找到一个合法的填入方式,则继续递归填充下一个空白格子;如果找不到合法的填入方式,则进行回溯。
  • 当所有空白格子都被填满且符合数独规则时,数独问题得到解决。

判断一个数独棋盘是否合法,主要依据以下三个维度进行检查:

  1. 同行是否重复:

    • 对于每一行,检查其中的每个数字是否唯一。遍历每一行,使用一个集合或者数组来记录已经出现过的数字,若再次出现相同数字则表示该行不合法。
  2. 同列是否重复:

    • 对于每一列,检查其中的每个数字是否唯一。遍历每一列,同样使用集合或数组记录已经出现过的数字,如果重复出现则该列不合法。
  3. 9宫格是否重复:

    • 数独棋盘被分为9个3x3的子宫格。对于每个子宫格,检查其中的数字是否唯一。通过计算当前位置所在的子宫格的起始行和起始列(使用 (row / 3) * 3 和 (col / 3) * 3 计算),遍历该子宫格内的所有数字,同样使用集合或数组来记录已经出现过的数字,若有重复则该子宫格不合法。

代码:

class Solution {
private:
    // 回溯函数,尝试解决数独问题
    bool backtracking(vector<vector<char>>& board) {
        // 遍历整个数独表格
        for(int i = 0; i < board.size(); i++) {
            for(int j = 0; j < board[0].size(); j++) {
                // 如果当前位置是空白格
                if(board[i][j] == '.') {
                    // 尝试填充数字1到9
                    for(char k = '1'; k <= '9'; k++) {
                        // 如果当前数字k在位置(i, j)合法
                        if(isValid(i, j, k, board)) {
                            board[i][j] = k;  // 放置数字k
                            // 递归调用backtracking,尝试填充下一个空白格
                            if(backtracking(board)) return true;
                            board[i][j] = '.';  // 回溯,撤销当前位置的填充
                        }
                    }
                    return false;  // 1-9都尝试过,无解
                }
            }
        }
        return true;  // 数独已解
    }
    
    // 检查在位置(row, col)填充字符val是否合法
    bool isValid(int row, int col, char val, vector<vector<char>>& board) {
        // 检查同一行是否有重复
        for(int i = 0; i < 9; i++) {
            if(board[row][i] == val) {
                return false;
            }
        }
        
        // 检查同一列是否有重复
        for(int j = 0; j < 9; j++) {
            if(board[j][col] == val) {
                return false;
            }
        }
        
        // 检查同一个3x3子数独内是否有重复
        int startRow = (row / 3) * 3;
        int startCol = (col / 3) * 3;
        for(int i = startRow; i < startRow + 3; i++) {
            for(int j = startCol; j < startCol + 3; j++) {
                if(board[i][j] == val) {
                    return false;
                }
            }
        }
        
        return true;  // 合法
    }
    
public:
    // 解决数独问题的入口函数
    void solveSudoku(vector<vector<char>>& board) {
        backtracking(board);  // 调用回溯函数解决数独
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1835025.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

pg分区表和mysql分区表的创建及删除添加操作

一、分区的类型 1、pg分区的类型 范围划分 列表划分 哈希分区 2、mysql分区的类型 范围分区 列表分区 hash分区 列分区 密匙分区 子分区 二、pg范围分区表的创建删除添加操作 1、pg分区表的创建 2、pg的分区表删除 3、pg分区表的添加 创建新的子分区 添加新创建的子分区 …

1999-2020年各地级市农村居民人均纯收入数据

1999-2020年各地级市农村居民人均纯收入数据 1、时间&#xff1a;1999-2020年 2、指标&#xff1a;年份、城市、农村居民人均纯收入 3、来源&#xff1a;区域年鉴、各省市年鉴 4、范围&#xff1a;地级市&#xff0c;具体每年城市数量参看下文图片&#xff0c;具体城市名单…

Jenkins macos 下 failed to create dmg 操作不被允许hdiutil: create failed - 操作不被允许?

解决方案&#xff1a; 打开设置&#xff0c;选择“隐私与安全”&#xff0c;选择“完全磁盘访问权限”&#xff0c;点击“”&#xff0c;选择jenkins的路径并添加。 同理&#xff0c;添加java的访问权限。

Vscode中的行尾序列CRLF/LF不兼容问题

最近开发的的时候&#xff0c;打开项目文件经常会出现爆红错误提示信息&#xff0c;显示如下图&#xff1a; 这东西太烦人了&#xff0c;毕竟谁都不希望在遍地都是爆红的代码里写东西&#xff0c;就像能解决这个问题&#xff0c;根据提示可以知道这是vscode中使用的prettier插件…

vue3-父子通信

一个简单的vue3子组件调用父组件方法的demo <template> <div> <h2>Parent Component父组件</h2> <ChildComponent notify-parent"handleParentMethod" /> </div> </template> <script> import { ref } fr…

图像处理与视觉感知复习--形态学图像处理

文章目录 计算图像膨胀和腐蚀计算开操作和闭操作击中或击不中变化 计算图像膨胀和腐蚀 定义&#xff1a; A c A^c Ac 表示集合A的补集几何的反射 有集合A中所有元素相对于原点的反射元素组成的集合称为集合A的反射&#xff0c;几位 A ^ \hat{A} A^ A ^ { w ∣ w − a , a …

泛微E9开发 查询页面添加按钮,完成特定功能

查询页面添加按钮&#xff0c;完成特定功能 1、关联知识&#xff08;查询页面实现新增按钮&#xff09;2、功能实现2.1. 点击按钮&#xff0c;输出选中的checkbox的值2.2. 点击按钮&#xff0c;打开一个自定义对话框 3、实现方法 1、关联知识&#xff08;查询页面实现新增按钮&…

SGPT论文阅读笔记

这是篇想要用GPT来提取sentence embedding的工作&#xff0c;提出了两个框架&#xff0c;一个是SGPT-BE&#xff0c;一个是SGPT-CE&#xff0c;分别代表了Bi-Encoder setting和Cross-Encoder setting。CE的意思是在做阅读理解任务时&#xff0c;document和query是一起送进去&am…

CP AUTOSAR标准之LargeDataCOM(AUTOSAR_CP_SWS_LargeDataCOM)

1 简介和功能概述 该规范描述了AUTOSAR基础软件模块LdCom的功能、API和配置。   在AUTOSAR分层架构中,AUTOSAR LdCom模块位于RTE/SwCluC_LdComProxy和PDU路由之间,参见[1,EXP LayeredSoftwareArchitecture]。   AUTOSAR LdCom模块提供了一种替代的交互层机制。通过专注于…

kafka在windows上的启动

启动zookeeper 解压kafka安装包到对应目录下&#xff0c;找到对应config目录下的zookeeper.properties文件 新建一个data文件夹&#xff0c;随便放哪 打开该文件&#xff0c;找到 dataDir/tmp/zookeeper 属性 将原来的属性值&#xff0c;修改为新建data文件夹地址&#xff0c;…

echarts 折线图 实现某两个点之间不要连线

通过插入null或NaN的数据点来实现"断开"的效果 const data [[a, 1], [b, 2], [c, 3], [d, 4], [e, 5]] data.splice(2, 0, NaN) option {xAxis: {type: "category",data: [a, b, c, d, e]},yAxis: {},series: [{data,type: "line"}] }

RadioML2016.10.a数据加载和介绍

RadioML2016.10.a For the RadioML 2016.10.a dataset, perform data loading and visualization. 数据集 RadioML 2016.10.a 官方链接https://www.deepsig.ai/datasets/https://www.deepsig.ai/datasets/ 具体的调制技术以及SNR [(QPSK, 2), (PAM4, 8), (AM-DSB, -4), (GFS…

基于Spring Boot+VUE毕业生信息招聘平台

系统详细设计 1管理员功能模块 管理员登录&#xff0c;管理员通过输入用户名、密码、角色等信息进行系统登录&#xff0c;如图1所示。 图1管理员登录界面图 管理员登录进入毕业生信息招聘平台可以查看首页、个人中心、企业管理、空中宣讲会管理、招聘岗位管理、毕业生管理、个…

Android下QVideoFrame转QImage不成功记录

1.由于QVideoFrame::image() const : unsupported pixel format Format_ABGR32 &#xff0c;在转换时需要做个特殊处理如下,增加了android手机下的特殊格式处理: if(frame.pixelFormat() QVideoFrame::Format_ABGR32) 此部分代码 QImage imageFromVideoFrame(QVideoFrame &…

复分析——第3章——亚纯函数和对数(E.M. Stein R. Shakarchi)

第3章 亚纯函数和对数 (Meromorphic Functions and the Logarithm) One knows that the differential calculus, which has contributed so much to the progress of analysis, is founded on the consideration of differential coefficients, that is derivatives of f…

013.随机指纹chromium编译-如何通过传参来固定指纹

随机指纹chromium编译-如何通过传参来固定指纹 由于要所有的类型的指纹都实现固定&#xff0c;工作量太大&#xff0c;这里我只用plugins指纹作为示例。 一、为什么要固定指纹 目标&#xff1a;启动浏览器时&#xff0c;加上参数--fingerprints"xxxxxxx", 参数变化时…

【机器学习】对大规模的文本数据进行多标签的分类处理

1. 引言 1.1. NLP研究的背景 随着人工智能技术的飞速发展&#xff0c;智能助手、聊天机器人和虚拟客服的需求正呈现出爆炸性增长。这些技术不仅为人们提供了极大的生活便利&#xff0c;如日程管理、信息查询和情感陪伴&#xff0c;还在工作场景中显著提高了效率。聊天机器人凭…

什么是云恶意软件攻击,如何进行有效的防护

一切都在向云转移。云端数据越多&#xff0c;恶意攻击者攻击云平台的兴趣就越大。 攻击者使用恶意软件窃取数据并破坏服务。虽然恶意软件在云端可能不像在个人电脑上那么普遍&#xff0c;但大行其道的云恶意软件令人担忧。此外&#xff0c;组织不像您预料的那样意识到这点。 …

EasyRecovery下载_EasyRecovery官方下载_2024最新版软件安装包附加详细安装步骤

EasyRecovery中文版是一款操作安全、恢复性比较高的数据恢复工具&#xff0c;小伙伴们可以使用EasyRecovery恢复各种各样被删除的文件、视频、图片等。EasyRecovery还可以支持恢复从硬盘、光盘、U盘、数码相机、手机等各种设备中恢复被删除或丢失的文件&#xff0c;只是使用Eas…

VBA学习(9):按指定名单一键删除工作表

今天继续给大家聊VBA编程中工作表对象的常用操作&#xff0c;主要内容是如何批量删除工作表&#xff1b;也就是删除单个工作表、删除全部工作表和删除指定名单内的工作表。 1.删除单个工作表 删除工作表需要使用到工作表对象的delete方法&#xff0c;语法格式如下&#xff1a…