算法总结-回溯法

文章目录

1.组合问题(同一个数字不可重复被选取)

1.组合总和 III
1.答案
java 复制代码
package com.sunxiansheng.arithmetic.day5;

import java.util.LinkedList;
import java.util.List;

/**
 * Description: 216. 组合总和 III
 *
 * @Author sun
 * @Create 2025/1/7 10:01
 * @Version 1.0
 */
public class t216 {

    public List<List<Integer>> combinationSum3(int k, int n) {
        List<List<Integer>> res = new LinkedList<>();
        LinkedList<Integer> list = new LinkedList<>();
        backtrace(1, list, res, 0, n, k);
        return res;
    }

    public static void backtrace(int startIndex, LinkedList<Integer> path, List<List<Integer>> res, int sum, int n, int k) {
        // 退出条件
        if (sum > n || path.size() > k) {
            return;
        }
        // 如果符合条件,就记录结果
        if (sum == n && path.size() == k) {
            res.add(new LinkedList<>(path));
            return;
        } else {
            for (int i = startIndex; i <= 9; i ++ ) {
                // 加入到路径中
                path.add(i);
                // 递归
                backtrace(i + 1, path, res, sum + i, n, k);
                // 回溯
                path.removeLast();
            }
        }
    }
}
2.思路

核心就是参数i从startIndex开始传递状态,如果同一个元素不可重复被选取,就传递i + 1

2.电话号码的字母组合
1.答案
java 复制代码
package com.sunxiansheng.arithmetic.day5;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Description: 17. 电话号码的字母组合
 *
 * @Author sun
 * @Create 2025/1/7 10:19
 * @Version 1.0
 */
public class t17 {

    // 静态初始化映射表
    private static final Map<Character, String> PHONE_MAP = new HashMap<>();

    static {
        PHONE_MAP.put('2', "abc");
        PHONE_MAP.put('3', "def");
        PHONE_MAP.put('4', "ghi");
        PHONE_MAP.put('5', "jkl");
        PHONE_MAP.put('6', "mno");
        PHONE_MAP.put('7', "pqrs");
        PHONE_MAP.put('8', "tuv");
        PHONE_MAP.put('9', "wxyz");
    }

    public List<String> letterCombinations(String digits) {
        List<String> digitsList = new ArrayList<>();
        if (digits == null || digits.length() == 0) {
            return digitsList;
        }
        // 将String转换为list
        for (int i = 0; i < digits.length(); i++) {
            digitsList.add(PHONE_MAP.get(digits.charAt(i)));
        }
        List<String> result = new ArrayList<>();
        backtrace(0, result, new StringBuilder(), digitsList);
        return result;
    }
    
    public static void backtrace(int startIndex, List<String> res, StringBuilder path, List<String> digitsList) {
        if (path.length() == digitsList.size()) {
            // 记录结果
            res.add(path.toString());
            return;
        } else {
            char[] charArray = digitsList.get(startIndex).toCharArray();
            for (int i = 0; i < charArray.length; i++) {
                // 加入路径
                path.append(charArray[i]);
                // 递归
                backtrace(startIndex + 1, res, path, digitsList);
                // 回溯
                path.deleteCharAt(path.length() - 1);
            }
        }
    }
}
2.思路

这里的核心就是startIndex指的是字符串数组的下标

2.组合问题(同一个数字可以无限制被选取)

1.组合总和
1.答案
java 复制代码
package com.sunxiansheng.arithmetic.day5;

import java.util.ArrayList;
import java.util.List;

/**
 * Description: 39. 组合总和
 *
 * @Author sun
 * @Create 2025/1/7 10:35
 * @Version 1.0
 */
public class t39 {

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        backtrace(0, res, new ArrayList<>(), 0, candidates, target);
        return res;
    }

    public static void backtrace(int startIndex,List<List<Integer>> res, List<Integer> path, int sum, int[] candidates, int target) {
        if (sum > target) {
            return;
        }
        if (sum == target) {
            // 记录结果
            res.add(new ArrayList<>(path));
            return;
        } else {
            for (int i = startIndex; i < candidates.length; i++) {
                // 加入路径
                path.add(candidates[i]);
                // 递归
                backtrace(i, res, path, sum + candidates[i], candidates, target);
                // 回溯
                path.remove(path.size() - 1);
            }
        }
    }
}
2.思路

