开始学算法 x+1天 ===> 回溯探索第二章!哥布林丛林周围(LeetCode刷题!!!)

跟着carl哥的第X+1天

x天是因为在医院躺了快三周,回到学校开始学习!!!
缺的会开始补回来的,我向天发誓

回溯算法基础模板

void backtracking(参数){

	//终止条件一般是当一个元素符合条件以后,将该元素存入结果集 然后返回到上一层再去向下遍历其他元素
    if(终止条件){
        存放结果;
        return;
    }
    
    //for循环就是用来遍历数组的每一个元素(横向遍历)
    for(选择:本层集合中元素(树中结点孩子的数量就是集合的大小)){
        处理节点;

		//递归向下遍历 在终止条件中判断是否符合条件
        backtracking(路径,选择列表); //递归

		//递归之后返回上一层后,再进行回溯处理,把原有下一层的元素去除 重新遍历该层中的其他元素
		//这便是回溯法
        回溯,撤销处理结果
    }
}

回溯思路(回溯三部曲):

  • 第一步:确定好递归后返回的参数(一般采用全局变量,不需要在回溯方法中传太多参数)

  • 第二步:就是确定好回溯法的终止条件 (在这道题是存储单一答案的数组大小 == k值)

  • 第三步:单层搜索的过程 (每一层我们应该怎么写 只看一层就不会乱 )

    其实回溯法就是暴力搜索,然后运用递归实现n个for循环。横向遍历是数组宽度,纵向遍历是递归。

回溯算法第二层【组合问题+分割问题】

开头第一只远程哥布林:leetcode39. 组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。

示例1:

  • 输入:candidates = [2,3,6,7], target = 7
  • 输出:[ [7], [2,2,3] ]

示例2:

  • 输入:candidates = [2,3,5], target = 8
  • 输出: [ [2,2,2,2], [2,3,3], [3,5] ]

思路(回溯三部曲):
因为candidates中的元素可以重复,所以idx要从本位置开始,不能从下一位开始,所以backTracking()中的i不能加1。 又因为从本元素开始,需要剪枝,防止时间复杂度过高。 然后定义一个sum来判断是否符合条件。


答案:::

public class Test01 {

    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));

    public void main(String[] args) throws IOException {

        //读入一行,并按照空格拆分
        String[] inStr = in.readLine().split(",");
        int[] candidates = new int[inStr.length];
        for (int i = 0; i < inStr.length; i ++) {
            candidates[i] = Integer.parseInt(inStr[i]);
        }

        Integer target = Integer.valueOf(in.readLine());

        List<List<Integer>> result = combinationSum(candidates, target);

        out.write(result.toString());
        out.flush();
        out.close();
    }

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(candidates);
        if (candidates[0] > target) {
            return res;
        }
        backtracking(res, new ArrayList<Integer>(), candidates, target, 0, 0);
        return res;
    }

    public void backtracking(List<List<Integer>> res, List<Integer> path, int[] candidates, Integer target, int sum, int idx) {

        if (sum > target) {
            return;
        }
        if (sum == target) {
            res.add(new ArrayList<Integer>(path));
            return;
        }
        for (int i = idx; i < candidates.length && sum + candidates[i] <= target; i ++) {

            path.add(candidates[i]);
            sum += candidates[i];
            backtracking(res, path, candidates, target, sum, i);
            sum -= candidates[i];
            path.remove(path.size() - 1);
        }

    }
}


又一只远程哥布林:leetcode40.组合总和II

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明: 所有数字(包括目标数)都是正整数。 解集不能包含重复的组合。

示例1 :

输入:  candidates = [10,1,2,7,6,1,5], target = 8
输出: [ [1, 7], [1, 2, 5], [2, 6], [1, 1, 6] ]

示例2 :

输入: candidates = [2,5,2,1,2], target = 5
输出: [[1,2,2],[5]]

思路:这道题需要注意的就是去重,就是每一层上不能使用相同的元素,所以我们需要重新定义一个跟candidates大小相同的数组,用来记录candidates在该位置上是否有使用该元素


