代码随想录——回溯

题目来自《代码随想录》

1. 组合问题

77. 组合

https://leetcode-cn.com/problems/combinations/

  1. 不剪枝

注意:
① res.add(path);//!!这里不能直接加path!!因为path会变空!!
② com(n, k, i+1); //这里不能是s+1!得是i+1!!!

	/*
	 * 1. 方法一:不剪枝
	 * 执行用时:14 ms, 在所有 Java 提交中击败了54.71%的用户
	 * 内存消耗:42.1 MB, 在所有 Java 提交中击败了67.75%的用户
	 */
	List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combine(int n, int k) {   	
    	com(n, k, 1);
    	return res;
    }
    
    //n数组总数,k选择的数量,s起始位置
    public void com(int n, int k, int s) {
    	//1.如果path长度已经等于k了,把path放进res
    	if(path.size() == k) {
    		//res.add(path);//!!这里不能直接加path!!因为path会变空!!
    		res.add(new ArrayList<>(path));
    		System.out.println("输出的path为:" + path.toString());
    		return;
    	}
    	//2.如果不够,从s开始,遍历可选择的个数
    	for(int i=s; i<=n; i++) {
    		//3.在path末尾添加答案
    		path.add(i);
    		System.out.println("s:" + s + ", " + path.toString());
    		//4.递归下一个
    		com(n, k, i+1); //这里不能是s+1!得是i+1!!!
    		//5.删除加了的元素
    		path.removeLast();
    	}   
    }
    
    
    //方法二:搜索起点上界 + 还要搜索的数 - 1 = 总数
    // 还要搜索的数 = k - path.size()
    // 总数 = n
    // 搜索起点上界 = 总数 - 还要搜索的数 + 1 = n - (k - path.size()) + 1
    /*
     * 执行用时:1 ms, 在所有 Java 提交中击败了99.99%的用户
     * 内存消耗:42.8 MB, 在所有 Java 提交中击败了20.92%的用户
     */

216. 组合总和 III

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

	/**
	 * 1. 不剪枝
	 * 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
	 * 内存消耗:39 MB, 在所有 Java 提交中击败了36.88%的用户
	 */
	List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
	public List<List<Integer>> combinationSum3(int k, int n) {
		com(k, n, 1, 0);
		return res;
	}
	
	/**
	 * 
	 * @param k 允许的数字个数
	 * @param n 目标和
	 * @param start 起始数字
	 * @param sum 当前和
	 */
	public void com(int k, int n, int start, int sum) {
		//1. 加超了,直接return回溯
		if(sum > n) return;
		//2. 够k个了,size够k了就return,若同时sum满足那res.add
		if(path.size() == k) {
			if( sum == n ) res.add(new ArrayList<>(path));
			return;
		}
		//3. 遍历从start开始
		for( int i=start; i<=9; i++) {
			path.add(i);
			sum+=i;
			com(k, n, i+1, sum);
			path.removeLast();
			sum-=i;
		}
	}
	
	//2. 剪了提升不大
	/*
	 * 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
	 * 内存消耗:39 MB, 在所有 Java 提交中击败了39.26%的用户
	 */

17. 电话号码的字母组合

https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/

	//设置全局列表存储最后的结果
    List<String> list = new ArrayList<>();
    
	public List<String> letterCombinations(String digits) {
		if (digits == null || digits.length() == 0) return list;
		backTracking(digits, numString, 0);
		return list;
	}
	String[] numString = {"", "", "abc", 
			"def", "ghi", "jkl", 
			"mno", "pqrs", "tuv", "wxyz"};
	
	StringBuilder temp = new StringBuilder();
	
	//比如digits如果为"23",num为0,则str表示2对应的 abc
    public void backTracking(String digits, String[] numString, int num) {
    	//遍历全部一次记录一次得到的字符串
        if (num == digits.length()) {
            list.add(temp.toString());
            return;
        }
        //str 表示当前num对应的字符串
        String str = numString[digits.charAt(num) - '0'];
        for (int i = 0; i < str.length(); i++) {
            temp.append(str.charAt(i));
            //c
            backTracking(digits, numString, num + 1);
            //剔除末尾的继续尝试
            temp.deleteCharAt(temp.length() - 1);
        }
    }

39. 组合总和

