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

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和标记数组usedvoid backtracking(vector<int>& nums, vector<bool>& used) {// 当路径长度等于数组长度时,将当前路径加入结果集合if (path.size() == nums.size()) {result.push_back(path);return;}// 遍历数组numsfor (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到9for(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.mzph.cn/diannao/32436.shtml

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

相关文章

rollup学习笔记

一直使用的webpack,最近突然想了解下rollup,就花点时间学习下. 一,什么是rollup? rollup 是一个 JavaScript 模块打包器&#xff0c;可以将小块代码编译成大块复杂的代码,比如我们的es6模块化代码,它就可以进行tree shaking,将无用代码进行清除,打包出精简可运行的代码包. 业…

【Java基础】

Java基础常见面试题总结(上) 基础概念与常识 Java 语言有哪些特点? 简单易学(语法简单,上手容易);面向对象(封装,继承,多态);平台无关性( Java 虚拟机实现平台无关性);支持多线程( C++ 语言没有内置的多线程机制,因此必须调用操作系统的多线程功能来进行多线程…

vue elementui table给表格中满足条件的每一条记录添加计时器

需求&#xff1a; 在前端给表格中给满足条件的每一条记录增加一个计时器&#xff0c;用于计算工作时长。 1.数据库中存储的有每条记录的作业开始时间&#xff0c;将当前时间和作业开始时间计算一个差值&#xff0c;作为作业时长的初始值&#xff1b; 2.把满足条件的每条记录绑…

Java中对象的比较

1. 对象的比较 在Java中&#xff0c;基本类型的对象可以直接比较大小&#xff0c;而自定义类型却不能 class Card {public int rank; // 数值public String suit; // 花色public Card(int rank, String suit) {this.rank rank;this.suit suit;}}public class TestPriori…

2008-2022年 全国31省-环境污染综合指数

环境污染综合指数是一个衡量环境污染程度或环境质量等级的抽象概括数值。它能够综合反映不同环境要素的污染情况&#xff0c;例如水污染指数和大气污染指数等。环境空气质量综合指数&#xff08;Air Quality Index, AQI&#xff09;是专门用来描述城市环境空气质量状况的一个指…

C++进阶之AVL树

个人主页&#xff1a;点我进入主页 专栏分类&#xff1a;C语言初阶 C语言进阶 数据结构初阶 Linux C初阶 C进阶​ ​​​​算法 欢迎大家点赞&#xff0c;评论&#xff0c;收藏。 一起努力&#xff0c;一起奔赴大厂 目录 一.前言 二.插入 三.旋转 3.1右旋 …

如何利用数据仓库进行业务分析:一名大数据工程师的视角

在大数据时代&#xff0c;数据的有效利用对企业的成功至关重要。 本文将基于上面的流程图&#xff0c;详细介绍如何利用数据仓库进行业务分析&#xff0c;并提供实际的例子和代码演示&#xff0c;以帮助读者更好地理解和应用相关技术。 数据仓库的基本流程 上图展示了一个典…

莱辅络Rebro BIM机电专业软件

莱辅洛&#xff08;Rebro&#xff09;是一款专业机电 BIM 软件。它具备专业人士所期待的各种专业功能&#xff0c;应用于建筑机电工程的三维设计&#xff0c;并且适用于建筑、结构、给排水、暖通、电气五大专业。 该软件具有以下特点&#xff1a; • 3D 模型&#xff1a;可以…

1.接口测试-postman学习

目录 1.接口相关概念2.接口测试流程3.postman基本使用-创建请求&#xff08;1&#xff09;环境&#xff08;2&#xff09;新建项目集合Collections&#xff08;3&#xff09;新建collection&#xff08;4&#xff09;新建模块&#xff08;5&#xff09;构建请求请求URLheader设…

常见的创建型设计模式( 一 )

设计模式( 一 ) 常见的创建型设计模式 1.单例模式 : 确保一个类只有一个实例 , 为整个程序提供一个全局的访问接口。getInstance 实现方式 饿汉式&#xff0c;在调用getInstance 创建实例的时候 &#xff0c;实例已经存在了 &#xff0c;不需要我们再次去 new创建。 优点&a…

修复 Android 手机卡在启动屏幕上的 7 种方法

Android 手机卡在启动屏幕上的情况并不常见。通常&#xff0c;问题出现在应用新更新或安装未知来源的应用程序后。幸运的是&#xff0c;您可以让您的 Android 手机跳过启动屏幕&#xff0c;而无需前往最近的服务中心。 当您的 Android 手机在启动屏幕上陷入无限循环时&#xf…

标准立项 | 《温室气体排放核算与报告要求 废油资源化企业》

《温室气体排放核算与报告要求 废油资源化企业》适用于废油资源化行业企业温室气体排放量的核算和报告。从事废油资源化生产的企业&#xff0c;均可参考该标准核算企业的温室气体排放量&#xff0c;并编制企业温室气体排放报告。 参编咨询&#xff1a;中华环保联合会水环境治理…

ECharts 蓝色系-荧光图标折线图01案例

ECharts 蓝色系-荧光图标折线图01案例 图表意义 本折线图案例展示了一周内不同路线的使用情况或数据统计。通过折线的上升和下降&#xff0c;可以直观地观察到每条路线的流量或数据变化趋势&#xff0c;从而进行分析和决策。 效果预览 效果图展示不同路线的数据统计和个性化…

<Rust><iced>在iced中显示gif动态图片的一种方法

前言 本文是在rust的GUI库iced中在窗口显示动态图片GIF格式图片的一种方法。 环境配置 系统&#xff1a;window 平台&#xff1a;visual studio code 语言&#xff1a;rust 库&#xff1a;iced、image 概述 在iced中&#xff0c;提供了image部件&#xff0c;从理论上说&…

链表中环的入口节点

链表中环的入口节点 描述 链表中环的入口节点 给一个长度为n链表&#xff0c;若其中包含环&#xff0c;请找出该链表的环的入口结点&#xff0c;否则&#xff0c;返回null。 数据范围&#xff1a; n≤10000&#xff0c; 1<结点值<10000 要求&#xff1a;空间复杂度 O(1)…

前后端交互的弯弯绕绕

前后端交互&#xff1a; &#x1f197;&#xff0c;收拾一下心情让我们来聊一聊AJax吧&#xff0c;随着前端的飞速发展&#xff0c;前后的交互也发生了天翻地覆的变化&#xff1a; 前后端交互的方式有很多&#xff1a; AJAX、表单提交、WebSocket、RESTful API、... 这对新入…

基于Java学生选课管理系统设计和实现(源码+LW+调试文档+讲解等)

&#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN作者、博客专家、全栈领域优质创作者&#xff0c;博客之星、平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌&#x1f497; &#x1f31f;文末获取源码数据库&#x1f31f; 感兴趣的可以先收藏起来&#xff0c;…

NSSCTF-Web题目14

目录 [CISCN 2019华东南]Web11和[NISACTF 2022]midlevel 1、题目 2、知识点 3、思路 [HDCTF 2023]SearchMaster 1、题目 2、知识点 3、思路 [CISCN 2019华东南]Web11和[NISACTF 2022]midlevel 这两道题目一样 1、题目 2、知识点 SSTI&#xff08;服务端模板注入漏洞&…

有哪些骨传导耳机是比较推荐入手的?精选五款热门骨传导耳机推荐!

耳机基本是每人人手一台&#xff0c;不管是在地铁上还是在公交上&#xff0c;都可以看到很多人戴着耳机度过空余的时光&#xff0c;甚至现在人们在耳机的选择方面更加偏向于骨传导耳机&#xff0c;开放耳道的奇特设计在户外佩戴的时候可以更好的感知到周围的环境音&#xff0c;…

Github 2024-06-22 开源项目日报 Top10

根据Github Trendings的统计,今日(2024-06-22统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量TypeScript项目3JavaScript项目2Python项目2HTML项目1Rust项目1Dart项目1Dockerfile项目1Shell项目1C++项目1Swift项目1RustDesk: 用Rust编写的…