递归专题【DFS+回溯】指数型+排列型+组合型

总结

  • 指数型:state[]
  • 排列型:state[] used[]
  • 组合型:state[] startIndex(startIndex标记从输入数组的哪一个位置开始遍历)

指数型枚举

每个数字要么选要么不选

方法1:数组

dfs思考递归搜索树,用state[]数组记录状态

import java.util.*;
public class Main{
    
    
    static boolean[] state; //记录选择状态 为true表示选择
    static int n;
    public static void main(String[] args){
    
    
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        state = new boolean[n + 1];
        dfs(1);
    }
    public static void dfs(int index){
    
    
        if(index > n){
    
    
            //输出
            for(int i = 1; i <= n; i++){
    
    
                if(state[i]) System.out.print(i + " ");
            }
            System.out.println();
            return;
        }
        state[index] = true;
        dfs(index + 1);
        state[index] = false;
        dfs(index + 1);
    }
}

方法2:List

import java.util.*;
public class Main{
    
    
    static LinkedList<Integer> path;
    static List<List<Integer>> res;
    static int n;
    public static void main(String[] args){
    
    
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        path = new LinkedList<>();
        res = new ArrayList<>();
        dfs(1);
        for(List<Integer> path : res){
    
    
            for(int i : path) System.out.print(i + " ");
            System.out.println();
        }
    }
    public static void dfs(int index){
    
    
        if(index > n){
    
    
            //输出
            res.add(new ArrayList<>(path));
            return;
        }
        path.add(index);
        dfs(index + 1);
        path.removeLast();
        dfs(index + 1);
    }
}

例题:lc784. 字母大小写全排列

https://leetcode.cn/problems/letter-case-permutation/description/

tips1:

  • 查看ASCII表可以发现ASCII在 65-90 之间是大写字母,97-122 是小写字母,即大小写相差32,可以使用位运算进行快捷转换

  • 位运算进行字母大小写转换arr[index] ^= 32;

tips2:

  • 使用Character.isDigit(arr[index])快捷判断是否是数字

代码

class Solution {
    
    
    List<String> res;
    int len;
    public List<String> letterCasePermutation(String s) {
    
    
        res = new ArrayList<>();
        len = s.length();
        dfs(s.toCharArray(),0);
        return res;
    }
    public void dfs(char[] arr, int index){
    
    
        while(index < len && Character.isDigit(arr[index])) index++;
        //最底端
        if(index == len){
    
    
            res.add(new String(arr));
            return;
        }
        //先变大小写
        arr[index] ^= 32;
        dfs(arr,index + 1);
        //变回来
        arr[index] ^= 32;
        dfs(arr,index + 1);
    }
}

遍历过程

[A1B2,A1b2,a1b2,a1B2]

排列型枚举

排列型

https://leetcode.cn/problems/permutations/description/

方法1:数组

两个数组记录状态

state[]数组记录每个位置的状态

used[]数组记录数值是否用过

import java.util.*;

public class Main{
    
    
    static int n;
    static int[] state; //记录路径
    static boolean[] used; //记录当前数字是否用过
    public static void main(String[] args){
    
    
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        used = new boolean[n + 1];
        state = new int[n + 1];
        dfs(1);//因为答案要求从1开始
    }
    public static void dfs(int index){
    
    
        if(index > n){
    
    
            for(int i = 1; i <= n; i++){
    
    
                System.out.print(state[i] + " ");
            }
            System.out.println();
            return;
        }
        for(int i = 1; i <= n; i++){
    
    
            if(used[i]) continue;
            used[i] = true; //记录已经使用过
            state[index] = i; //记录路径
            dfs(index + 1);
            used[i] = false;
        }
    }
}

方法2:List

import java.util.*;

public class Main{
    
    
    static int n;
    static LinkedList<Integer> path;
    static List<List<Integer>> res;
    static boolean[] used; //记录当前数字是否用过
    public static void main(String[] args){
    
    
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        used = new boolean[n + 1];
        path = new LinkedList<>();
        res = new ArrayList<>();
        // state = new int[n + 1];
        dfs(1);//因为答案要求从1开始
        for(List<Integer> path : res){
    
    
            for(int i : path) System.out.print(i + " ");
            System.out.println();
        }
    }
    public static void dfs(int index){
    
    
        if(index > n){
    
    
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = 1; i <= n; i++){
    
    
            if(used[i]) continue;
            used[i] = true; //记录已经使用过
            path.add(i);
            dfs(index + 1);
            used[i] = false;
            path.removeLast();
        }
    }
}

