Leetcode 回溯法 (持续更新)

回溯法

  • 子集树 与 排序树
    对于 n = 3 的 0-1 背包问题,可以用一棵完全二叉树表示解空间 当所给的问题是从 n 个元素中找出满足某种性质的子集时,相应的解空间树称为 子集树,这类子集树通常有个 2n 2 n 叶节点,其节点总数为 2n+11 2 n + 1 − 1 ,遍历子集树的任何算法均需要 Ω(2n) Ω ( 2 n ) 的计算时间
    子集树
    对于旅行售货员的解空间树为
    排序树
    当所给的问题是确定 n n 个元素满足某种性质的排列时,相应的解空间树称为 排列树 ,通常有个 2! 叶节点,其节点总数为 2n+11 2 n + 1 − 1 ,遍历子集树的任何算法均需要 Ω(n!) Ω ( n ! ) 的计算时间
  • 子集树模板 用回溯法搜索子集树的一般算法:
void backtrack(int t) {
        if (t > n) Output(x);
        else
            for (int i = 0; i <= 1; i++) { // 0 或 1 选或不选
                x[t] = i;
                if (Constraint(t) && Bound(t)) backtrack(t + 1);
                // Constraint() 约束函数 Bound() 限界函数
            }
    }
  • 排序树模板 用回溯法搜索排列树的一般算法:
void backtrack(int t) {
        if (t > n) Output(x);
        else
            for (int i = t; i <= n; i++) {
                swap(x[t],x[i]); //交换
                if (Constraint(t) && Bound(t)) backtrack(t + 1);
                swap(x[t],x[i]);
            }
    }
题 78. 子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: nums = [1,2,3]
输出:

[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

My Answer:

class Solution {

    List<List<Integer>> result = new ArrayList<>();
    List<Integer> x = new ArrayList<Integer>();

    public List<List<Integer>> subsets(int[] nums) {
        backtrack(1,nums);
        return result;
    }
    public void backtrack(int t, int[] nums){
        //最后一层退出条件
        if(t > nums.length){
            result.add(new ArrayList(x));
        }else{
            //取
            x.add(nums[t-1]);
            backtrack(t+1,nums);
            //不取
            x.remove(x.size()-1);
            backtrack(t+1,nums);
        }
    }
}

题 90. 子集 II

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: [1,2,2]
输出:

[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]

My Answer:

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> x = new ArrayList<Integer>();

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        backtrack(1,nums,true);
        return result;
    }
    //如果与前一个相同,并且前面一个没有取,则不能跳过上一个数,即不取 
    public void backtrack(int t, int[] nums,boolean choose){
        //最后一层退出条件
        if(t > nums.length){
            result.add(new ArrayList(x));
        }else{
            if(t != 1 && nums[t-1] == nums[t-2] && !choose){
                backtrack(t+1,nums,false);
            }else{
                //取
                x.add(nums[t-1]);
                backtrack(t+1,nums,true);
                //不取
                x.remove(x.size()-1);
                backtrack(t+1,nums,false); 
            }
        }
    }
}

题 46.全排序

给定一个没有重复数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3]
输出:

[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

My Answer:

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> x = new ArrayList<>();
    int n;

    public List<List<Integer>> permute(int[] nums) {
        for(int i = 0; i < nums.length; i++){
            x.add(nums[i]);
        }
        n = nums.length;
        backtrack(0);
        return result;
    }

    public void backtrack(int t){
        if(t == n){
            result.add(new ArrayList<>(x));
        }else{
            for(int i = t; i < n; i++){
                swap(x,t,i);
                backtrack(t+1);
                swap(x,t,i);
            }
        }
    }
    public void swap(List<Integer> x, int t, int i){
        int temp = x.get(t);
        x.set(t,x.get(i));
        x.set(i,temp);
    }
}

题 47.全排序II

给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

输入: [1,1,2]
输出:

