leetcode刷题之回溯法

本文介绍了回溯法在排列问题(如全排列和组合)、处理重复字符的策略、以及在子集、递增子序列和IP地址恢复中的应用。通过实例展示了如何使用backtracking算法解决LeetCode中的经典题目,如46全排列、47全排列(含重复数字)、39组合总和和131分割回文串。
摘要由CSDN通过智能技术生成

基本套路

套路来源于代码随想录模板

void backtracking(参数) {
if (终止条件) {

    存放结果;
    return;
}

for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
    处理节点;
    backtracking(路径,选择列表); // 递归
    回溯,撤销处理结果
}
}

排列问题

leetcode46 全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
//递归深度 nums.sze(), 每层宽度num.size()剪枝已经使用过的。
相较模板特殊之处:used数组

class Solution {
    vector<vector<int>> res;
    vector<int> path;
public:
    vector<vector<int>> permute(vector<int>& nums) {
        res.clear();
        path.clear();
        vector<bool> used(nums.size(), false);
        backtracking(nums, used);
        return res;

    }        
    void backtracking(vector<int>& nums,vector<bool>&used) {//参数是已经走过的路径和总集合
    if (path.size()==nums.size()) {
        res.push_back(path);
        return;
    }
    for(int i=0;i<nums.size();i++)
    {
        if(used[i]==true)
        continue;
        path.push_back(nums[i]);//处理节点(使之为本层集合可选元素)
        used[i]=true;
        backtracking(nums,used);//选择列表(路径被设为全局变量了)
        path.pop_back();//回溯
        used[i]=false;

    }
    }
};
class Solution {
   List<List<Integer>>ans=new ArrayList<>();
    LinkedList<Integer> path=new LinkedList<>(); 

    public List<List<Integer>> permute(int[] nums) {
        boolean []used=new  boolean[nums.length];//和c++区别
        if(nums.length==0)
        return ans;
        backtracking(nums, used);
        return ans;


    }
    private void backtracking(int[] nums,boolean[] used)
    {
        if(path.size()==nums.length)
        {
            ans.add(new ArrayList<>(path));//重点关注:这里的add方法表明将ans的尾部指向***所以不能写ans.add(path)而是要重新开辟一片空间来写,否则path变化时ans中的值也会跟着变化

        }
        for(int i=0;i<nums.length;i++)
        {
            if(used[i])
            {continue;}
            path.add(nums[i]);
            used[i]=true;
            backtracking(nums, used);
            used[i]=false;
            path.removeLast();

        }
    } 
}

剑指38 全排列(含有重复字符)

剑指 Offer 38. 字符串的排列 输入一个字符串,打印出该字符串中字符的所有排列。

你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

示例:

输入:s = “abc” 输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]

string----------vector char 转换

(方法来源https://blog.csdn.net/wangshubo1989/article/details/50274289/

---------vector<char>如何转string:

std::vector<char> data ;
std::string res;
//方法一
for (int i = 0;i<data.size();++i) {
  res+=data[i];
}
res+='\0';
std:cout << res;

 //方法二
std::vector<char> data ;
std::string res;
res.insert(res.begin(), data.begin(), data.end());
std::cout << res;

 //方法三
std::vector<char> *data = response->getResponseData();
std::string res;
const char* s = &(*data->begin());
res = std::string(s, data->size());
std::cout << res;

-----string转化为vector<char>

string ch = "what a fucking day!";
vector <char> ta;
ta.resize(ch.size());
ta.assign(ch.begin(),ch.end());

程序代码:

class Solution {
public:
vector<char> path;
vector<string> res;
    vector<string> permutation(string s) {
        path.clear();
        res.clear();
        sort(s.begin(),s.end());//排序对去重很关键
        vector<bool>used(s.length(),false);
        backtracking(s, used);
        return res;


    }
    void backtracking(string &s,vector<bool>&used)
    {
        if(s.length()==path.size())
        {
            string m;
            for(int i=0;i<path.size();i++)
            {
                m+=path[i];
            }
          

            res.push_back(m);
            return;
        }
        for(int i=0;i<s.length();i++)
        {
            //关于去重同一层:used[i-1]==false代表在同层已用过
            //used[i-1]==true代表在同支曾用过
            if(used[i]==true)
            continue;
            if(i>0&&s[i]==s[i-1]&&used[i-1]==false)//去重
            {
                continue;
            }
            used[i]=true;
            path.push_back(s[i]);
            backtracking(s,used);
            used[i]=false;//回溯
            path.pop_back();


        }

    }
};

leetcode47全排列(含有重复数字)

  1. 全排列 II
    给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
    示例 1:
    输入:nums = [1,1,2]
    输出:
    [[1,1,2],
    [1,2,1],
    [2,1,1]]
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        res.clear();
        path.clear();
        sort(nums.begin(),nums.end());
        vector<bool> used(nums.size(),false);
        backtracking(nums,used);
        return res;

    }
    void backtracking(vector<int>&nums,vector<bool>&used)
    {
        if(nums.size()==path.size())
        {
            res.push_back(path);
            return;
        }
        for(int i=0;i<nums.size();i++)
        {
            if(used[i]==true)
            continue;
            if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false)
            continue;
            used[i]=true;
            path.push_back(nums[i]);
            backtracking(nums,used);
            used[i]=false;
            path.pop_back();
        }

    }
};

