回溯五题【Leetcode17数独/37组合问题/51N皇后/212字典树/980状态压缩】

文章目录

  • 关于回溯
  • 37. 解数独(37.sudoku-solver)
  • 17. 电话号码的数字组合(17.letter-combinations-of-a-phone-number)
  • 51. N皇后(51.n-queens)
  • 212. 单词搜索 II(212.word-search-ii)
      • 简单的回溯+剪枝(TLE)
      • 新方法:字典树Trie+回溯
  • 980. 不同路径III
    • [写法1]用vector存三维数组(内存超限)
    • [写法2]map存三元组(通过)
  • 五题小结

关于回溯

回溯跟枚举差不多。要注意“回溯”,别忘记“回”之前把之前的改动都复原。

37. 解数独(37.sudoku-solver)

在这里插入图片描述

leetcode37是解数独问题。本题保证有且仅有唯一解。

思路:先把空格子的位置存下来,然后对每一个空位置挨个枚举1-9。枚举之前,先建立一个一维数组,把要排除的数先排除,效率会高些。

class Solution {// 空格的信息int x[100], y[100], cnt = 0;bool dfs(int i, vector<vector<char>>& board) {if (i == cnt) return true;bool s[60] = {false};// 检查行、列for (int j = 0; j < 9; j++) s[board[x[i]][j]] = s[board[j][y[i]]] = true;// 检查九宫格for (int j = x[i] / 3 * 3; j < x[i] / 3 * 3 + 3; j++)for (int k = y[i] / 3 * 3; k < y[i] / 3 * 3 + 3; k++)s[board[j][k]] = true;// 枚举尝试1-9for (char c = '1'; c <= '9'; c++) {if (s[c] == false) {board[x[i]][y[i]] = c;if (dfs(i + 1, board))return true;}}board[x[i]][y[i]] = '.';return false;}public:void solveSudoku(vector<vector<char>>& board) {// 检索空格子for (int i = 0; i < 9; i++) {for (int j = 0; j < 9; j++) {if (board[i][j] == '.') {x[cnt] = i;y[cnt++] = j;}}}dfs(0, board);return;}
};

17. 电话号码的数字组合(17.letter-combinations-of-a-phone-number)

在这里插入图片描述
leetcode17是纯纯的枚举问题。

逐位处理那串数字,把记录好的当作参数string alreadyHave。由于这个形参是每递归一下就新开辟一个栈帧,所以这样写不涉及到“改动复原”的事。如果占用空间太大了,就需要把这个参数改为引用,那么就需要“复原”了。

class Solution {vector<string> ans;string d;void dfs(int index, string alreadyHave) // index是待处理下标{if (index == d.length()) {if (alreadyHave != "")ans.push_back(alreadyHave);return;}int num = d[index] - '0', start, end;if (num >= 2 && num <= 7) {start = (num - 2) * 3 + 'a';end = start + 2;}if (num == 7)end++;if (num == 8) {start = 't';end = 'v';}if (num == 9) {start = 'w';end = 'z';}for (int i = start; i <= end; i++) {dfs(index + 1, alreadyHave + (char)(i));}return;}public:vector<string> letterCombinations(string digits) {d = digits;dfs(0, "");return ans;}
};

51. N皇后(51.n-queens)

在这里插入图片描述

信息记录的不是棋盘格,而是皇后们的列索引