[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

My Answer:

class Solution {
    Set<List<Integer>> result = new HashSet<>();
    List<Integer> x = new ArrayList<>();
    int n;

    public List<List<Integer>> permuteUnique(int[] nums) {
        for(int i = 0; i < nums.length; i++){
            x.add(nums[i]);
        }
        n = nums.length;
        backtrack(0);
        return new ArrayList<>(result);
    }

    public void backtrack(int t){
        if(t == n){
            result.add(new ArrayList<>(x));
        }else{
            for(int i = t; i < n; i++){
                swap(x,t,i);
                backtrack(t+1);
                swap(x,t,i);
            }
        }
    }
    public void swap(List<Integer> x, int t, int i){
        int temp = x.get(t);
        x.set(t,x.get(i));
        x.set(i,temp);
    }
}

题 51. N皇后

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
这里写图片描述
上图为 8 皇后问题的一种解法。

给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。

每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

输入: 4
输出: [
 [".Q..",  // 解法 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // 解法 2
  "Q...",
  "...Q",
  ".Q.."]
]
解释: 4 皇后问题存在两个不同的解法。

My Answer:

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

class Solution {
    List<List<Integer>> resultInt = new ArrayList<>();
    List<Integer> x;
    int n;
    public List<List<String>> solveNQueens(int n) {
        this.n = n;
        this.x = new ArrayList<>(n);
        backtrack(0);
        return get(resultInt);
    }

    public void backtrack(int t){
        if(t == n){
            resultInt.add(new ArrayList<>(x));
        }else{
            for(int i = 0; i < n; i++){
                x.add(i);
                if(place(t)) backtrack(t+1);
                x.remove(x.size()-1);
            }
        }
    }

    public boolean place(int t){
        for(int i = 0; i < t; i++){
            if((Math.abs(t-i) == Math.abs(x.get(i) - x.get(t))) || (x.get(i) == x.get(t))) return false;
        }
        return true;
    }

    public List<List<String>> get(List<List<Integer>> res){
        List<List<String>> result = new ArrayList<>();
        List<String> list;
        StringBuilder sb = new StringBuilder();

        for(int i = 0; i < n; i++){
            sb.append(".");
        }

        String str = sb.toString();

        for(List<Integer> x : res){
            list = new ArrayList<>();
            for(Integer y : x){
                StringBuilder builder  = new StringBuilder(str);
                builder.setCharAt(y,'Q');
                list.add(builder.toString());
            }
            result.add(list);
        }
        return result;
    }
}

题 784. 字母大小写全排列

给定一个字符串 S,通过将字符串 S 中的每个字母转变大小写,我们可以获得一个新的字符串。返回所有可能得到的字符串集合。

示例:
输入: S = "a1b2"
输出: ["a1b2", "a1B2", "A1b2", "A1B2"]

输入: S = "3z4"
输出: ["3z4", "3Z4"]

输入: S = "12345"
输出: ["12345"]

注意:

  • S 的长度不超过12。
  • S 仅由数字和字母组成。

My Answer:

class Solution {
    List<String> result = new ArrayList<>();

    public List<String> letterCasePermutation(String S) {
        char[] str = S.toCharArray();
        backtrack(0, str);
        return result;
    }

    public void backtrack(int t, char[] str) {
        if (t == str.length) {
            result.add(new String(str));
        } else {
            //不转
            backtrack(t + 1, str);
            //转
            char s = str[t];
            if (!Character.isLetter(s)) {
                return;
            }
            s ^= (1 << 5);
            str[t] = s;
            backtrack(t + 1, str);
        }
    }
}

2018-08-24 更新

题 Alibaba 在线编程. 物流派送员送快递最短路径问题

如下图,某物流派送员p,需要给 a、b、c、d. 4个快递点派送包裹,请问派送员需要选择什么样的路线,才能完成最短路程的派送。假设如图派送员的起点坐标(0,0),派送路线只能沿着图中的方格边行驶,每个小格都是正方形,且边长为1,如p到d的距离就是4。随机输入n个派送点坐标,求输出最短派送路线值。
(从起点开始完成n个点派送并回到起始点的距离)
这里写图片描述

输入示例:
4
2,2
2,4
4,4
7,2

输出:
30

My Answer:

package leetcode;

import java.util.Scanner;

public class Main {
    private static final Point start = new Point(0,0);
    private static Integer res = Integer.MAX_VALUE;

    private static class Point{
        private int x;
        private int y;

        public Point(int x, int y) {
            this.x = x;
            this.y = y;
        }

        public int getDistanceTo(Point p) {
            Integer pathX = Math.abs(p.x - x);
            Integer pathY = Math.abs(p.y - y);
            return pathX + pathY;
        }
    }

    public static Integer backtrack(int t, Point[] points) {
        int n = points.length;
        if (t == n) {
            int sum = start.getDistanceTo(points[0]);
            for (int i = 1; i < n; i++) {
                sum += points[i-1].getDistanceTo(points[i]);
            }
            sum += points[n-1].getDistanceTo(start);
            res = Math.min(res, sum);
        } else {
            for (int i = t; i < n; i++) {
                swap(points, t, i);
                backtrack(t + 1, points);
                swap(points, t, i);
            }
        }
        return res;
    }

    private static void swap(Point[] points, int i, int j) {
        if (i == j) return;
        Point temp = points[i];
        points[i] = points[j];
        points[j] = temp;
    }
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        //read
        int n = Integer.parseInt(sc.nextLine());
        Point[] points = new Point[n];
        for (int i = 0; i < n; i++) {
            String line = sc.nextLine();
            String[] xy = line.split(",");
     //Ps:这是阿里在线编程题,我说怎么做不对,下面 new Point() 的后边分割符下标都写成0了,org
            points[i] = new Point(Integer.parseInt(xy[0]), Integer.parseInt(xy[1]));
        }
        backtrack(0, points);
        System.out.println(res);
    }
}

