Leedcode刷题——5 DFS+回溯

news2024/11/22 15:41:19

注:以下代码均为c++

1. 电话号码的字母组合

在这里插入图片描述
思路:
在这里插入图片描述
例如:”23“
请添加图片描述

在这里插入图片描述

//1. 我自己写的
vector<string> letterCombinations(string digits) {
    if(digits == "")
        return {};

    vector<string> state = {""};
    string s;
    vector<string> strs = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    for(int i = 0; i < digits.size(); i++){
        s = strs[digits[i] - '0'];  //"abc"
        vector<string> state_new = {};
        for(int j = 0; j < s.size(); j++){
            for(int t = 0; t < state.size(); t++){
                string sta = state[t] + s[j];
                state_new.push_back(sta);
            }
        }
        state = state_new;
    }
    return state;
}

//2. 答案
//把循环换种方式写
string strs[8] = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
vector<string> letterCombinations(string digits){
    if(digits == "")  return vector<string>();

    vector<string> state = {""};
    for(auto u: digits){
        vector<string> now;
        for(auto c: strs[u-'2']){
            for(auto s: state)
                now.push_back(s+c);
        }
        state = now;
    }
    return state;
}

2. 单词搜索

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
思路:
在这里插入图片描述

int n, m;  //行,列
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};  //dx为行加的距离,dy为列加的距离。四位分别表示为:上右下左

bool dfs(vector<vector<char>> &board, int i, int j, string &word,
         int u) {  //注:这里的board传引用的原因是:每搜索过一个元素后,我们需要修改board的值,对其进行标记。
    if (board[i][j] != word[u]) return false;
    if (u == word.size() - 1) return true;

    board[i][j] = '.';  //如果搜索过了,进行标记
    //依次搜索下一点的位置(上下左右)
    for (int k = 0; k < 4; k++) {
        int x = i + dx[k], y = j + dy[k];
        if (x >= 0 && x < n && y >= 0 && y < m)
            if (dfs(board, x, y, word, u + 1))
                return true;
    }
    //当上下左右都搜索完一遍后,将标记点复原。
    board[i][j] = word[u];
    return false;
}

bool exist(vector<vector<char>> &board, string word) {  //为什么传引用?因为不传引用会复制一遍整个数组,浪费时间。
    if (board.empty() || board[0].empty()) return false;

    n = board.size(), m = board[0].size();
    //1 枚举起点
    for (int i = 0; i < n; i++)
        for (int j = 0; j < m; j++)
            if (dfs(board, i, j, word, 0))  //字符网格,枚举起点的位置,搜索单词,搜索单词位置
                return true;
    return false;
}

3. 全排列

在这里插入图片描述
思路:
在这里插入图片描述
法1:枚举每个位置放哪个数
我们定义递归函数 dfs(vector& nums, int u) 表示输入数组为 nums,下一个待填入的位置是第u个位置(下标从 0 开始)。定义vector num;表示当前排列。那么整个递归函数分为两个情况:

  1. 如果 u=n,说明我们已经填完了 n 个位置,找到了一个可行的解,我们将 num 放入答案数组中,递归结束。
  2. 如果u<n,我们要考虑第u 个位置填哪个数。根据题目要求我们肯定不能填已经填过的数,因此很容易想到的一个处理手段是我们定义一个标记数组vector st;来标记已经填过的数,那么在填第 u个数的时候我们遍历题目给定的 n 个数,如果这个数没有被标记过,我们就尝试填入,并将其标记,继续尝试填下一个位置,即调用函数 dfs(nums, u+1);。搜索回溯的时候要撤销该个位置填的数以及标记,并继续尝试其他没被标记过的数。

这里用第一种方法。

vector<vector<int>> res;
vector<int> num;
vector<bool> st;  //标记已经填过的数
//我自己之前的想法是填完一个数删一个数,但是这样没办法把所有的排列都列出来,每个循环只能列一种。
//所有不能删除,应该用一个布尔数组标记已经填过的数。
int n;