答案:

public class Test02 {

    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    static List<List<Integer>> res = new ArrayList<>();
    static List<Integer> path = new ArrayList<>();
    static boolean[] used;
    static int sum = 0;

    public static  void main(String[] args) throws IOException {

        //读入一行,并按照空格拆分
        String[] inStr = in.readLine().split(",");
        int[] candidates = new int[inStr.length];
        for (int i = 0; i < inStr.length; i ++) {
            candidates[i] = Integer.parseInt(inStr[i]);
        }

        Integer target = Integer.valueOf(in.readLine());

        List<List<Integer>> result = combinationSum2(candidates, target);

        out.write(result.toString());
        out.flush();
        out.close();
    }

    public static List<List<Integer>> combinationSum2(int[] candidates, int target) {

        used = new boolean[candidates.length];

        //加标志数组,用来辅助判断同层节点是否已经遍历
        Arrays.fill(used, false);

        //去重需要先排序,意在把相同的值放在一起
        Arrays.sort(candidates);
        backtracking(candidates, target, 0);
        return res;
    }

    public static void backtracking(int[] candidates, Integer target, int idx) {

        if (sum > target) {
            return;
        }
        if (sum == target) {
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = idx; i < candidates.length && sum + candidates[i] <= target; i ++) {

            // candidates[i] == candidates[i - 1]表示元素不等于同树层前一元素 (横向)
            // 因为纵向也有 candidates[i] == candidates[i - 1],故我们需要一个used数组 用来记录同一层每一元素的使用情况
            // 当 used[i - 1] == true 时,(纵向)可以使用,因为是不同层
            // (横向)当到i时 由于used[i-1]等于false 且candidates[i] == candidates[i - 1] 说明现在的i位置元素已经是相同层的重复元素,可以跳过
            if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
                continue;
            }

            path.add(candidates[i]);
            sum += candidates[i];
            used[i] = true;
            backtracking(candidates, target, i + 1);
            used[i] = false;
            sum -= candidates[i];
            path.remove(path.size() - 1);
        }
    }
}

Carl哥设计的used方法很巧妙,你首先要把回溯过程看成一棵树,试着理解
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false)

  • 横向看:当到i时 由于used[i-1]等于false 且candidates[i] == candidates[i - 1] 说明现在的i位置元素已经是相同层的重复元素,可以跳过
  • 纵向:因为used[i - 1] = true 表示的是 上一层的时候即使 用过相同元素,也无所谓,因为是在不同层

一只等级高一点的远程哥布林:leetcode131.分割回文串

给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。

返回 s 所有可能的分割方案。

示例 1:

输入:"aab"
输出:[ ["aa","b"], ["a","a","b"] ]

思路:

  • 回文的意思就是 回文文回
  • 其次你要怎么去切割字符串
  • 最后你该怎么判断是否是回文

答案:

public class Test03 {

    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    static List<List<String>> res = new ArrayList<>();
    static Deque<String> deque = new LinkedList<>();

    public static  void main(String[] args) throws IOException {

        //读入一行,并按照空格拆分
        String str = in.readLine();

        List<List<String>> result = partition(str);

        out.write(result.toString());
        out.flush();
        out.close();
    }

    private static List<List<String>> partition(String s) {

        backtracking(s, 0);
        return res;
    }


    public static void backtracking(String s, int startIndex) {

        if (startIndex >= s.length()) {

            res.add(new ArrayList<>(deque));
        }

        //aab
        for (int i = startIndex; i < s.length(); i ++) {

            if (isPalindrome(s, startIndex, i)) {

                //substring是左闭右开 所以需要i+1
                String str = s.substring(startIndex, i + 1);
                deque.add(str);
            } else {
                continue;
            }

            backtracking(s, i + 1);
            deque.removeLast();
        }
    }