还是从startIndex去循环的传递状态,只不过传递的startIndex变成了i,而不是i+1,这样就表示可以选择重复元素

3.组合问题(组合去重)

1. 组合总和 II
1.答案
java 复制代码
package com.sunxiansheng.arithmetic.day5;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * Description: 40. 组合总和 II
 *
 * @Author sun
 * @Create 2025/1/7 10:51
 * @Version 1.0
 */
public class t40 {

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        // 先排序
        Arrays.sort(candidates);
        List<List<Integer>> res = new LinkedList<>();
        backtrace(0, res, new LinkedList<>(), 0, candidates, target);
        return res;
    }

    public static void backtrace(int startIndex, List<List<Integer>> res, LinkedList<Integer> path, int sum, int[] candidates, int target) {
        if (sum == target) {
            // 记录结果
            res.add(new LinkedList<>(path));
            return;
        } else {
            for (int i = startIndex; i < candidates.length; i++) {
                // 去除同一列的重复
                if (i > startIndex && candidates[i] == candidates[i - 1]) {
                    continue;
                }
                // 加入路径
                path.add(candidates[i]);
                // 递归
                backtrace(i + 1, res, path, sum + candidates[i], candidates, target);
                // 回溯
                path.removeLast();
            }
        }
    }
}
2.思路

核心就在于去重的逻辑,这样就不会有重复数组了

            if (i > startIndex && candidates[i] == candidates[i - 1]) {
                continue;
            }

4.组合问题(扩展)

1.分割回文串
1.答案
java 复制代码
package com.sunxiansheng.arithmetic.day5;

import java.util.LinkedList;
import java.util.List;

/**
 * Description: 131. 分割回文串
 *
 * @Author sun
 * @Create 2025/1/7 11:31
 * @Version 1.0
 */
public class t131 {

    public List<List<String>> partition(String s) {
        List<List<String>> res = new LinkedList<>();
        backtrace(0, res, new LinkedList<>(), s);
        return res;
    }

    public static void backtrace(int startIndex, List<List<String>> res, LinkedList<String> path, String s) {
        if (startIndex == s.length()) {
            res.add(new LinkedList<>(path));
            return;
        } else {
            for (int i = startIndex; i < s.length(); i++) {
                // 只要不是回文就跳过
                String substring = s.substring(startIndex, i + 1);
                if (!palindrome(substring)) {
                    continue;
                }
                // 放到路径中
                path.add(s.substring(startIndex, i + 1));
                // 递归
                backtrace(i + 1, res, path, s);
                // 回溯
                path.removeLast();
            }
        }
    }