void dfs(vector<int>& nums, int u){  //u表示格子枚举到哪一位
    if(u == n){
        res.push_back(num);
        return;
    }
    for(int i = 0; i < n; i++){
        if(!st[i]){  //如果该数没被填过
            st[i] = true;
            num.push_back(nums[i]);
            dfs(nums, u+1);
            num.pop_back();
            st[i]= false;
        }
    }
}
vector<vector<int>> permute(vector<int>& nums) {
    n = nums.size();
    st = vector<bool>(n);  //注:st需要初始化,默认为false
    dfs(nums, 0);
    return res;
}

4. 全排列2

在这里插入图片描述
思路:该题与上一道题的区别是序列中有重复数字。
要解决重复问题,我们只要设定一个规则,保证在填第 u个数的时候重复数字只会被填入一次即可。而在本题解中,我们选择对原数组排序,保证相同的数字都相邻,然后每次填入的数一定是这个数所在重复数集合中「从左往右第一个未被填过的数字」。

vector<vector<int>> res;
vector<int> num;
vector<bool> st;  //标记已经填过的数
//我自己之前的想法是填完一个数删一个数,但是这样没办法把所有的排列都列出来,每个循环只能列一种。
//所有不能删除,应该用一个布尔数组标记已经填过的数。
int n;

void dfs(vector<int>& nums, int u){  //u表示格子枚举到哪一位
    if(u == n){
        res.push_back(num);
        return;
    }
    for(int i = 0; i < n; i++){
        //注i>0一定别忘了,且要放到最前面
        if(i > 0 && st[i-1] && !st[i] && nums[i] == nums[i-1])  //如果上一个数被填过,该数没被填过,且该数与上一个数相同,则跳过,不继续枚举。
            continue;
        if(!st[i]){  //如果该数没被填过
            st[i] = true;
            num.push_back(nums[i]);
            dfs(nums, u+1);
            num.pop_back();
            st[i]= false;
        }
    }
}

vector<vector<int>> permuteUnique(vector<int>& nums) {
    n = nums.size();
    st = vector<bool>(n);  //注:st需要初始化,默认为false
    sort(nums.begin(), nums.end());  //处理重复的方法:排序
    dfs(nums, 0);
    return res;
}

红框为与上一题的区别。
在这里插入图片描述

5. 子集

在这里插入图片描述
法1:回溯
输入: [1, 2, 3]
请添加图片描述

//法1:回溯
vector<vector<int>> res = {{}};
vector<int> num;
void dfs(vector<int>& nums, int u){   //u: 从哪一项开始枚举
    if(u == nums.size())
        return;
    for(int i = u; i < nums.size(); i++){
        num.push_back(nums[i]);
        res.push_back(num);
        dfs(nums, i+1);  //注:下一个枚举项是i+1不是u+1
        num.pop_back();
    }
}

vector<vector<int>> subsets(vector<int>& nums) {
    dfs(nums, 0);
    return res;
}

法2:循环:利用二进制
这里枚举的所有二进制即为所有子集。
在这里插入图片描述
若i的二进制表示的第j位为1,则将其加入当前子集。
在这里插入图片描述

vector<vector<int>> subsets(vector<int>& nums) {
    vector<vector<int>> res;
    //二进制运算,O(1)的时间复杂度
    //1<<nums.size():将1左移n位,结果是一个二进制数,第n位是1,其它位都是0,即2^n
    for(int i = 0; i < 1<<nums.size(); i++){  //枚举所有0~2^n-1的二进制
        vector<int> now;
        for(int j = 0; j < nums.size(); j++){  //遍历每个二进制
            //i>>j & 1:将i右移j位,再与1
            if(i>>j & 1)  //判断i的二进制表示的第j位是否为1
                now.push_back(nums[j]);
        }
        res.push_back(now);
    }
    return res;
}

6. 子集2

在这里插入图片描述
思路:
请添加图片描述

