回溯问题解决思路

回溯专场

1、全排列、组合、子集

1、字符串全排列、组合问题

1.1 字典序解法(求下一个字典序数、字符串)字典序、接受重复
//O(n*n!)
    // 利用字典序来解决全排列问题,时间占用最少!
    public static ArrayList<String> permutation_dictory(String str) {
        ArrayList<String> res = new ArrayList<String>();
        if(str.length() == 0) return res;
        char [] array = str.toCharArray();
        Arrays.sort(array);
        String s = new String(array);
        res.add(str);
        while(true){
            s = nextString(s);
            if(!s.equals("-1"))
                res.add(s);
            else
                break;
        }
        return res;
    }

    public static String nextString(String str){
        char [] array = str.toCharArray();
        int length = str.length();
        int i = length-2;
        for(; i>=0 && array[i] >= array[i+1]; i--);
        if(i == -1) return "-1";
        int j = length-1;
        for(; j>=0 && array[j] <= array[i]; j--);
        char tmp = array[i];
        array[i] = array[j];
        array[j] = tmp;
        for(int a=i+1, b=length-1; a<b;a++,b--){
            tmp = array[a];
            array[a] = array[b];
            array[b] = tmp;
        }
        return new String(array);
    }
1.2 递归回溯法(不能去重,也不能保证字典序)

有重复时:

	static HashSet<String> set = new HashSet<String>();
    static HashMap<Character, Integer> map;
    public static ArrayList<String> permutation_dfs(String str) {
        char[] chars = str.toCharArray();
        StringBuilder sb = new StringBuilder();
        int n = chars.length;
        ArrayList<String> list = new ArrayList<String>();
        // 记录每个字符出现的次数: 去重
        map = new HashMap<>();
        for (char c : chars){
            map.put(c, map.getOrDefault(c, 0)+1);
        }
        dfs(list, sb, chars); //针对重复字符出现的回溯方法
        dfs_2(list, sb, chars);//非重复
        System.out.println(list);
        Collections.sort(list);  //字典序输出
        System.out.println(list);
        return list;
    }

	public static void dfs(List<String> list, StringBuilder sb, char[] chars){
        if(sb.length() == chars.length){
            if(!set.contains(sb.toString())){
                set.add(sb.toString());
                list.add(sb.toString());
            }
            return;
        }
        for (char c: chars){
            if(map.get(c) > 0){ // 记录每个字符出现的次数: 去重
                sb.append(c);
                map.put(c, map.getOrDefault(c, 0)-1);
                dfs(list, sb, chars);
                map.put(sb.charAt(sb.length()-1), map.getOrDefault(sb.charAt(sb.length()-1),0)+1);
                sb.deleteCharAt(sb.length()-1);
            }
        }
    }

无重复时:

在这里插入代码片

1、全排列、组合、子集

public static void dfsSubset(int[] nums, List<List<Integer>> list, List<Integer> ans, int index) {
        list.add(new ArrayList<>(ans));
        for (int i = index; i < nums.length; i++) {
            ans.add(nums[i]);
            dfsSubset(nums, list, ans,index+1);
            ans.remove(ans.size()-1);
        }
    }
    public static void dfsArrangement(int[] nums, List<List<Integer>> list, List<Integer> ans){
        if(nums.length == ans.size()){
            list.add(new ArrayList<>(ans));
            return;
        }
        for(int num : nums)
        {
            if(ans.contains(num))
                continue;
            ans.add(num);
            dfsArrangement(nums, list, ans);
            ans.remove(ans.size() - 1);
        }
    }
    public static void dfsCombination(int k, int[] nums, List<List<Integer>> list, List<Integer> ans, int index){
        if(k == ans.size())
            list.add(new ArrayList<>(ans));
        for (int i = index; i < nums.length; i++) {
            ans.add(nums[i]);
            dfsCombination(k, nums, list, ans, i+1);
            ans.remove(ans.size() - 1);
        }
    }

2、跳台阶问题

二级目录

1、由0生成一个数(加一或乘2)

三种方法解决:

def dfs(num):
    if(num == 0):
        return 0
    elif(num % 2 != 0):
        return 1 + dfs(num - 1)
    else:
        return 1 + min(dfs(int(num/2)), dfs(num-1))

def dp(n):
    if n <= 3:
        return n
    else:
        dp = [0 for i in range(n + 1)]
        dp[1], dp[2], dp[3] = 1, 2, 3
        for i in range(4, len(dp)):
            if i % 2 != 0:
                dp[i] = 1 + dp[i-1]
            else:
                dp[i] = 1 + min(dp[i-1], dp[int(i / 2)])
        return dp[n]

def erjinzhi(n):
    m = 0
    for i in range(2, len(bin(n))):
        if bin(n)[i].__eq__(1):
            m += 1
    return m+len(bin(n))-2-1

3、字符串回溯

3.1 生成括号

public static void dfsGenerateParent(int left, int right, List<String> list, int n,StringBuilder sb){
        if(left < 0 || right < 0)
            return;
        if(right < left)
            return;
        if (left == 0 && right == 0){
            list.add(sb.toString());
            return;
        }

        sb.append('(');
        dfsGenerateParent(left - 1, right, list, n, sb);
        sb.deleteCharAt(sb.length()-1);

        sb.append(')');
        dfsGenerateParent(left, right-1, list, n, sb);
        sb.deleteCharAt(sb.length()-1);
    }

3.2 字符串在另一个字符串以子序列形式出现的次数

在这里插入代码片

3.3 大小写字母的全排列