组合问题

leetcode 77 组合问题

  1. 组合
    给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
    示例:
    输入: n = 4, k = 2
    输出:
    [
    [2,4],
    [3,4],
    [2,3],
    [1,2],
    [1,3],
    [1,4],
    ]
class Solution {
public:
vector<vector<int>> res;
vector<int> path;

void backtracking(int n,int k,int startindex)//待搜索路径,从左向右拿数字,不存在重复数字
{
if(path.size()==k)
{
    res.push_back(path);
    return;}
for(int i=startindex;i<=n;i++)
{
    path.push_back(i);//处理节点
    backtracking(n, k, i+1);//递归
    path.pop_back();//撤销

}
}

    vector<vector<int>> combine(int n, int k) {
        backtracking(n,k,1);
        return res;



    }
};

leetcode39 组合总和(有重复,无限深度)

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。

java版本

class Solution {
    List<List<Integer>> res =new ArrayList<>();
    LinkedList<Integer > path=new LinkedList<>();

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        
        if(candidates.length==0)
        return res;
        backtracing(candidates, target, 0, 0);
        return res;

    }
    private void backtracing (int []candidates,int target, int startindex,int sum)
    {
        if(sum>target)
        {
            return;
        
        }
        if(sum==target)
        {
            res.add(new ArrayList<>(path));
            return; 
        }
        for(int i=startindex;i<candidates.length;i++)
        {
            sum=sum+candidates[i];
            path.add(candidates[i]);
            backtracing(candidates, target, i, sum);
            sum=sum-candidates[i];
            path.removeLast();


        }
    }
}

子集问题

leetcode90子集(有重复)

  1. 子集 II
    给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
    解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
    示例 1:输入:nums = [1,2,2]
    输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
    示例 2:输入:nums = [0]
    输出:[[],[0]]
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        vector<bool>used(nums.size(),false);
        sort(nums.begin(),nums.end());
        for(int i=0;i<=nums.size();i++)
        backtracking(used, 0, nums, i);
        return res;

    }
    void backtracking(vector<bool>used,int startindex,vector<int> nums,int k)
    {
        if(path.size()==k)
        {
            res.push_back(path);
            return;//可以不加return因为子集问题遍历整棵树,不需要剪枝
            for(int i=startindex;i<nums.size();i++)
        {
            if(i>0&&used[i-1]==false&&nums[i-1]==nums[i])//同层不允许使用重复的数字
            {
               continue;
            }
            used[i]=true;
            path.push_back(nums[i]);
            backtracking(used, i+1, nums, k);
            path.pop_back();
            used[i]=false;
        }
    }

};

