回溯算法a

回溯算法

常见的一些问题都是用回溯算法去解答的,例如:八皇后、0-1背包、字符串匹配;感觉回溯算法就是去遍历每种情况的可能性。
纯暴力搜索,可以解决的问题:

  • 组合问题:n个数里面按一定规则找出k个数的集合
  • 切割问题:一个字符串按照一定规则有几种切割方式
  • 子集:一个n个数的集合里有多少符合条件的子集
  • 排列:n个数按照一定规则全排列,有几种排列方式
  • 棋盘问题:n皇后、数独
    如果把子集问题、组合问题、分割问题都抽象成一棵树的话,那么组合问题和分割问题都是收集树的叶子节点,而子集问题是找数据所有节点;
    回溯模板
void backtracking(参数) {
if (终⽌条件) {
	存放结果;
	return;
}
for (选择:本层集合中元素(树中节点孩⼦的数量就是集合的⼤⼩)) {
	处理节点;
	backtracking(路径,选择列表);//递归
	回溯,撤销处理结果
	}
}

其中for循环为横向遍历,递归纵向遍历,回溯不断调整结果集

算法题目

1. 二进制手表

二进制手表顶部有 4 个 LED 代表 小时(0-11),底部的 6 个 LED 代表 分钟(0-59)。每个 LED 代表一个 0 或 1,最低位在右侧。
思路:需要去遍历各个情况,Integer.bitCount(i)实现的功能是计算一个(byte,short,char,int统一按照int方法计算)int,long类型的数值在二进制下“1”的数量。

class Solution {
    public List<String> readBinaryWatch(int turnedOn) {
        ArrayList<String> list = new ArrayList<String>();
        for(int h = 0;h<12;h++){//0-11小时
            for(int m = 0;m<60;m++){//0-59秒 枚举
                if(Integer.bitCount(h)+Integer.bitCount(m)==turnedOn){
                    list.add(h+":"+((m<10)? "0":"")+m);
                }
            }
        }
        return list;
    }
}

2. 找出所有自己的异或和再求和

一个数组的 异或总和 定义为数组中所有元素按位 XOR 的结果;如果数组为 空 ,则异或总和为 0 。
例如,数组 [2,5,6] 的 异或总和 为 2 XOR 5 XOR 6 = 1 。
给你一个数组 nums ,请你求出 nums 中每个 子集 的 异或总和 ,计算并返回这些值相加之 和 。
注意:在本题中,元素 相同 的不同子集应 多次 计数。
数组 a 是数组 b 的一个 子集 的前提条件是:从 b 删除几个(也可能不删除)元素能够得到 a 。
思路:遍历nums中的每个数,每个数都有两种可能,或者取或者不取,最终将所有结果求和,是0-1背包问题的简化

class Solution {
    int sum=0;int res=0;
    public int subsetXORSum(int[] nums) {
        yihuo(nums,0,sum);
        return res;
    }
    public void yihuo(int[]nums,int i,int sum){
        if(i==nums.length){
            res+=sum;
            return;
        }
        yihuo(nums,i+1,sum);
        sum = sum^nums[i];
        yihuo(nums,i+1,sum);
    }
}

子集问题

3.子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
思路:感觉就是在遍历所有的可能出现的情况

class Solution {
    List<List<Integer>> list = new ArrayList<List<Integer>>();
    List<Integer> li = new ArrayList<Integer>();
    public List<List<Integer>> subsets(int[] nums) {
        backtracking(nums,0);
        return list;
    }
    public void backtracking(int[] nums,int start){
        list.add(new ArrayList<Integer>(li));//收割结果的位置,需要收割各个节点的值,而不是叶子节点的值
        if(start>=nums.length){
            return;
        }
        for(int i =start;i<nums.length;i++){
            li.add(nums[i]);
            backtracking(nums,i+1);
            li.remove(li.size()-1);
        }
    }
}