变形:数组中有重复元素 需要返回结果不能重复

  • 剑指Offer 38. 字符串的排列

https://leetcode.cn/problems/zi-fu-chuan-de-pai-lie-lcof/description/

注意点:排序以及加入树层去重

在这里插入图片描述

Arrays.sort(arr);

if(i - 1 >= 0 && arr[i - 1] == arr[i] && used[i-1] == false) continue;

class Solution {
    
    
    //全排列问题
    char[] arr;
    StringBuilder path = new StringBuilder();
    List<String> res = new ArrayList<>();
    boolean[] used;
    public String[] permutation(String s) {
    
    
        arr = s.toCharArray();
        Arrays.sort(arr);
        used = new boolean[arr.length];
        dfs(0);
        String[] ans = new String[res.size()];
        for(int i = 0; i < res.size(); i++){
    
    
            ans[i] = res.get(i);
        }
        return ans;
    }
    public void dfs(int index){
    
    
        if(index == arr.length){
    
    
            res.add(path.toString());
            return;
        }
        for(int i = 0; i < arr.length; i++){
    
    
            //加入同一层去重 如果前面已经用过则不再使用防止重复
            if(i - 1 >= 0 && arr[i - 1] == arr[i] && used[i-1] == false) continue;
            if(!used[i]){
    
    
                used[i] = true;
                path.append(arr[i]);
                dfs(index + 1);
                path.deleteCharAt(path.length() - 1);
                used[i] = false;
            }
        }
    }
}

lc47. 全排列 II

https://leetcode.cn/problems/permutations-ii/description/

class Solution {
    
    
    List<List<Integer>> res;
    LinkedList<Integer> path;
    boolean[] used;
    int[] nums;
    public List<List<Integer>> permuteUnique(int[] nums) {
    
    
        //去重的核心是排序
        res = new ArrayList<>();
        path = new LinkedList<>();
        used = new boolean[nums.length];
        Arrays.sort(nums);
        this.nums = nums;
        backtracking();
        return res;
    }
    public void backtracking(){
    
    
        if(path.size() == nums.length){
    
    
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = 0; i < nums.length; i++){
    
    
            //nums[i] == nums[i - 1] && used[i - 1] == false表明当前和上一个值一样,但是上一个已经用过
            if(i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) continue;
            if(used[i] == false){
    
    
                used[i] = true;
                path.add(nums[i]);
                backtracking();
                used[i] = false;
                path.removeLast();
            }
        }
    }
}

组合型枚举

https://leetcode.cn/problems/combinations/description/

方法1:数组

state[]数组记录每个位置的状态,即路径

startIndex记录遍历位置

n相当于树的宽度,m相当于树的深度

在这里插入图片描述

import java.util.*;
public class Main{
    
    
    static int[] state; //记录路径,m个位置都放的是什么数
    static int n;
    static int m;
    public static void main(String[] args){
    
    
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        state = new int[m]; 
        dfs(0,1);
    }
    public static void dfs(int index,int startIndex){
    
    
        // 剪枝
        //已经不足m个
        if(index + n - startIndex  + 1 < m) return;
        if(index == m){
    
    
            //叶子结点
            for(int i = 0; i < m; i++) System.out.print(state[i] + " ");
            System.out.println();
            return;
        }
        for(int i = startIndex; i <= n; i++){
    
    
            state[index] = i;
            dfs(index + 1, i + 1);
            state[index] = 0;
        }
    }
}

方法2:List

import java.util.*;
public class Main{
    
    
    // static int[] state; //记录路径,m个位置都放的是什么数
    static int n;
    static int m;
    static LinkedList<Integer> path;
    static List<List<Integer>> res;
    public static void main(String[] args){
    
    
        Scanner sc = new Scanner(System.in);
        n = sc.nextInt();
        m = sc.nextInt();
        path = new LinkedList<>();
        res = new ArrayList<>();
        dfs(0,1);
        for(List<Integer> path : res){
    
    
            for(int i : path) System.out.print(i + " ");
            System.out.println();
        }
    }
    public static void dfs(int index,int startIndex){
    
    
        // 剪枝
        if(index + n - startIndex  + 1 < m) return;
        if(index == m){
    
    
            //叶子结点
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = startIndex; i <= n; i++){
    
    
            path.add(i);   
            dfs(index + 1, i + 1);
            path.removeLast();
        }
    }
}

变形

https://leetcode.cn/problems/combination-sum-iii/description/

