综合练习dfs_1

1863. 找出所有子集的异或总和再求和

之前我们就做了到关于找集合子集的问题,但我们不需要记录路径上的数,求路径上数的异或和就可以。

class Solution {
    int path;
    int sum=0;
public:
    int subsetXORSum(vector<int>& nums) {
        dfs(nums,0);
        return sum;
    }
    void dfs(vector<int>&nums,int pos)
    {
        sum+=path;
        for(int i=pos;i<nums.size();i++)
        {
            path^=nums[i];
            dfs(nums,i+1);
            path^=nums[i];//恢复现场 A^A==0
        }
    }
};

47. 全排列 II

之前做的全排列的题是不含重复的数字,但这道题有重复的数字且返回不重复的全排列。

选数时要满足两个条件:

1.在一条路径上同一个数只能选一次(不是值相同的数),和全排列1一样用check[]标记该数是否用过。

2.在同一个节点的所有分支中,相同值的元素只能选一次

eg.[1,1,1,2] 看似可以选4个数,但为了不重复第一个数只能选1_ _ _ 2_ _ _。

当我们选了第一个1,第二个1第三个1就不能选了,因为以第2 3个1开头的数和第一个1都是值一样的。

所以我们选数时1.该数没有被用过 2.如果该数和前一个数相同也不能选,但看图中左下分支

3.如果和前一个数相同,但前一个数已经被用过了 (可以理解为和其它数不在同一个分支上) 还是可以选的。(因为要判断和前一个数是否相同 所以先sort()排序)

4.i==0时 i-1会越界 只要i==0且该数每被用过就可以选

反过来我们也可以从不合法的分支入手,1.用过直接返回2.没用过 首先不是第一个数(因为没有i-1)该数和前一个相等 且前一个数没有被用过。

class Solution {
    vector<vector<int>> re;
    vector<int> path;
    bool check[9];
    int n;
public:
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        n=nums.size();
        dfs(nums);
        return re;
    }
    void dfs(vector<int>& nums)
    {
        if(path.size()==n)
        {
            re.push_back(path);
            return;
        }
        for(int i=0;i<n;i++)
        {
            //只关心合法分支
            if(check[i]==false&&(i==0||nums[i]!=nums[i-1]||check[i-1]==true))
            {
                path.push_back(nums[i]);
                check[i]=true;
                dfs(nums);
                //回溯
                path.pop_back();
                check[i]=false;
            }
        }
    }
};

17. 电话号码的字母组合

1.先建立数字和字符串的映射关系。可以用字符串数组代替哈希表,数组前两个设为空。

2.传参数pos表示要找数字下标,用数字根据映射关系找到对应的字符串,遍历字符串进行递归。

3.处理特殊情况 digits=""没有数字直接返回空,进行dfs会push""字符串