public static void dfs(String s, StringBuilder sb, List<String> list, int index){
        if(index > s.length())
            return;
        if(sb.length() == s.length()){
            list.add(sb.toString());
            return;
        }
        for (int i = index; i < s.length(); i++) {
            char c = s.charAt(i);
            if(Character.isDigit(c)){
                sb.append(c);
            }
            else {
                char[] chars = new char[]{c,Character.isUpperCase(c)? Character.toLowerCase(c) :  Character.toUpperCase(c)};
                for(Character character : chars){
                    sb.append(character);
                    dfs(s, sb, list, i+1);
                    while (sb.length() > 0 && Character.isDigit(s.charAt(sb.length()-1))){
                        sb.deleteCharAt(sb.length()-1);
                    }
                    sb.deleteCharAt(sb.length()-1);
                }
            }
        }
    }

3.4 判断括号合法性

在这里插入代码片

4、八皇后、解数独

在这里插入代码片

https://zhuanlan.zhihu.com/p/388681117

4、N数之和问题(凑零钱、和为target、Nsum)

1、Nsum

1.1 2Sum(数组中只存在一组唯一解)的三种解法。

public static int[] twoSum1(int[] nums,int target){
        int n = nums.length;
        for (int i = 0; i < n; i++) {
            for (int j =i+1; j < n; j++) {
                if(target == nums[i] + nums[j])
                    return new int[]{i, j};
            }
        }
        return new int[]{-1,-1};
    }

    public static int[] twoSum2(int[] nums, int target){
        int n = nums.length;
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            map.put(nums[i], i);
        }

        //以空间换时间,先放进hashmap中,再检索。
        //双层for循环常用优化方法!!!
        for (int i = 0; i < n; i++) {
            int j = target - nums[i];
            if(map.containsKey(j) && map.get(j) != i)
                return new int[]{i, map.get(j)};
        }
        return new int[]{-1, -1};
    }

    public static int[] twoSum3(int[] nums, int target){
        Arrays.sort(nums);
        //先排序,后用双指针
        int n = nums.length;
        int left = 0, right = n-1;
        while (left < right){
            int sum = nums[left] + nums[right];
            if(sum > target)
                right--;
            else if (sum < target)
                left++;
            else if (sum == target)
                return new int[]{left, right};
        }
        return new int[]{-1,-1};
    }

1.2 Nsum(存在多组且重复解得数组,且target > 0)的回溯去重解法

public static void dfs(int N, int target, int[] nums, List<List<Integer>> list, int index, List<Integer> res,HashSet<List<Integer>> set){
        if(target < 0)
            return;
        if(target == 0 && res.size() == N){
            List<Integer> repeat = new ArrayList<>(res);
            Collections.sort(repeat);
            if(!set.contains(repeat)){
                list.add(new ArrayList<>(res));
                set.add(repeat);
            }

            return;
        }
        for (int i = index; i < nums.length; i++) {
            res.add(nums[i]);
            dfs(N, target-nums[i], nums, list, i+1, res,set);
            res.remove(res.size()-1);
        }
    }

1.3 目标和(用正负号组合数组中的数得到target)

在这里插入图片描述

int count = 0;

    public int findTargetSumWays(int[] nums, int target) {
        backtrack(nums, target, 0, 0);
        return count;
    }

    public void backtrack(int[] nums, int target, int index, int sum) {
        if (index == nums.length) {
            if (sum == target) {
                count++;
            }
        } else {
            backtrack(nums, target, index + 1, sum + nums[index]);
            backtrack(nums, target, index + 1, sum - nums[index]);
        }
    }

2、组合为target,求list

1.1 、组合总数为target(数组不重复)

题目描述:在这里插入图片描述

public List<List<Integer>> combinationSum(int[] candidates, int target) {
        int len = candidates.length;
        List<List<Integer>> res = new ArrayList<>();
        if (len == 0) {
            return res;
        }
        //排序是剪枝的前提
        Arrays.sort(candidates);
        List<Integer> path = new ArrayList<>();
        dfs(candidates, 0, target, path, res);
        return res;
    }

    private void dfs(int[] candidates, int index,int target, List<Integer> arr, List<List<Integer>> list) {
        // 由于进入更深层的时候,小于 0 的部分被剪枝,因此递归终止条件值只判断等于 0 的情况
        if (target == 0) {
            list.add(new ArrayList<>(arr));
            return;
        }

        for (int i = index; i < candidates.length; i++) {
            // 重点理解这里剪枝,前提是候选数组已经有序,
            if (target - candidates[i] < 0) {
                break;
            }
            
            arr.add(candidates[i]);
            dfs(candidates, i, target - candidates[i], arr, list);
            arr.remove(arr.size() - 1);
        }
    }

3、组合为target,求最小或最大凑法

1、数目不限量,求凑齐钱的最少钱币数目

public static int coinChange(int[] coins, int amount) {
        if (amount < 1) {
            return 0;
        }
        int n = coinChange(coins, amount, new int[amount]);
        System.out.println(n);
        return n;
    }

    private static int coinChange(int[] coins, int rem, int[] count) {
        if (rem < 0) {
            return -1;
        }
        if (rem == 0) {
            return 0;
        }
        if (count[rem - 1] != 0) {
            return count[rem - 1];
        }
        int min = Integer.MAX_VALUE;
        for (int coin : coins) {
            int res = coinChange(coins, rem - coin, count);
            if (res >= 0 && res < min) {
                min = 1 + res;
            }
        }
        count[rem - 1] = (min == Integer.MAX_VALUE) ? -1 : min;
        return count[rem - 1];
    }

2、数目不限量,求凑齐钱有多少种方法

public int change(int amount, int[] coins) {
        int[] dp = new int[amount + 1];
        dp[0] = 1;
        for (int coin : coins) {
            for (int i = coin; i <= amount; i++) {
                dp[i] += dp[i - coin];
            }
        }
        return dp[amount];
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值