找出所有相加之和为 nk 个数的组合,且满足下列条件:

  • 只使用数字1到9
  • 每个数字 最多使用一次

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。

示例 2:

输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
解释:
1 + 2 + 6 = 9
1 + 3 + 5 = 9
2 + 3 + 4 = 9
没有其他符合的组合了。

示例 3:

输入: k = 4, n = 1
输出: []
解释: 不存在有效的组合。
在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。

提示:

  • 2 <= k <= 9
  • 1 <= n <= 60

code

class Solution {
    
    
    List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combinationSum3(int k, int n) {
    
    
        dfs(k,n,1,0);
        return res;
    }
    public void dfs(int k, int target, int startIndex, int sum){
    
    
        // 剪枝
        if(sum > target) return;
        if(path.size() + 9 - startIndex + 1 < k) return;
        // 到底
        if(path.size() == k && sum == target){
    
    
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = startIndex; i <= 9; i++){
    
    
            path.add(i);
            dfs(k,target,i+1,sum+i);
            path.removeLast();
        }
    }
}

带分数

方法1:

暴力全排列+枚举

import java.util.*;
public class Main{
    
    
    static int target; //目标等式的左边
    static int[] nums;  //目标等式的右边
    static boolean[] used; //记录用过的数字
    static int cnt; //记录结果数
    static int N = 10;
    public static void main(String[] args){
    
    
        Scanner sc = new Scanner(System.in);
        target = sc.nextInt();
        nums = new int[N];
        used = new boolean[N];
        dfs(0);
        System.out.println(cnt);
    }
    //根据数组左右返回数值
    public static int cal(int l, int r){
    
    
        int res = 0;
        for(int i = l; i <= r; i++){
    
    
            res = res * 10 + nums[i];
        }
        return res;
    }
    public static void dfs(int index){
    
    
        if(index == 9){
    
    
            //新的全排列
            //开始枚举a b c
            for(int i = 0; i < 7; i++){
    
    
                for(int j = i + 1; j < 8; j++){
    
    
                    int a = cal(0,i);
                    int b = cal(i + 1, j);
                    int c = cal(j + 1, 8);
                    //去掉0
                    if(a == 0 || b ==0 || c == 0) continue;
                    //判断是符合
                    if(c * target == c * a + b) cnt++;
                }
            }
            return;
        }
        //继续搜索
        for(int i = 1; i <= 9; i++){
    
    
            if(!used[i]){
    
    
                used[i] = true;
                nums[index] = i;
                dfs(index + 1);
                used[i] = false;
            }
        }
    }
}

方法2:

剪枝优化,暴力枚举a ,c b自动得出,判断是否满足

import java.util.*;
public class Main{
    
    
    static int target;
    static boolean[] used;
    static int N = 10;
    static int cnt;
    public static void main(String[] args){
    
    
        Scanner sc = new Scanner(System.in);
        target = sc.nextInt();
        used = new boolean[N];
        dfs_a(0,0);
        System.out.println(cnt);
    }
    //index表示当前遍历到第几位
    //a表示当前a的值
    public static void dfs_a(int index, int a){
    
    
        if(index == 8 || a >= target) return;
        if(a > 0) dfs_c(index, a , 0);
        for(int i = 1; i <= 9; i++){
    
    
            if(!used[i]){
    
    
                used[i] = true;
                dfs_a(index + 1, a * 10 + i);
                used[i] = false;
            }
        }
    }
    
    public static void dfs_c(int index, int a, int c){
    
    
        if(index == 9) return;
        if(c > 0 && check(a,c)) cnt++;
        for(int i = 1; i <= 9; i++){
    
    
            if(!used[i]){
    
    
                used[i] = true;
                dfs_c(index + 1, a, c * 10 + i);
                used[i] = false;
            }
        }
    }
    //判断当前的a c是否符合条件
    public static boolean check(int a, int c){
    
    
        int b = (target - a) * c;
        if(a == 0 || b == 0 || c == 0) return false;
        boolean[] backup = used.clone();
        //先判断b中的数字是否有了,没有就标记为true
        while(b > 0){
    
    
            int x = b % 10;
            b /= 10;
            if(x == 0 | backup[x]) return false;
            backup[x] = true;
        }
        //判断used是否全部都用过了
        for(int i = 1; i <= 9; i++){
    
    
            if(!backup[i]) return false;
        }
        return true;
    }
}

array.clone()可以用于复制数组

  • array:要复制的数组
  • 方法返回一个copy数组

猜你喜欢

转载自blog.csdn.net/weixin_51712663/article/details/129676201