https://leetcode-cn.com/problems/combination-sum/

	/*
	 * 1. 不剪
	 * 执行用时:3 ms, 在所有 Java 提交中击败了54.50%的用户
	 * 内存消耗:41.9 MB, 在所有 Java 提交中击败了17.80%的用户
	 * 2. 排序,提前判断下一个会不会超
	 * 执行用时:2 ms, 在所有 Java 提交中击败了94.72%的用户
	 * 内存消耗:41.6 MB, 在所有 Java 提交中击败了41.27%的用户
	 */
	List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
	public List<List<Integer>> combinationSum(int[] candidates, int target) {
		Arrays.sort(candidates); // 先进行排序
		com(candidates, target, 0, 0);
		return res;
	}
	
	public void com(int[] candidates, int target, int s, int sum) {
		//1. 加超了,直接return
		//if( sum > target ) return; //排序后不会出现这种情况
		
		//2. 总和够了,path加入res
		if( sum == target ) {
			res.add(new ArrayList<>(path));
			return;
		}
		
		//3. 总和不够,接着加,注意同一个数字可以被无限选取
		for(int i=s; i<candidates.length; i++) {
			if( sum + candidates[i] > target) break;
			path.add(candidates[i]);
			sum += candidates[i];
			com(candidates, target, i, sum);
			sum -= candidates[i];
			path.removeLast();
		}
	}

40. 组合总和 II

跟上面不一样,同一层不能重复,同一枝可以。额外布尔数组存储是否使用过。

	/*
	 * 额外要求:在同一层树上不能重复,但是在同一枝上可以重复
	 */
	List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
	public List<List<Integer>> combinationSum2(int[] candidates, int target) {
		Arrays.sort(candidates); // 先进行排序
		boolean[] flag = new boolean[candidates.length];// 判重布尔数组
		com(candidates, target, 0, 0, flag);
		return res;
	}
	
	public void com(int[] candidates, int target, int s, int sum, boolean[] flag) {
		//1. 加超了,直接return
		//if( sum > target ) return; //排序后不会出现这种情况
		
		//2. 总和够了,path加入res
		if( sum == target ) {
			System.out.println("加入res的为:" + path.toString());
			res.add(new ArrayList<>(path));
			return;
		}
		
		//3. 总和不够,接着加,注意同一个数字可以被无限选取
		for(int i=s; i<candidates.length; i++) {
			show(flag);
			// flag[i - 1] == true,说明同一树枝candidates[i - 1]使用过
	        // flag[i - 1] == false,说明同一树层candidates[i - 1]使用过
	        // 要对同一树层使用过的元素进行跳过
			if (i > 0 && candidates[i] == candidates[i - 1] && !flag[i - 1]) {
				flag[i] = false;//连续多个重复的值,需要手动置false,因为会直接continue,错过下面置false
				System.out.println("被跳过的值为" + candidates[i]);
				continue;
			}			
			flag[i] = true;
			if( sum + candidates[i] > target) break;
			path.add(candidates[i]);
			System.out.println( path.toString());
			sum += candidates[i];
			com(candidates, target, i+1, sum, flag);
			flag[i] = false;
			sum -= candidates[i];
			path.removeLast();
			//System.out.println("删除最后值的path为:" + path.toString());
		}
	}
	
	public void show(boolean[] flag) {
		System.out.println("\n");
		for(boolean f : flag) System.out.print(f + ", ");
		System.out.println("\n");
	}

2. 切割问题

131. 分割回文串

https://leetcode-cn.com/problems/palindrome-partitioning/

	List<List<String>> res = new ArrayList<>();
    LinkedList<String> path = new LinkedList<>();
	public List<List<String>> partition(String s) {
		huisu(s, 0);
		return res;
	}
	
	//回溯代码
	public void huisu(String s, int start) {
		//1. 起始位置大于s大小,分完了,放入res
		System.out.println("start:" + start);
		if(start >= s.length()) {
			System.out.println("加");
			res.add(new LinkedList<String>(path));
            return;
		}
		
		for(int i=start; i<s.length(); i++) {
			if( hui(s, start, i)) {//如果是回文串,就记录
				String str = s.substring(start, i + 1);
				System.out.println(str);
				path.add(str);
			}else continue;
			
			//起始位置后移,保证不重复
            huisu(s, i + 1);
            path.removeLast();
		}
	}
		
	//判断回文串:双指针
	public boolean hui(String s, int start, int end) {
		int l = start;
		int r = end;		
		while(l < r) {
			if(s.charAt(l) != s.charAt(r)) return false;
			l++;
			r--;
		}				
		return true;
	}

93. 复原 IP 地址

这题后面得再做一下
https://leetcode-cn.com/problems/restore-ip-addresses/

	List<String> res = new ArrayList<>();
    Deque<String> path = new ArrayDeque<>(4);
    String s;
    public List<String> restoreIpAddresses(String s) {
        this.s = s;
        dfs(0, 4);
        return res;
    }
    //剔除一些不变量,最后只保留当前索引和剩余段数
    public void dfs(int begin, int reside){
        if(begin == s.length()){
            //当遍历到最后一个字符且剩余段数为0时,将此时的path添加到结果中
            if(reside==0){
                res.add(String.join(".", path));
            }
            return;
        }
        //每段最多只截取3个数
        for(int i=begin; i<begin+3; i++){
            if(i >= s.length())
                break;
            //字符串剩余长度和分段所需长度
            if(s.length()-i > reside*3)
                continue;
            //当截取的字符串满足条件
            if(judgeNumber(s, begin, i)){
                String curS = s.substring(begin, i+1);
                path.addLast(curS);
                dfs(i+1, reside-1);
                path.removeLast();
            }
        }
    }

    public boolean judgeNumber(String s, int left, int right){
        int len = right - left + 1; 
        //当前为0开头的且长度大于1的数字需要剪枝
        if(len>1 && s.charAt(left)=='0')
            return false;
        //将当前截取的字符串转化成数字
        int res = len<=0 ? 0 : Integer.parseInt(s.substring(left, right+1));
        //判断截取到的数字是否符合
        return res>=0 && res<=255;
    }