    /**
     * 判断回文
     *
     * @param s
     * @return
     */
    private static boolean palindrome(String s) {
        int left = 0;
        int right = s.length() - 1;
        while (left < right) {
            if (s.charAt(left) != s.charAt(right)) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}
2.思路

就是通过startIndex先获取到子串,然后判断如果是回文再加入路径,最后进行递归

2.复原 IP 地址
1.答案
java 复制代码
package com.sunxiansheng.arithmetic.day6;

import java.util.LinkedList;
import java.util.List;

/**
 * Description: 93. 复原 IP 地址
 *
 * @Author sun
 * @Create 2025/1/8 09:29
 * @Version 1.0
 */
public class t93 {

    public List<String> restoreIpAddresses(String s) {
        List<String> res = new LinkedList<>();
        backtrace(0, res, new LinkedList<>(), s);
        return res;
    }
    
    public static void backtrace(int startIndex, List<String> res, LinkedList<String> path, String s) {
        if (path.size() == 4 && startIndex == s.length()) {
            // 构造结果
            StringBuilder sb = new StringBuilder();
            for (String str : path) {
                sb.append(str);
                sb.append(".");
            }
            sb.deleteCharAt(sb.length() - 1);
            res.add(sb.toString());
        } else {
            for (int i = startIndex; i < s.length(); i++) {
                // 如果path的长度已经为4了,那么也不合法
                if (path.size() >= 4) {
                    return;
                }
                // 获取到这个数
                String num = s.substring(startIndex, i + 1);
                // 如果不合法,直接返回
                if (!judgment(num)) {
                    return;
                }
                // 如果合法,则加入路径
                path.addLast(num);
                // 递归
                backtrace(i + 1, res, path, s);
                // 回溯
                path.removeLast();
            }
        }
    }

    /**
     * 判断是否合法
     * @return
     */
    private static boolean judgment(String num) {
        // 1.数字大于255
        if (Integer.parseInt(num) > 255) {
            return false;
        }
        // 2.有前导零
        if (num.length() > 1 && num.charAt(0) == '0') {
            return false;
        }
        // 3.有特殊符号
        for (char c : num.toCharArray()) {
            if (!Character.isDigit(c)) {
                return false;
            }
        }
        return true;
    }
}
2.思路

参数,循环,如果判断合法就传递状态!

5.子集问题

1.子集
1.答案
java 复制代码
package com.sunxiansheng.arithmetic.day6;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * Description: 78. 子集
 *
 * @Author sun
 * @Create 2025/1/8 10:12
 * @Version 1.0
 */
public class t78 {

    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> res = new LinkedList<>();
        // 空集
        res.add(Collections.emptyList());
        backtrace(0, res, new LinkedList<>(), nums);
        return res;
    }

    public static void backtrace(int startIndex, List<List<Integer>> res, LinkedList<Integer> path, int[] nums) {
        if (startIndex == nums.length) {
            return;
        } else {
            for (int i = startIndex; i < nums.length; i++) {
                // 加入路径
                path.add(nums[i]);
                // 加入结果
                res.add(new LinkedList<>(path));
                // 递归传递状态
                backtrace(i + 1, res, path, nums);
                // 回溯
                path.removeLast();
            }
        }
    }
}
2.思路

子集就是每一次加入路径就直接记录结果

2.子集 II
1.答案
java 复制代码
package com.sunxiansheng.arithmetic.day6;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

/**
 * Description: 90. 子集 II
 *
 * @Author sun
 * @Create 2025/1/8 10:20
 * @Version 1.0
 */
public class t90 {

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        // 排序
        Arrays.sort(nums);
        List<List<Integer>> res = new LinkedList<>();
        // 空集
        res.add(Collections.emptyList());
        backtrace(0, res, new LinkedList<>(), nums);
        return res;
    }

    public static void backtrace(int startIndex, List<List<Integer>> res, LinkedList<Integer> path, int[] nums) {
        if (startIndex == nums.length) {
            return;
        } else {
            for (int i = startIndex; i < nums.length; i++) {
                // 去重
                if (i > startIndex && nums[i] == nums[i - 1]) {
                    continue;
                }
                path.add(nums[i]);
                res.add(new LinkedList<>(path));
                backtrace(i + 1, res, path, nums);
                path.removeLast();
            }
        }
    }
}
2.思路

去重问题,首先一定要排序,然后在判断合法性的位置上去重即可

6.全排列问题

1.全排列
1.答案
java 复制代码
package com.sunxiansheng.arithmetic.day6;

import java.util.LinkedList;
import java.util.List;

/**
 * Description: 46. 全排列
 *
 * @Author sun
 * @Create 2025/1/8 10:31
 * @Version 1.0
 */
public class t46 {

    public static List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new LinkedList<>();
        backtrace(new boolean[nums.length], res, new LinkedList<>(), nums);
        return res;
    }

    public static void backtrace(boolean[] visited, List<List<Integer>> res, LinkedList<Integer> path, int[] nums) {
        if (path.size() == nums.length) {
            // 记录结果
            res.add(new LinkedList<>(path));
            return;
        } else {
            for (int i = 0; i < nums.length; i++) {
                // 如果访问过就跳过
                if (visited[i]) {
                    continue;
                }
                // 没有访问过就加入路径并传递状态
                path.add(nums[i]);
                visited[i] = true;
                backtrace(visited, res, path, nums);
                // 回溯
                path.removeLast();
                visited[i] = false;
            }
        }
    }
}
2.思路

