回溯算法总结

回溯算法

一、回溯算法框架

回溯算法的框架:
result = []
def backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
    for 选择 in 选择列表:
        做选择
        backtrack(路径, 选择列表)
        撤销选择

二、相关题目

1.全排列

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

代码如下(示例):

class Solution {
    //全排列 回溯算法二刷
    //记录全部的排列
    List<List<Integer>> res = new LinkedList<>();

    public List<List<Integer>> permute(int[] nums) {
        // 回溯算法例子
        // 定义路径
        LinkedList<Integer> track = new LinkedList<>();
        backtrack(nums,track);

        return res;
    }

    void backtrack(int[] nums, LinkedList<Integer> track){
        // 判断回溯结束条件 如果num值全在track里
        if(track.size() == nums.length){
            // 注意track是一个对象引用 必须重新new一个
            res.add(new LinkedList<Integer>(track));
            return;
        }

        for(int i = 0;i < nums.length;i++){
            //如果num[i]值已经被选择了
            if(track.contains(nums[i])){
                continue;
            }

            //做选择
            track.add(nums[i]);

            //进入下一层决策递归
            backtrack(nums,track);

            //撤销选择
            track.removeLast();

        }
    }

}

2.子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

画图理解 注意选择列表 是用索引区分的
注意选择列表 是用索引区分的

代码:

class Solution {
    //回溯法 注意和全排列的回溯有些不一样 要加一个索引参数
    List<List<Integer>> res = new LinkedList<>();
    List<Integer> list = new LinkedList<>();

    public List<List<Integer>> subsets(int[] nums) {
        bfs(0,nums);
        return res;

    }

    public void bfs(int j, int[] nums){
        res.add(new LinkedList(list));
       
        for(int i = j;i < nums.length;i++){
            list.add(nums[i]);
            bfs(i + 1,nums);
            list.remove(list.size() - 1);
        }
    }
}

3.字符串排列 剑指offer

输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。

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

代码如下(示例):

import java.util.*;
class Solution {
// 理解参考https://www.cnblogs.com/cxjchen/p/3932949.html
//递归实现
/*
固定第一个字符,递归取得首位后面的各种字符串组合;
再把第一个字符与后面每一个字符交换,并同样递归获得首位后面的字符串组合; *
递归的出口,就是只剩一个字符的时候,
递归的循环过程,就是从每个子串的第二个字符开始依次与第一个字符交换,然后继续处理子串。
 
假如有重复值呢?
如abb aba
全排列中去掉重复的规则:
去重的全排列就是从第一个数字起,每个数分别与它后面非重复出现的数字交换。
若重复 不交换
*/
    //存储全排列结果
    ArrayList<String> list  = new ArrayList<String>();
    public String[] permutation(String s) {
        char[] arr = s.toCharArray();
        permutation_all(0,arr);

        String[] result = new String[list.size()];
        Collections.sort(list);
        for(int i = 0;i < result.length;i++){
            result[i] = list.get(i);
        }

        return result;
    }

    //递归函数 获得首位后面的字符串组合 
    public void permutation_all(int index, char[] arr){
        if(index == arr.length - 1){
            //若是全排列组合数目 count++
            list.add(String.valueOf(arr));
            return;
        }
        //字符与后面每一个字符交换
        Set<Character> set = new HashSet<Character>();
        for(int i = index;i < arr.length;i++){
             //判断是否有重复元素 若当前交换字符和之前固定字符串有相同 不用交换
            if(!set.contains(arr[i])){
                set.add(arr[i]);
                exchange(index,i,arr);
                permutation_all(index + 1,arr);
                exchange(i,index,arr);
            }
        }
    }

    public void exchange(int a,int b,char[] arr){
        char temp = arr[a];
        arr[a] = arr[b];
        arr[b] = temp;
    }
}

剑指offer 字符串排列的解法可用于全排列2 当然也可以用来全排列1

4.全排列2 比全排列多了重复数字

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]

代码如下(参考字符串的排列 这个路径和选择列表都是一个):

import java.util.*;
class Solution {
class Solution {
    List<List<Integer>> res = new LinkedList<>();
    public List<List<Integer>> permuteUnique(int[] nums) {
        // 参考剑指offer 字符串的排列
        permute_all(0,nums);
        return res;
    }

    public void permute_all(int index, int[] nums){
        if(index == nums.length - 1){
            List<Integer> list = new LinkedList<>();
            for(int i:nums){
                list.add(i);
            }
            res.add(list);
            return;
        }

        Set<Integer> set = new HashSet<>();
        for(int i = index;i < nums.length;i++){
            if(!set.contains(nums[i])){
                set.add(nums[i]);
                swap(index,i,nums);
                permute_all(index + 1,nums);
                swap(i,index,nums);
            }
        }
    }

    public void swap(int a,int b,int[] nums){
        int temp = nums[a];
        nums[a] = nums[b];
        nums[b] = temp;
    }
}

5.格雷编码

格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。
示例 1:
输入: 2
输出: [0,1,3,2]
解释:
00 - 0
01 - 1
11 - 3
10 - 2

画图理解:
在这里插入图片描述

代码如下:

class Solution {
    //回溯法 注意用到一个小技巧
    //做选择时 两个连续的数值仅有一个位数的差异 
    List<Integer> res = new LinkedList<>();
    public List<Integer> grayCode(int n) {
        bfs(n,new StringBuffer(),new int[]{0,1});
        return res;
    }

    public void bfs(int n,StringBuffer sb,int[] nums){
        if(sb.length() == n){
            res.add(Integer.valueOf(sb.toString(),2));
            return;
        }

        sb.append(nums[0]);
        bfs(n,sb,new int[]{0,1});
        sb.deleteCharAt(sb.length() - 1);

        sb.append(nums[1]);
        bfs(n,sb,new int[]{1,0});
        sb.deleteCharAt(sb.length() - 1);

    }
}

总结

以上就是做回溯算法的一些题目总结

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值