组合问题

4. 组合

给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
思路:

lass Solution {
    List<List<Integer>> list = new ArrayList<List<Integer>>();
    List<Integer> li = new ArrayList<Integer>();
    public List<List<Integer>> combine(int n, int k) {
        backtracking(n,k,0);
        return list;
    }
    public void backtracking(int n,int k,int start){
        if(li.size() == k){
            //收割结果
            list.add(new ArrayList<Integer>(li));
            return;
        }
        //单层处理逻辑
        for(int i = start;i<n;i++){
            li.add(i+1);
            backtracking(n,k,i+1);
            li.remove(li.size()-1);
        }

    }
}
5. 组合总和III

找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
思路:组合问题的延伸,上一题基础上增加了一个判断条件

class Solution {
    List<List<Integer>> list = new ArrayList<List<Integer>>();
    List<Integer> li = new ArrayList<Integer>();
    public List<List<Integer>> combinationSum3(int k, int n) {
        backtracking(k,n,0,0);
        return list;
    }
    public void backtracking(int k,int n,int start,int sum){
        if(li.size()==k){
            if(sum == n){
                list.add(new ArrayList<Integer>(li));
            }
            return;
        }     
        //单层处理逻辑
        for(int i = start;i<9;i++){
            li.add(i+1);
            sum += i+1;
            backtracking(k,n,i+1,sum);
            sum = sum -i-1;
            li.remove(li.size()-1);
        }
    }
}
6. 组合总和

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。
candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是唯一的。
对于给定的输入,保证和为 target 的唯一组合数少于 150 个。
思路:本题中的结束条件和之前有所不同,同时注意for循环开始的条件,同时这几个题都没有用到剪枝,如果考虑效率问题,可以添加上剪枝

class Solution {
    List<List<Integer>> list = new ArrayList<List<Integer>>();
    List<Integer> li = new ArrayList<Integer>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        backtracking(candidates,target,0,0);
        return list;
    }
    public void backtracking(int[] candidates, int target,int sum,int start){
        if(sum == target){
            //获取结果
            list.add(new ArrayList<Integer>(li));
            return;
        }
        if(sum>target){
            return;
        }
        //单层循环
        for(int i=start;i<candidates.length;i++){
            li.add(candidates[i]);
            sum +=candidates[i];
            backtracking(candidates,target,sum,i);
            sum -= candidates[i];
            li.remove(li.size()-1);
        }
    }
}
7.组合总和II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

注意:解集不能包含重复的组合。

思路:此题需要注意,candidates内会有重复元素,但是只能取一次,要做到同一层只能取一次但是每一个分支上的可以取
对应题目
代码随想录,回溯算法精讲v1.2.pdf

分割问题

8. 分割回文串

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
输入:s = “aab”
输出:[[“a”,“a”,“b”],[“aa”,“b”]]
思路:需要注意终止条件、以及每一层中如何去选择

class Solution {
    List<List<String>> list = new ArrayList<List<String>>();
    List<String> li = new ArrayList<String>();
    public List<List<String>> partition(String s) {
        backtracking(s,0);
        return list;
    }
    public void backtracking(String s,int start){
        if(start>=s.length()){
            list.add(new ArrayList<String>(li));
            return;
        }
        for(int i =start;i<s.length();i++){
            if(ishuiwen(s,start,i)){
                String ss = s.substring(start,i+1);
                li.add(ss);     
                backtracking(s,i+1);
                li.remove(li.size()-1); 
            }else{
                continue;
            }
        }
    }
    public boolean ishuiwen(String s,int l,int r){
        boolean flag = true;
        while(l<r){
            if(s.charAt(l)!=s.charAt(r)){
                flag = false;
                break;
            }
            l++;
            r--;
        }
        return flag;
    }
}
9.复原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 地址。
思路:需要在每层循环中判断是否满足条件,将满足条件的数保存,同时如果不满足条件,则直接break;