使用visited数组来记录访问状态,在判断合法性的位置进行判断即可

2.全排列 II
1.答案
java 复制代码
package com.sunxiansheng.arithmetic.day6;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

/**
 * Description: 47. 全排列 II
 *
 * @Author sun
 * @Create 2025/1/8 10:44
 * @Version 1.0
 */
public class t47 {

    public static List<List<Integer>> permuteUnique(int[] nums) {
        // 排序
        Arrays.sort(nums);
        List<List<Integer>> res = new LinkedList<>();
        backtrace(new boolean[nums.length], res, new LinkedList<>(), nums);
        return res;
    }
    
    public static void backtrace(boolean[] visited, List<List<Integer>> res, LinkedList<Integer> path, int[] nums) {
        if (path.size() == nums.length) {
            res.add(new LinkedList<>(path));
            return;
        } else {
            for (int i = 0; i < nums.length; i++) {
                // 合法性判断
                // 1.访问过了,直接退出
                if (visited[i]) {
                    continue;
                }
                // 2.如果当前元素跟前一个元素一样,并且没有被访问过,也要去重
                if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
                    continue;
                }
                // 递归传递状态
                path.add(nums[i]);
                visited[i] = true;
                backtrace(visited, res, path, nums);
                // 回溯
                path.removeLast();
                visited[i] = false;
            }
        }
    }
}
2.思路

跟全排列一,不同的是在合法性判断里面多加了一个去重条件:如果当前元素跟前一个元素一样,并且没有被访问过,也要去重

7.其他

1.括号生成
1.答案
java 复制代码
package com.sunxiansheng.arithmetic.day6;

import java.util.ArrayList;
import java.util.List;

/**
 * Description: 22. 括号生成
 *
 * @Author sun
 * @Create 2025/1/8 11:06
 * @Version 1.0
 */
public class t22 {

    public List<String> generateParenthesis(int n) {
        List<String> res = new ArrayList<>();
        backtrace(new StringBuilder(), res, 0, 0, n);
        return res;
    }

    public static void backtrace(StringBuilder path, List<String> res, int left, int right, int n) {
        if (path.length() == n * 2) {
            // 记录结果
            res.add(path.toString());
            return;
        } else {
            // 加左括号
            if (left < n) {
                // 递归传递状态
                path.append("(");
                backtrace(path, res, left + 1, right, n);
                // 回溯
                path.deleteCharAt(path.length() - 1);
            }
            // 加右括号
            if (right < left) {
                // 递归传递状态
                path.append(")");
                backtrace(path, res, left, right + 1, n);
                // 回溯
                path.deleteCharAt(path.length() - 1);
            }
        }
    }
}
2.思路

循环,合法性判断:加左括号的条件是left < n,加右括号的条件就是right < left 一旦合法了,就进行递归传递状态然后回溯即可

相关推荐
查理零世7 分钟前
【算法】数论基础——约数个数定理、约数和定理 python
python·算法·数论
汉克老师2 小时前
GESP2024年3月认证C++六级( 第三部分编程题(1)游戏)
c++·学习·算法·游戏·动态规划·gesp6级
闻缺陷则喜何志丹2 小时前
【C++图论】2685. 统计完全连通分量的数量|1769
c++·算法·力扣·图论·数量·完全·连通分量
利刃大大2 小时前
【二叉树深搜】二叉搜索树中第K小的元素 && 二叉树的所有路径
c++·算法·二叉树·深度优先·dfs
CaptainDrake2 小时前
力扣 Hot 100 题解 (js版)更新ing
javascript·算法·leetcode
一缕叶2 小时前
洛谷P9420 [蓝桥杯 2023 国 B] 子 2023 / 双子数
算法·蓝桥杯
甜甜向上呀3 小时前
【数据结构】空间复杂度
数据结构·算法
Great Bruce Young3 小时前
GPS信号生成:C/A码序列生成【MATLAB实现】
算法·matlab·自动驾驶·信息与通信·信号处理
Mryan20053 小时前
LeetCode | 不同路径
数据结构·c++·算法·leetcode
qy发大财3 小时前
验证二叉搜索树(力扣98)
数据结构·算法·leetcode·职场和发展