题 401. 二进制手表

二进制手表顶部有 4 个 LED 代表小时(0-11),底部的 6 个 LED 代表分钟(0-59)。
每个 LED 代表一个 0 或 1,最低位在右侧。
401
例如,上面的二进制手表读取 “3:25”。
给定一个非负整数 n 代表当前 LED 亮着的数量,返回所有可能的时间。

输入: n = 1
返回: ["1:00", "2:00", "4:00", "8:00", "0:01", "0:02", "0:04", "0:08", "0:16", "0:32"]

注意事项:

  • 输出的顺序没有要求。
  • 小时不会以零开头,比如 “01:00” 是不允许的,应为 “1:00”。
  • 分钟必须由两位数组成,可能会以零开头,比如 “10:2” 是无效的,应为 “10:02”。

My Answer:

public class Solution {
    private List<String> res = new ArrayList<>();
    private int n = 10;
    private int[] time = new int[10];
    public List<String> readBinaryWatch(int num) {
        backtrack(0,num);
        return res;
    }

    public void backtrack(int t, int num) {
        if(t == n) {
            if (num == 0) output();
            return;
        }
        if (num > 0) {
            time[t] = 1;
            num--;
            backtrack(t + 1,num);
            time[t] = 0;
            num++;
            backtrack(t + 1,num);
        }else {
            backtrack(n,num);
        }
    }

    public void output() {
        StringBuilder stringBuilder = new StringBuilder();
        int hour = 8 * time[0] + 4 * time[1] + 2 * time[2] + time[3];
        if (hour >= 12) return;
        stringBuilder.append(hour + ":");
        int minute = 32 * time[4] + 16 * time[5] + 8 * time[6] + 4 * time[7] + 2 * time[8] + time[9];
        if (minute > 59) return;
        if (minute < 10) stringBuilder.append("0" + minute);
        else stringBuilder.append(minute);
        String t = stringBuilder.toString();
        res.add(t);
    }
}
题 22. 括号生成

给出 n 代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。
例如,给出 n = 3,生成结果为:

[
  "((()))",
  "(()())",
  "(())()",
  "()(())",
  "()()()"
]

My Answer:

class Solution {
    private static ArrayList<String> res = new ArrayList<>();
    // 基于左括号数时刻大于等于右括号数
    public List<String> generateParenthesis(int n) {
        backtrack(0,0,0,"",2 * n);
        return res;
    }

    public void backtrack(int t, int left, int right, String s, int n) {
        if (t == n) {
            if (left == right) res.add(s);
            return;
        }
        if (left < right) return;
        if (left >= 0) backtrack(t + 1, left + 1, right, s + "(", n);
        if (left > right) backtrack(t + 1, left, right + 1, s + ")", n);
    }
}
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值