class Solution {int q[10];int n;vector<vector<string>> ans;void r(int k){if (k == n){// 0~n-1个皇后都摆好了,存答案vector<string> thisans;for (int i=0; i<n; i++){//q[i]个点,1个Q,n-1-q[i]个点string curr = "";int a = q[i];int b = n-1-q[i];while (a--)curr += '.';curr+='Q';while (b--)curr += '.';thisans.push_back(curr);}ans.push_back(thisans);return;}//这个皇后将会在k行、i列for (int i=0; i<n; i++){//依次和之前的每个皇后检查冲突int j;for (j=0; j<k; j++){if (i == q[j] || abs(j-k) == abs(i-q[j])) break;}if (j != k) continue;q[k] = i;r(k+1);}return;}
public:vector<vector<string>> solveNQueens(int n) {this->n = n;r(0);return ans;}
};

212. 单词搜索 II(212.word-search-ii)

简单的回溯+剪枝(TLE)

class Solution {vector<string> ans;int dx[4] = {0,0,1,-1};int dy[4] = {1,-1,0,0};int row, col;bool dfs(vector<vector<char>>& board, vector<string>& words, int wordIndex, int charIndex, int x, int y){if (charIndex == words[wordIndex].length()){ans.push_back(words[wordIndex]);words.erase(words.begin()+ wordIndex);return true;}if (x < 0 || x >= row || y < 0 || y >= col) return false;if (board[x][y] != words[wordIndex][charIndex]) return false;char tmp = board[x][y]; //必须备份board[x][y] = '#';for (int i=0; i<4; i++){int nx = x+dx[i], ny = y+dy[i]; //上下左右if (dfs(board, words, wordIndex, charIndex+1, nx, ny)){board[x][y] = tmp;return true;}}board[x][y] = tmp;return false;}
public:vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {row = board.size();col = board[0].size();for (int i=0; i<row; i++)for (int j=0; j<col; j++)for (int k=0; k<words.size(); )if (!dfs(board, words, k, 0, i, j))k++;return ans;}
};

通过了42/65个。
在这里插入图片描述

新方法:字典树Trie+回溯

struct TrieNode {string word = ""; //只有叶节点的word是非空的map<char, TrieNode *> child; //孩子节点的指针
};void insertTrie(TrieNode * root, string word) // 在字典树中插入新单词
{TrieNode * p = root;for (auto c : word) {if (p->child[c] == 0) //当前节点的孩子中没有word的某个字符(即变量c)p->child[c] = new TrieNode(); //新建一片叶子p = p->child[c]; //沿着树的节点从上往下捋}p->word = word; //因为单词表之内没有重复,所以最后一定停在新的叶子上
}class Solution {set<string> tmp_ans; //为了去重vector<string> ans;int dx[4] = {0, 0, 1, -1};int dy[4] = {1, -1, 0, 0};int row, col;bool dfs(vector<vector<char>>& board, int x, int y, TrieNode * root) {if (root->word != "") //走到了叶子{tmp_ans.insert(root->word); //找到,插入到返回值中//return true;}if (x < 0 || x >= row || y < 0 || y >= col)return false;if (root->child[board[x][y]] == 0)return false;// 上下左右继续找char tmp = board[x][y];board[x][y] = '#';for (int i = 0; i < 4; i++) {int nx = x + dx[i], ny = y + dy[i];if (dfs(board, nx, ny, root->child[tmp])) {board[x][y] = tmp;return true;}}board[x][y] = tmp;return false;}public:vector<string> findWords(vector<vector<char>>& board, vector<string>& words) {TrieNode * root = new TrieNode(); //空字典树for (auto & word : words)insertTrie(root, word);row = board.size();col = board[0].size();for (int i = 0; i < row; i++)for (int j = 0; j < col; j++)dfs(board, i, j, root);for (auto & word : tmp_ans)ans.push_back(word);return ans;}
};

虽然还是超时,但可以把测试用例通过了,也就是超时的没那么离谱。可以继续优化。
在这里插入图片描述
最终的官方题解和我这个代码是高度相似的,所以针对卡常再修补修补即可通过。

980. 不同路径III

在二维网格 grid 上,有 4 种类型的方格:

1 表示起始方格。且只有一个起始方格。
2 表示结束方格,且只有一个结束方格。
0 表示我们可以走过的空方格。
-1 表示我们无法跨越的障碍。
返回在四个方向(上、下、左、右)上行走时,从起始方格到结束方格的不同路径的数目。
每一个无障碍方格都要通过一次,但是一条路径中不能重复通过同一个方格。

状态压缩、记忆化dfs
用vector存三维数组,内存超限的代码。那么就尝试用map进行优化

[写法1]用vector存三维数组(内存超限)

class Solution {int row, col, dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};int dfs(int s, int x, int y, vector<vector<int>>& grid, vector<vector<vector<int>>>& f) {if (x < 0 || x >= row || y < 0 || y >= col) return 0;if (f[s][x][y] != -1) return f[s][x][y];int ans = 0;if (grid[x][y] == 2) {// 检查是否每一个无障碍方格都到过int i;for (i = 0; i < col * row; i++)if ((s >> i & 1) == 0 &&grid[i / col][i - i / col * col] == 0) {break;}if (i == col * row)ans = 1;elseans = 0;} else if ((s >> (col * x + y) & 1) == 1 || grid[x][y] == -1) {ans = 0;} else {grid[x][y] = -1;for (int i = 0; i < 4; i++) {int nx = x + dx[i], ny = y + dy[i];ans += dfs(s | 1 << (col * x + y), nx, ny, grid, f);}grid[x][y] = 0;}f[s][x][y] = ans;return ans;}public:int uniquePathsIII(vector<vector<int>>& grid) {row = grid.size();col = grid[0].size();vector<vector<vector<int>>> f(1 << (row * col), vector<vector<int>>(row, vector<int>(col, -1)));for (int i = 0; i < row; i++)for (int j = 0; j < col; j++)if (grid[i][j] == 1) {grid[i][j] = 0;               // 为了方便return dfs(0, i, j, grid, f); // i,j是起始点}return -1;}
};

在这里插入图片描述
(*  ̄︿ ̄)

[写法2]map存三元组(通过)

class Solution {// 为了节约内存,把f[1<<20][20][20]变为map,缺点是速度变慢map<pair<int, pair<int, int>>, int> f;int row, col, dx[4] = {0, 0, 1, -1}, dy[4] = {1, -1, 0, 0};int dfs(int s, int x, int y, vector<vector<int>>& grid) {if (x < 0 || x >= row || y < 0 || y >= col) return 0;if (f.count(make_pair(s, make_pair(x,y)))) return f[make_pair(s, make_pair(x,y))];int ans = 0;if (grid[x][y] == 2) {// 检查是否每一个无障碍方格都到过int i;for (i = 0; i < col * row; i++)if ((s >> i & 1) == 0 && grid[i / col][i - i / col * col] == 0)break;if (i == col * row) ans = 1;} else if ((s >> (col * x + y) & 1) == 1 || grid[x][y] == -1) {ans = 0;} else {grid[x][y] = -1;for (int i = 0; i < 4; i++) {int nx = x + dx[i], ny = y + dy[i];ans += dfs(s | 1 << (col * x + y), nx, ny, grid);}grid[x][y] = 0;}f[make_pair(s, make_pair(x,y))] = ans;return ans;}public:int uniquePathsIII(vector<vector<int>>& grid) {row = grid.size();col = grid[0].size();for (int i = 0; i < row; i++)for (int j = 0; j < col; j++)if (grid[i][j] == 1) {grid[i][j] = 0;               // 为了方便return dfs(0, i, j, grid); // i,j是起始点}return -1;}
};

继续优化:用map实现记忆化时,状态要压缩的彻底。比如本题要记忆的内容实际上是三维的(状态、x、y) 这三个不用写成三元组,一个32位的整型int就能存下来。

五题小结

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

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

相关文章

Mysql5.7的安装详细步骤(计算机专业大一新生必看)

以下是Mysql 5.7的安装详细步骤&#xff1a; 下载安装程序&#xff1a; 前往Mysql官方网站&#xff1a;https://dev.mysql.com/downloads/mysql/5.7.html选择适合您操作系统的版本进行下载。 Mysql windows64位 下载安装版本5.7 : https://dev.mysql.com/downloads/file/?id52…

基于Python实现银行卡识别

在本文中将介绍如何使用Python和深度学习技术来实现银行卡识别功能。银行卡识别是一个在金融、安全等领域具有重要应用的问题&#xff0c;将使用深度学习模型来实现银行卡图像的识别和分类。 目录 引言数据集准备预处理和特征提取模型选择与训练模型评估与性能优化部署与应用 引…

深入探讨javascript的流程控制与分支结构,以及js的函数

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属的专栏&#xff1a;前端泛海 景天的主页&#xff1a;景天科技苑 文章目录 1.流程控制与分支结构1.if分支结构2.switch case 分支结构3.循环结…

ROS从入门到精通4-2:Docker安装ROS、可视化仿真与终端复用

目录 0 专栏介绍1 Docker安装ROS2 Docker可视化仿真2.1 显示配置2.2 启动容器 3 终端复用工具3.1 session操作3.2 window操作3.3 pane操作3.4 其他操作 0 专栏介绍 本专栏旨在通过对ROS的系统学习&#xff0c;掌握ROS底层基本分布式原理&#xff0c;并具有机器人建模和应用ROS…

UE5数字孪生系列笔记(一)

智慧城市数字孪生系统 虚幻引擎连接数据库 将自己的mysql版本的libmysql.dll替换掉插件里面的libmysql.dll 然后将这个插件目录复制到虚幻项目目录下 然后添加这个插件即可 新建一个UMG&#xff0c;添加一个按钮试试&#xff0c;数据库是否连接 将UI添加到视口 打印是否连接…

C语言初阶—操作符

逻辑操作符&#xff1a; 这段代码的结果是什么&#xff1f; #include <stdio.h>int main() {int i 0,a 0,b 2,c 3,d 4;i a && b && d;printf("a%d,b%d,c%d,d%d\n",a,b,c,d);return 0; } 计算的时候&#xff0c;a先使用&#xff0c;是0&…

EVSV08-25、EVSV08-28、EVEP-08、EVEP-10、EVGR-10插装式比例阀放大器

EVBP-10、EVF-12、EVGB-10、EVRB-02、EVGB-E081、EVRB-03、EVF-08、EVRB-06、EVF-10、EVRB-10、EVEP-12、EVR-01、EVR-10、EVR-12、EVR-T8、EVSV08-20、EVSV08-25、EVSV08-28、EVEP-08、EVEP-10、EVGR-10插装式比例阀包括比例止逆阀、比例换向阀、比例调速阀、比例减压阀、比例…

julia语言中的异常处理

在程序运行中&#xff0c;可能会遇到各种预期之外的情况&#xff0c;如用户输入错误、网络故障、硬件问题等。通过异常处理机制&#xff0c;将错误处理代码与正常的业务逻辑代码分离开来&#xff0c;程序可以在遇到这些问题时做出适当的响应&#xff0c;而不是直接崩溃&#xf…

在vue3中使用el-tree-select做一个树形下拉选择器

el-tree-select是一个含有下拉菜单的树形选择器&#xff0c;结合了 el-tree 和 el-select 两个组件的功能。 因为包含了el-tree的功能&#xff0c;我们可以自定义tree的节点&#xff0c;创造出想要的组件 使用default插槽可以自定义节点内容&#xff0c;它的default插槽相当于…

python自动化之获取实际响应数据-登录模块与我的商铺(第四天)

1.配置文件 新建config.py(config包) 2.登录 新建login.py模块(lib-apiLib) 根据接口文档,可知道登录接口的密码需要md5加密 接口文档,如有需要,可在评论区留言!!! login.py代码 # -*- coding: utf-8 -*- # @File : login.py # @Time : 2024/3/4 15:32 # @Autho…

本地项目推送到腾讯云轻量应用服务器教程(并实现本地推送远程自动更新)

将本地项目上传到腾讯云轻量应用服务器并实现后续的推送更新&#xff0c;具体步骤如下&#xff1a; 在本地项目目录下初始化 Git 仓库&#xff1a; cd 项目目录 git init将项目文件添加到 Git 仓库并提交&#xff1a; git add . git commit -m "Initial commit"在…

LeetCode_25_困难_K个一组翻转链表

文章目录 1. 题目2. 思路及代码实现&#xff08;Python&#xff09;2.1 模拟 1. 题目 给你链表的头节点 h e a d head head &#xff0c;每 k k k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k k k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节…