class Solution {
    List<String> list = new ArrayList<String>();
    List<String> li = new ArrayList<String>();
    public List<String> restoreIpAddresses(String s) {
        backtracking(s,0);
        return list;

    }
    public void backtracking(String s,int start){
        if(li.size()==4){
            if(start>=s.length()){
                //收割结果
                list.add(zhunhuan(li));
                return;
            }else{
                return;
            }
        }
        for(int i =start;i<s.length()&&li.size()<4;i++){
            if(isLegal(s,start,i)){
                String str = s.substring(start,i+1);
                li.add(str);
                backtracking(s,i+1);
                li.remove(li.size()-1);
            }else{
                //添加条件
                break;
                //continue;
            }
        }
    }
    public boolean isLegal(String s,int l,int r){
        if(l==r){//只有一位,即使是0也无所谓
            return true;
        }else{
            if(s.charAt(l)=='0'){//大于一位的,如果0开头的就排除
                return false;
            }else{//判断是否小于等于255
                int num =0;
                for(int i = l;i<=r;i++){
                    int shuzi = Character.getNumericValue(s.charAt(i));
                    num = num *10+shuzi;
                }
                if(num<=255){
                    return true;
                }else{
                    return false;
                }
            }
        }
    }
    public String zhunhuan(List<String> list){
        String str = "";
        for(int i =0;i<list.size();i++){
            str =str+list.get(i)+".";
        }
        return str.substring(0,str.length()-1);
    }
}

排序问题

10.全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
思路:
每层都需要从0开始搜索,而不是从start开始搜索
需要保留叶子节点的值
需要使用used数组来记录哪些元素已经使用过

class Solution {
    List<List<Integer>> list = new ArrayList<List<Integer>>();
    List<Integer> li = new ArrayList<Integer>();
    public List<List<Integer>> permute(int[] nums) {
        boolean[]used = new boolean[nums.length];
        backtracking(nums,used);
        return list;
    }
    public void backtracking(int[] nums,boolean[] used){
        if(li.size()==nums.length){
            list.add(new ArrayList<Integer>(li));
            return;
        }
        for(int i = 0;i<nums.length;i++){
            if(used[i]==true){
                continue;
            }
            used[i]=true;
            li.add(nums[i]);
            backtracking(nums,used);
            li.remove(li.size()-1);
            used[i]=false;
        }
    }
}
11.全排列II

给定一个可包含重复数字的序列nums,按任意属性返回所有不重复的全排列
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
思路:本题和上题的不同点在于这个是包含重复数字的序列,所以需要提前排序,然后在本次循环内进行判断,如果本次待加入的数字和前面的数字一样,则跳过本次,否则就重复了~~

class Solution {
   List<List<Integer>> list = new ArrayList<List<Integer>>();
   List<Integer> li = new ArrayList<Integer>();
   public List<List<Integer>> permuteUnique(int[] nums) {
       Arrays.sort(nums);
       boolean[] used = new boolean[nums.length];
       backtracking(nums,used);
       return list;
   }
   public void backtracking(int[] nums,boolean[] used){
       if(li.size()==nums.length){
           list.add(new ArrayList<Integer>(li));
           return;
       }
       for(int i =0;i<nums.length;i++){
           if(used[i]==true||(i>0&&nums[i]==nums[i-1]&&used[i-1]==false)){//used[i]==true 在一个树枝中向下搜索如果已经用过了则跳过;
           //(i>0&&nums[i]==nums[i-1]&&used[i-1]==false) 后面的数和前面的数一样,同时前面的那一条分支已经走过了used[i-1]==false,所以此个分支也应该跳过,如果走的化和上个一样的结果,所以需要continue;
               continue;
           }
           li.add(nums[i]);
           used[i]=true;
           backtracking(nums,used);
           used[i]=false;
           li.remove(li.size()-1);
       }
   }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值