vector<vector<int>> res = {{}};
vector<int> num;
vector<bool> st;  //标记已经用过的数,默认为false

void dfs(vector<int>& nums, int u){   //u: 从哪一项开始枚举
    if(u == nums.size())
        return;
    for(int i = u; i < nums.size(); i++){
        if(i > 0 && nums[i] == nums[i-1] && st[i-1] == false)  //如果该数与上一个数相同,且上一个数没有被用过,剪枝(数层去重)
            continue;
        num.push_back(nums[i]);
        st[i] = true;
        res.push_back(num);
        dfs(nums, i+1);  //注:下一个枚举项是i+1不是u+1
        //恢复现场
        num.pop_back();
        st[i] = false;
    }
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
    sort(nums.begin(), nums.end());
    st = vector<bool>(nums.size());
    dfs(nums, 0);
    return res;
}

与上一题区别:如下红框部分
在这里插入图片描述

7. 组合总和3

在这里插入图片描述
在这里插入图片描述
思路:
请添加图片描述

小技巧:倒着枚举

vector<int> num;
vector<vector<int>> nums;

void dfs(int k, int start, int n){  //枚举到第几个位置,开始枚举的数字,当前选择的数之和
    if(k == 0){  //如果枚举完了所有位数,则递归结束,返回。
        if(n == 0)  //如果如果所有数和为n,则将该组合加入答案。
            nums.push_back(num);
        //当 n!=0 时直接return:剪枝
        return;
    }
//    for(int i = start; i <= 9; i++){
//        num.push_back(i);
//        dfs(k-1, i+1, n-i);  //start保证下一次枚举的数字是当前数字之后的数字
//        num.pop_back();
//    }
    //这里的for循环可以有一个优化:保证再选k个数,i~9之间一定要留>=k个数才行,所以9-i+1>=k:剪枝
    for(int i = start; i <= 10-k; i++){
        num.push_back(i);
        dfs(k-1, i+1, n-i);  //start保证下一次枚举的数字是当前数字之后的数字
        num.pop_back();
    }
}

vector<vector<int>> combinationSum3(int k, int n) {
    dfs(k, 1, n);  //倒着枚举
    return nums;
}

8. N皇后2

在这里插入图片描述

思路:
全排列:枚举每个位置放哪个数,放这个数的时候不能用前面放过的数(标记已填过的数)
八皇后:枚举每一行皇后放到哪个位置上,放的时候不能产生冲突(标记已填过的列和已填过的斜线)
所以,八皇后问题可以等同于全排列问题。
问题:斜线应如何标记?
开两个数组,大小为2n,分别表示正斜线和反斜线。d = ud = vector<bool>(2 * n);
请添加图片描述

int ans = 0;
vector<bool> col, d, ud;  //标记:列,正斜线(y = x + b),反斜线(y = -x + b)

void dfs(int u, int n){  //枚举每一行
    if(u == n){  //找到一个八皇后解
        ans++;
        return;
    }
    for(int i = 0; i < n; i++){  //遍历每一列
        if(!col[i] && !d[u-i+n] && !ud[u+i]){  //如果该列没有皇后,且正斜线和反斜线也没有皇后
            col[i] = d[u-i+n] = ud[u+i] = true;  //放一个皇后,做标记。
            dfs(u+1, n);  //递归下一行
            col[i] = d[u-i+n] = ud[u+i] = false;  //恢复现场
        }
    }
}
int totalNQueens(int n) {
    col = vector<bool>(n);
    d = ud = vector<bool>(2 * n);
    dfs(0, n);
    return ans;
}

9. 解数独

在这里插入图片描述
在这里插入图片描述
本题与八皇后相似,区别是:
搜索所有符合题目要求的路径,返回void;
搜索单条路径,返回bool。

bool row[9][9], col[9][9], cell[3][3][9];  //标记每一行1~9是否已经填了,标记每一列1~9是否已经填了,标记每个小方格1~9是否已经填了。