leetcode 491 递增子序列集合

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是 2 。示例:
输入:[4, 6, 7, 7]
输出:[[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]
提示:
给定数组的长度不会超过15。
数组中的整数范围是 [-100,100]。
给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。

同层不可使用重复数字(不可排序情况)使用uset数组记录本层使用过的元素去重(used另一种情况)

class Solution {
public:
//类似 不可重复选择 递归深度不限制 子集问题 
vector<vector<int>> res;
vector<int> path;
    vector<vector<int>> findSubsequences(vector<int>& nums) {
        backtracing(nums, 0);
        return res;


    }
void backtracing (vector<int >& nums,int start)
{
    if(path.size()>1)
    {
        res.push_back(path);//不加return 因为要遍历整棵树,所以不设立递归终止条件,遍历完了自然终止
    }
    //因为无法事先排序,所以无法采取used[i-1]=false这种来判断
    int uset[201]={0};//用数组去重,保证同一层中没有重复取值
    for(int i=start;i<nums.size();i++)
    {
        if((!path.empty()&&nums[i]<path.back())||uset[nums[i]+100]!=0 )//同层不选择该元素原因
        continue;
        uset[nums[i]+100]=1;
        path.push_back(nums[i]);
        backtracing(nums, i+1);
        path.pop_back();

    }
}

};

分割问题

leetcode 131 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。

类似无重复选择 不限制递归深度 的组合问题

class Solution {
public:
//回溯深度不限,无重复选择
//回溯终止条件:startindex==s.length(); 
vector<string> path;
vector<vector<string>> res;
    vector<vector<string>> partition(string s) {
        backtracking(0, s);
        return res;
        

    }
    void backtracking(int startindex,string s)
    {
        if(startindex==s.length())//递归结束条件:分割结束
        {
            res.push_back(path);
            return;

        }
        for(int i=startindex;i<s.length();i++)
        {
            if(!isvalid(startindex, i, s))//本层不选择条件:非回文串
                continue;
            path.push_back(s.substr(startindex,i-startindex+1));
            backtracking(i+1, s);
            path.pop_back();


        }

    }
    bool isvalid (int start,int


 end,string s)//左右都闭[]
    {

        for(int i=start,j=end;i<j;i++,j--)
        {
            if(s[i]!=s[j])
            return false;
        }
        return true;

    }
};
  1. 复原 IP 地址
    给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 IP 地址 。你可以按任何顺序返回答案。
    有效 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
    例如:“0.1.2.201” 和 “192.168.1.1” 是 有效 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效 IP 地址。
    示例 1:
    输入:s = “25525511135”
    输出:[“255.255.11.135”,“255.255.111.35”]
    示例 2:
    输入:s = “0000”
    输出:[“0.0.0.0”]
    示例 3:
    输入:s = “1111”
    输出:[“1.1.1.1”]
    示例 4:
    输入:s = “010010”
    输出:[“0.10.0.10”,“0.100.1.0”]
    示例 5:
    输入:s = “101023”
    输出:[“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“101.0.2.3”]
class Solution {
public:
vector<string> res;
vector<string> path;
vector<string> restoreIpAddresses(string s) {
    if(s.length()>12)
    return res; 
    backtracking(0, s, 0);
    return res;

};
void backtracking(int startindex,string&s,int k)//
{
    //在进行到第三层的时候来做控制,不如从for循环中直接做控制好
    if(k==3)
    {
        if(isvalid(startindex,s.length()-1,s))
        {
           // cout<<"startindex"<<startindex<<endl;
           // cout<<"s.length"<<s.length()<<endl;
            string m=s.substr(startindex,s.length()-startindex);//注意不要写.push_back因为这样就破坏了回溯法
           // cout<<m;
            string a;
            for(int i=0;i<3;i++)
            {
                a=a+path[i]+'.';
            }
            res.push_back(a+m);
             

        }   
    }
    for(int i=startindex;i<s.length();i++)
    {
        if(!isvalid(startindex, i, s))
        break;
        path.push_back(s.substr(startindex,i-startindex+1));
        k++;
        backtracking(i+1, s, k);
        k--;
        path.pop_back();
    }



}
bool isvalid(int start,int end,string&s)
{        if (start > end) {
            return false;
        }
        if (s[start] == '0' && start != end) { // 0开头的数字不合法
                return false;
        }
        int num = 0;
        for (int i = start; i <= end; i++) {
            if (s[i] > '9' || s[i] < '0') { // 遇到非数字字符不合法
                return false;
            }
            num = num * 10 + (s[i] - '0');
            if (num > 255) { // 如果大于255了不合法
                return false;
            }
        }
        return true;


}
    
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值