class Solution {
    vector<string> re;
    vector<string> hash={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
    string path;
public:
    vector<string> letterCombinations(string digits) {
        if(digits=="") return re;
        dfs(digits,0);
        return re;
    }
    void dfs(string digits,int pos)
    {
        if(path.size()==digits.size())
        {
            re.push_back(path);
            return;
        }
        //pos表示递归到第几层 有几个数字就有几层
        for(auto&ch:hash[digits[pos]-'0'])
        {
            path+=ch;
            dfs(digits,pos+1);
            //回溯
            path.pop_back();
        }
    }
};

22. 括号生成

递归返回的路径字符数肯定是n*2的,我们直接递归枚举出所有的情况,再进行剪枝剪去不能组成括号的情况:1.左括号数不能大于n 2.在递归过程中右括号的数不能大于左括号的数

递归终止条件:path==n*2 或者右括号数==n

class Solution {
    vector<string> re;
    string path;
public:
    vector<string> generateParenthesis(int n) {
        dfs(n,0,0);
        return re;
    }
    void dfs(int n,int l,int r)
    {
        if(r>l||l>n) return;//剪枝
        if(path.size()==n*2)
        {
            re.push_back(path);
            return;
        }
        path+="(";;
        dfs(n,l+1,r);
        path.pop_back();//恢复现场
        path+=")";
        dfs(n,l,r+1);
        path.pop_back();

    }
};

77. 组合

因为不能出现重复的eg.[1,2] [2,1],所以在递归时要进行剪枝 eg.2_ _ _选数时不能选自身 也不能选前面的数,只能选后面的数。

dfs函数参数pos传下一个要遍历的数

class Solution {
    vector<vector<int>> re;
    vector<int> path;
    int n,k;
public:
    vector<vector<int>> combine(int _n, int _k) {
        n=_n+1,k=_k;
        dfs(1);
        return re;
    }
    void dfs(int pos)
    {
        if(path.size()==k)
        {
            re.push_back(path);
            return;
        }
        for(int i=pos;i<n;i++)
        {
            path.push_back(i);
            dfs(i+1);//注意是i+1 不是pos+1
            //回溯
            path.pop_back();
        }
    }
};

i代表了当前递归层选择的数字,因此我们应该从i+1开始递归,确保下一次选择当前数字的后面。

494. 目标和

这道题和我们之前做的求子集时,用的方法一相同。对一个数选和不选,到这道题就是加和减。暴力枚举每种情况的加和减,统计符合条件的数。

1.传参数,pos指对第几个元素进行+/-

2.定义int path记录最终的和。(可以定义成全局的,也可以传参 定义成局部的,这样就不用恢复现场)

3.到最后一个数 path==target re++ 返回

1.path局部

class Solution {
    int count=0;