//搜索所有符合题目要求的路径,返回void;搜索单条路径,返回bool。
//注意:这里需要有一个返回值,只要找到一个结果立刻返回,不需要再继续搜索下去了
bool dfs(vector<vector<char>>& board, int x, int y){
    if(y == 9)  x++, y = 0;  //如果y走到头,下一行重起
    if(x == 9)  return true;  //如果x走到头,递归结束
    if(board[x][y] != '.')  //如果该格子有数,则递归下一个
        return dfs(board, x, y + 1);

    for(int i = 0; i < 9; i++){  //遍历1~9每一个数
        //判断该数能否被填
        if(!row[x][i] && !col[y][i] && !cell[x/3][y/3][i]){
            //填数,做好标记
            board[x][y] = i + '1';
            row[x][i] = col[y][i] = cell[x/3][y/3][i] = true;
            //递归下一个
            if(dfs(board, x, y + 1))  return true;
            //恢复现场
            board[x][y] = '.';
            row[x][i] = col[y][i] = cell[x/3][y/3][i] = false;
        }
    }
    return false;
}

void solveSudoku(vector<vector<char>>& board) {
    //遍历一遍棋盘,标记所有已经填过的数
    for(int i = 0; i < 9; i++){
        for(int j = 0; j < 9; j++){
            if(board[i][j] != '.'){
                int a = board[i][j] - '1';
                row[i][a] = true, col[j][a] = true, cell[i/3][j/3][a] = true;
            }
        }
    }
    dfs(board, 0, 0);
}

10. 火柴拼正方形

在这里插入图片描述
在这里插入图片描述
思路:
从大到小枚举所有边。
剪枝:
如果当前木棍拼接失败,且是当前边第一个,则直接剪掉当前分支。
如果当前木棍拼接失败,且是当前边最后一个,则直接剪掉当前分支。
如果当前木棍拼接失败,则跳过接下来所有长度相同的木棍。

vector<bool> st;  //标记每个木棍是否被用过
bool dfs(vector<int>& nums, int u, int cur, int length){  //当前拼到了第几条边,当前边的长度,每条边的长度
	//注意这两句话顺序不能倒
    if(cur == length)  u++, cur = 0;  //如果当前边拼好了,开始拼下一条边。
    if(u == 4)  return true;  //如果所有边都拼好了,返回true
    //枚举每个木棍
    for(int i = 0; i < nums.size(); i++){
        //如果当前木棍没有被用过,且加上当前木棍的长度后不超过每条边的长度,才选择该木棍进行递归。
        if(!st[i] && cur + nums[i] <= length){
            st[i] = true;  //标记
            if(dfs(nums, u, cur + nums[i], length))  return true;  //递归
            st[i] = false;  //恢复现场
            //剪枝
            if(cur == 0)  return false;  //如果当前木棍拼接失败,且是当前边第一个,则直接剪掉当前分支。
            if(cur + nums[i] == length)  return false;  //如果当前木棍拼接失败,且是当前边最后一个,则直接剪掉当前分支。
            while(i + 1 < nums.size() && nums[i + 1] == nums[i]) i++;  //如果当前木棍拼接失败,则跳过接下来所有长度相同的木棍。
        }
    }
    return false;
}
bool makesquare(vector<int>& matchsticks) {
    //异常值判断:若没有木棍或木棍和不能被4整除,返回false
    int sum = 0;
    for(auto u: matchsticks)
        sum += u;
    if(sum == 0  || sum % 4 != 0)
        return false;
    //初始化st
    st = vector<bool>(matchsticks.size());
    //从大到小排序
    sort(matchsticks.begin(), matchsticks.end());
    reverse(matchsticks.begin(), matchsticks.end());

    return dfs(matchsticks, 0, 0, sum/4);
}

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

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

相关文章

读取txt文件并统计每行最长的单词以及长度