    //判断区间内的字符是不是回文子串(startIndex=0, i = 5)
    private static boolean isPalindrome(String s, int startIndex, int i) {

        int n = i - startIndex + 1;
        char start = s.charAt(startIndex);
        char end = s.charAt(i);

        for (int j = 0; j < n / 2 + 1; j ++) {

            if (start == end) {

                start = s.charAt(startIndex ++);
                end = s.charAt(i --);
            } else {

                return false;
            }
        }

        return true;
    }
}

第一只小Boss火焰哥布林:leetcode93.复原IP地址

给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。

有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。

例如:“0.1.2.201” 和 “192.168.1.1” 是 有效的 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效的 IP 地址。

实例1:

  • 输入:s = “25525511135”
  • 输出:[“255.255.11.135”,“255.255.111.35”]

实例2:

  • 输入:s = “0000”
  • 输出:[“0.0.0.0”]

示例 3:

  • 输入:s = “1111”
  • 输出:[“1.1.1.1”]

示例 4:

  • 输入:s = “010010”
  • 输出:[“0.10.0.10”,“0.100.1.0”]

示例 5:

  • 输入:s = “101023”
  • 输出:[“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“101.0.2.3”]

提示:
0 <= s.length <= 3000
s 仅由数字组成

思路:
主要还是回溯三部曲

  • 返回值
  • 终止条件 因为是IP地址,所以我们可以去判断 . 的个数
  • 单层搜索逻辑 如果回溯你要怎么处理 .
  • 判断IP地址中的每一个 字串是否合法(不要用Integer.parseInt()去直接将字符串转换为数字 因为00会被判断成0)

答案

public class Test04 {

    static BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
    static BufferedWriter out = new BufferedWriter(new OutputStreamWriter(System.out));
    static List<String> res = new ArrayList<>();
    static StringBuffer path = new StringBuffer();

    public static  void main(String[] args) throws IOException {

        //读入一行,并按照空格拆分
        String s = in.readLine();

        List<String> result = restoreIpAddresses(s);

        out.write(result.toString());
        out.flush();
        out.close();
    }

    private static List<String> restoreIpAddresses(String s) {
        if (s.length() > 12) {
            return res;
        }
        backtracking(s, 0, 0);
        return res;
    }


    /**
     *
     * 回溯三部曲:返回值 终止条件 单层搜索逻辑
     */
    public static void backtracking(String s, int startIndex, int pointNum) {

        //终止条件:逗点数量为3时,分割结束
        if (pointNum == 3) {

            // 判断第四段子字符是否合法,如果合法就放进result中
            if (isValid(s, startIndex, s.length() - 1)) {
                res.add(s);
            }
            return;
        }

        //aab
        for (int i = startIndex; i < s.length(); i ++) {

            if (isValid(s, startIndex, i)) {
                //path.append(s.substring(0, i + 1) + "." + s.substring(i + 1));
                s = s.substring(0, i + 1) + "." + s.substring(i + 1);
                pointNum ++;

                //插入逗点之后下一个子串的起始位置为i+2
                backtracking(s, i + 2, pointNum);

                //回溯
                pointNum --;

                //回溯删掉逗点
                s = s.substring(0, i + 1) + s.substring(i + 2);
            } else {
                break;
            }
        }
    }

    //判断区间内的字符是不是IP地址(startIndex=0, i = 5)
    //如果只有一个字符且为0 则直接返回true
    //如果三个字符 则需要小于等于255
    //如果超过三个字符则返回 false
    private static boolean isValid(String s, int start, int end) {

        if (start > end) {
            return false;
        }
        if (s.charAt(start) == '0' && start != end) {
            return false;
        }

        int num = 0;
        for (int i = start; i <= end; i ++) {

            if (s.charAt(i) > '9' || s.charAt(i) < '0') {
                return false;
            }
            num = num * 10 + (s.charAt(i) - '0');
            if (num > 255) {
                return false;
            }
        }

        return true;
    }
}

自我总结

回溯好难啊 一道题做2小时还是错的 虽然想法对了 但是实现一直错误。。
少年仍需努力

朋友仍需努力 一起加油
兄弟萌冲啊!

大概就是这样,大家懂了吗 有什么不懂的评论区评论或者私信我吧!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值