    int target;
public:
    int findTargetSumWays(vector<int>& nums, int _target) {
        target=_target;
        dfs(nums,0,0);
        return count;
    }
    void dfs(vector<int>& nums,int pos,int path)
    {
        if(pos==nums.size())
        {
            if(path==target) count++;
            return;
        }
        //1.+
        dfs(nums,pos+1,path+nums[pos]);

        //2.-
        dfs(nums,pos+1,path-nums[pos]);
  
    }
};

dfs(nums,pos+1,path+=nums[pos]); 不能写成+=,不能改变原本局部变量path的值

2.path全局

class Solution {
    int count=0;
    int path=0;
    int target;
public:
    int findTargetSumWays(vector<int>& nums, int _target) {
        target=_target;
        dfs(nums,0);
        return count;
    }
    void dfs(vector<int>& nums,int pos)
    {
        if(pos==nums.size())
        {
            if(path==target) count++;
            return;
        }
        //1.+
        path+=nums[pos];
        dfs(nums,pos+1);
        path-=nums[pos];
        //2.-
        path-=nums[pos];
        dfs(nums,pos+1);
        path+=nums[pos];
    }
};

39. 组合总和

数组每个元素不同,但可以重复选。

[2,3,5],第一个数选了2 后面还可以选2/3/5。

但第一个数选了3(数组中第二个元素),后面还可以选2吗?不能选,会出现重复情况。eg.[2,3,3] [3,2,3]。所以当我们选完一个数后,选下一个数时,只能选该数和其后面的数。

pos记录选到数组的第几个数

class Solution {
    vector<vector<int>> re;
    vector<int> path;
    int target;

public:
    vector<vector<int>> combinationSum(vector<int>& nums, int _target) {
        target = _target;
        dfs(nums, 0, 0);
        return re;
    }
    void dfs(vector<int>& nums, int sum, int pos) {
        if (sum == target) 
        {
            re.push_back(path);
            return;
        }
        //超过目标值||pos越界
        if (sum > target || pos == nums.size())
            return;
        for (int i = pos; i < nums.size(); i++) 
        {
            path.push_back(nums[i]);
            dfs(nums, sum + nums[i], i);
            path.pop_back();
        }
    }
};

注意可以选重复的数,所以要包含自身 dfs(nums,sum+nums[i],i) 不是i+1,i+1是只选该数后面的情况。

解法二:

我们可以先对选数组第一个数,从0开始累加直到大于目标值。0 2 4 6 8 再从小于目标值的情况中继续选数组第二个元素,同理也是不断累加直到大于目标值。

注意:在累积的过程中是不进行回溯的,在原基础上再加1次。等向上层返回时再进行回溯。

class Solution {
    vector<vector<int>> re;
    vector<int> path;
    int target;

public:
    vector<vector<int>> combinationSum(vector<int>& nums, int _target) {
        target = _target;
        dfs(nums, 0, 0);
        return re;
    }
    void dfs(vector<int>& nums, int sum, int pos) {
        if (sum == target) 
        {
            re.push_back(path);
            return;
        }
        if (sum > target || pos == nums.size())
            return;
        //将所有<=target情况push进去 <情况进入下一层累加 =记录并返回
        for(int k=0;sum+k*nums[pos]<=target;k++)
        {
            //k!=0进行push
            if(k) path.push_back(nums[pos]);
            dfs(nums,sum+k*nums[pos],pos+1);
        }
        //push几次就回溯几次 因为k==0 没有进行push所以这种情况不能进行pop 从k==1情况算起
        for(int k=1;sum+k*nums[pos]<=target;k++)
        {
            path.pop_back();
        }
    }
};

784. 字母大小写全排列

通过改变原字符串中的字母大小写,变成不同的字符串,问一共有多少种情况?

这个字符串里面有数字,我们可以用数组记录每个字母在字符串的下标,通过遍历数组间接遍历字符串中的字母。

遍历字符串字母,暴力枚举所有可能的情况。

pos记录要改变字母位置,改变完后选下一个要选改数后面的,选前面的会出现重复情况,传i+1。(i当前遍历的位置)

class Solution {
    vector<string> re;
    string path;
    vector<int> sub;
public:
    vector<string> letterCasePermutation(string s) {
        path=s;
        for(int i=0;i<s.size();i++) if(islower(s[i])||isupper(s[i])) sub.push_back(i);
        dfs(0);
        return re;
    }
    char toggle_case(char c)
    {
        if(isupper(c)) return tolower(c); //c+32变小写
        else if(islower(c)) return toupper(c); //c-32变大写
        else return c;
    }
    void dfs(int pos)
    {
        re.push_back(path);
        for(int i=pos;i<sub.size();i++)
        {
            path[sub[i]]=toggle_case(path[sub[i]]);//改变大小写
            dfs(i+1);
            path[sub[i]]=toggle_case(path[sub[i]]);//回溯
        }
        return;
    }
};
相关推荐
破-风3 小时前
leetcode-------mysql
算法·leetcode·职场和发展
自不量力的A同学6 小时前
如何利用人工智能算法优化知识分类和标签?
人工智能·算法·分类
CodeJourney.7 小时前
PyTorch不同优化器比较
人工智能·pytorch·算法·能源
winner88817 小时前
对比学习损失函数 - InfoNCE
学习·算法·对比学习·infonce
南宫生8 小时前
力扣-数据结构-12【算法学习day.83】
java·数据结构·学习·算法·leetcode
KeyPan8 小时前
【数据结构与算法:五、树和二叉树】
java·开发语言·数据结构·人工智能·算法·机器学习·计算机视觉
WBingJ8 小时前
机器学习基础-贝叶斯分类器
人工智能·算法·机器学习
Lenyiin8 小时前
第431场周赛:最长乘积等价子数组、计算字符串的镜像分数、收集连续 K 个袋子可以获得的最多硬币数量、不重叠区间的最大得分
c++·算法·leetcode·周赛·lenyiin
行知SLAM9 小时前
第0章 机器人及自动驾驶SLAM定位方法全解析及入门进阶学习建议
人工智能·算法·机器人·自动驾驶
起名方面没有灵感11 小时前
力扣23.合并K个升序链表
java·算法