读取txt文件并统计每行最长的单词以及长度 题目 在 D:\\documant.txt 文本中,文件中有若干行英文文本,每行英文文本中有若干个单词&#xff0c;每个单词不会跨行出现每行至多包含100个字符,要求编写一个程序,处理文件,分析各行中的单词,找到每行中的最长单词&#xff0c;分别…

cmd 输入pip 提示不是内部或外部命令,也不是可运行的程序 或批处理文件。

解决办法&#xff1a; 把pthon的安装路径加入到环境变量中&#xff0c;重新打开cmd运行即可 比如&#xff1a;C:\Users\huanhuan\AppData\Local\Programs\Python\Python311\Scripts

测试环境搭建整套大数据系统(十:测试环境minio单节点部署)

一&#xff1a;部署minio 创建安装目录。 mkdir -p /data/server/minio/{bin,data,config,log}bin&#xff1a;二进制文件目录data&#xff1a;数据存储目录config&#xff1a;配置文件目录log&#xff1a;日志文件路径下载二进制安装包到bin目录&#xff0c;或者提前下载好拷…

edm邮件是什么意思:与普通邮件有何不同?

edm邮件是什么意思&#xff1f;如何优化邮件内容以提高转化率&#xff1f; edm邮件因其独特的营销价值而备受关注。那么&#xff0c;edm邮件究竟是什么意思呢&#xff1f;它与普通邮件又有哪些不同呢&#xff1f;下面&#xff0c;AokSend就来为大家介绍一下。 edm邮件的概念与…

KMP算法——解决字符串匹配问题

一般来说在你没学过KMP算法前&#xff0c;你解决字符串匹配问题会采用BF算法——BF算法&#xff0c;即暴力(Brute Force)算法&#xff0c;是普通的模式匹配算法&#xff0c;BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配&#xff0c;若相等&#xff0c;…

这三个长辈必备的手机模式,你给他们设置了吗?

您是否曾听家里长辈说过手机图标字体太小看起来很吃力&#xff1f;是否曾担心家里长辈在手机上误安装恶意软件&#xff1f;是否曾因为各种骚扰和推销电话而感到困扰&#xff1f;现在&#xff0c;这些问题不再是难题&#xff0c;因为有了华为手机的长辈关怀功能——一键开启放大…

运维打工人,兼职跑外卖的第二个周末

北京&#xff0c;晴&#xff0c;西南风1级。 前序 今天天气还行&#xff0c;赶紧起来&#xff0c;把衣服都洗洗&#xff0c;准备准备&#xff0c;去田老师吃饭早饭了。 一个甜饼、一个茶叶蛋、3元自助粥花费7.5。5个5挺吉利的。 跑外卖的意义 两个字减肥&#xff0c;记录刚入…

HCIP --- BGP 综合实验

目录 实验拓扑图&#xff1a; 实验要求&#xff1a; 实验步骤&#xff1a; 1.划分IP地址 R1的配置 R2的配置 R3的配置 R4的配置 R5的配置 R6的配置 R7的配置 R8的配置 2.检测查询IP地址 3.OSPF 建邻 4.查询OSPF 建邻是否成功 5.AS 2 内部BGP 建邻 a. AS 2 内部 IBG…

淘宝扭蛋机小程序:探索未知的惊喜之旅

你是否曾在商场里被那闪闪发光的扭蛋机吸引&#xff0c;却因为种种原因无法下手&#xff1f;现在&#xff0c;淘宝扭蛋机小程序带给你全新的扭蛋体验&#xff0c;让你随时随地都能感受到那份未知的惊喜。 淘宝扭蛋机小程序是一款集娱乐与购物于一体的全新应用。它汇聚了众多热…

(开源项目)OpenHarmony、社区共建Sample合入要求

1.新增Sample功能不能重复于当前已有Sample的功能&#xff1b; 2.新增Sample的工程推荐使用ArkTS语言编写&#xff1b; 3.新增Sample的工程推荐使用Stage模型编写&#xff1b; 4.新增Sample的工程中需要包含UI自动化用例&#xff08;ohosTest工程模块&#xff09;&#xff0…