3. 子集问题

78. 子集

https://leetcode-cn.com/problems/subsets/

	List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
	public List<List<Integer>> subsets(int[] nums) {
		if( nums.length == 0 ) {
			res.add(new ArrayList<>());
			return res;
		}
		dfs(nums, 0);
		return res;
	}
	
	public void dfs(int[] nums, int s) {
		res.add(new ArrayList<>(path));
		for(int i=s; i<nums.length; i++) {
			path.add(nums[i]);
			dfs(nums, i+1);
			path.removeLast();
		}
	}

90. 子集 II

和前面一样维护一个used数组
但是为什么这个不多写一行主动置false就对了呢?
https://leetcode-cn.com/problems/subsets-ii/

	List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    boolean[] flag;
	public List<List<Integer>> subsetsWithDup(int[] nums) {
		if( nums.length == 0 ) {
			res.add(new ArrayList<>());
			return res;
		}
		Arrays.sort(nums);
		flag = new boolean[nums.length];
		dfs(nums, 0);
		return res;
	}
	
	public void dfs(int[] nums, int s) {
		res.add(new ArrayList<>(path));
		// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
        // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
        // 而我们要对同一树层使用过的元素进行跳过
		for(int i=s; i<nums.length; i++) {
			if (i > 0 && nums[i] == nums[i - 1] && !flag[i - 1]){
                continue;
            }
			flag[i] = true;
			path.add(nums[i]);
			dfs(nums, i+1);
			path.removeLast();
			flag[i] = false;
		}
	}

491. 递增子序列

https://leetcode-cn.com/problems/increasing-subsequences/

  1. 不要跟之前一样直接return
  2. 数据范围有限(200个),利用数组去重
	List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
	public List<List<Integer>> findSubsequences(int[] nums) {
		dfs(nums, 0);
		return res;
	}

	public void dfs(int[] nums, int s) {
		//如果path里数量大于一个,就输出到res。但是这会不要return,因为可能path里面还加!
		if( path.size() > 1) {
			res.add(new ArrayList<>(path));
		}
		
		int[] used = new int[201];//每一层一个新的int数组
		for(int i=s; i<nums.length; i++) {
			//如果path不为空 且 比前一个值小 ,就continue
			if(!path.isEmpty() && nums[i] < path.get(path.size() - 1)) {
				continue;
			}
			//这层用过了,也跳过
			if(used[nums[i] + 100] == 1) continue;	
			used[nums[i] + 100] = 1;
            path.add(nums[i]);
			dfs(nums, i+1);
			path.removeLast();
		}
	}

4. 排列问题

46. 全排列

https://leetcode-cn.com/problems/permutations/

	List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    boolean[] used;
	public List<List<Integer>> permute(int[] nums) {
		used = new boolean[nums.length];
		dfs(nums);
		return res;
	}
	
	public void dfs(int[] nums) {
		//如果数量够了就返回
		if(path.size() == nums.length) {
			res.add(new ArrayList<>(path));
			return;
		}
		
		for(int i=0; i<nums.length; i++) {
			if(used[i]) continue;
			used[i] = true;
			path.add(nums[i]);
            dfs(nums);
            path.removeLast();
            used[i] = false;
		}
	}

47. 全排列 II

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

	List<List<Integer>> res = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    boolean[] used;
	public List<List<Integer>> permuteUnique(int[] nums) {
		used = new boolean[nums.length];
		Arrays.sort(nums);
		dfs(nums);
		return res;
	}
	
	public void dfs(int[] nums) {
		//如果数量够了就返回
		if(path.size() == nums.length) {
			res.add(new ArrayList<>(path));
			return;
		}
		
		for(int i=0; i<nums.length; i++) {
			//continue条件
			//1. 同一枝上use过-----used[i]
			//2. 同一层相同数用过-----i>0 && nums[i-1]==nums[i] && !used[i-1]
			if(used[i] || (i>0 && nums[i-1]==nums[i] && !used[i-1])) {
				continue;
			}
			used[i] = true;
			path.add(nums[i]);
            dfs(nums);
            path.removeLast();
            used[i] = false;
		}
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

平什么阿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值