Windows11安装NodeJS18并配置环境变量

从官网下载&#xff0c;或者从百度网盘下载 解压下载的zip包&#xff1a; 重命名为nodejs&#xff1a; 在nodejs中添加cache和global两个目录&#xff1a; 将nodejs和nodejs\global添加到环境变量&#xff1a; 打开终端&#xff0c;输入&#xff1a; node -v接着配置…

AI新工具(20240312) Midjourney官方发布角色一致性功能;免费且开源的简历制作工具;精确克隆语调、控制声音风格

1: Midjourney角色一致性功能 使人物画像在多方面高度一致成为可能。 Midjourney的角色一致性功能的使用方法如下&#xff1a; ⭐在你的输入指令后面加上 --cref URL&#xff0c;其中URL是你选择的角色图像的链接。 ⭐你可以通过 --cw 参数来调整参照的强度&#xff0c;范围…

基于Spring Boot的校园管理系统 ,计算机毕业设计(带源码+论文)

源码获取地址&#xff1a; 码呢-一个专注于技术分享的博客平台一个专注于技术分享的博客平台,大家以共同学习,乐于分享,拥抱开源的价值观进行学习交流http://www.xmbiao.cn/resource-details/1767745870094217218

高频:spring知识

1、bean的生命周期&#xff1f; 主要阶段 初始化 org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh 信息: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext67424e82: startup date []; root of context hi…

品牌升级 | 图扑物联正式启用新LOGO

为进一步提升品牌形象&#xff0c;提高品牌影响力&#xff0c;2024年&#xff0c;我们迎来了一次重要的品牌升级——LOGO迭代。此次升级&#xff0c;在传承与创新中既保留了公司的核心精神&#xff0c;又融入了新的视觉语言&#xff0c;不仅代表了公司的新形象、新面貌&#xf…

当运维深陷“困境”,如何“破旧立新”?

背景 先来看下运维小伙伴们遇到的几种境遇&#xff1a; 投产变更&#xff0c;流程流于形式 投产步骤特别复杂&#xff0c;几百套系统几百种投产方式&#xff0c;公司的测试环境流于形式&#xff0c;经常会有投产文档和真实操作不一致的情况 几百套系统&#xff0c;几十种操作…

rhce证书含金量高吗?rhce考试包括哪些内容?考完工资多少?

rhce证书含金量 高吗&#xff1f; 随着目前国内IT职业的飞速发展&#xff0c;RHCE已经成为IT职业的首选认证、高薪亮点。Linux人才出现大比例短缺的状况&#xff0c;企业对Linux人才的需求也继续升温。那相关认证RHCE现在具有怎样的含金量呢&#xff0c;本期小编给大家分析分析…

Docker入门笔记(1)

Docker入门笔记&#xff08;1&#xff09; 容器技术入门 之前我的WIT问卷管理系统在阿里云上部署需要好多配置&#xff0c;各个环境耦合的比较紧密&#xff0c;花了不少时间去做部署和调配。 现在有了Docker以后&#xff0c;我们可以把各种组件配置好&#xff0c;然后打包成…

docker部署Atheos云IDE平台

Codiad 是一个基于 Web 的 IDE 框架 部署 下载镜像 docker pull hlsiira/atheosvim docker-compose.yaml version: 3 services:atheos:image: hlsiira/atheosports:- 8565:80container_name: atheosrestart: always启动 docker-compose up -d访问 http://x.x.x.x:8565

【网络安全】 MSF生成木马教程

本文章仅用于信息安全学习&#xff0c;请遵守相关法律法规&#xff0c;严禁用于非法途径。若读者因此作出任何危害网络安全的行为&#xff0c;后果自负&#xff0c;与作者无关。 环境准备&#xff1a; 名称系统IP攻击机Kali Linux10.3.0.231客户端Windows 710.3.0.234 一、生…