leetcode回溯算法总结

17. 电话号码的字母组合
46. 全排列
47. 全排列 II
39. 组合总和
40. 组合总和 II
491. 递增子序列
77. 组合
78. 子集
90. 子集 II
60. 排列序列
784. 字母大小写全排列
22. 括号生成
51. N 皇后
491. 递增子序列
93. 复原 IP 地址
回溯算法入门级详解 + 练习(持续更新)

17. 电话号码的字母组合

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

class Solution {
    public List<String> letterCombinations(String digits) {
        List<String> list=new ArrayList<>();
        StringBuffer strbf=new StringBuffer();
        if(digits==null||digits.length()<=0)
        return list;
        Map<Character,String> map=new HashMap<>();
        map.put('2',"abc");map.put('3',"def");map.put('4',"ghi");
        map.put('5',"jkl");map.put('6',"mno");map.put('7',"pqrs");
        map.put('8',"tuv");map.put('9',"wxyz");
        backTrack(list,digits,map,strbf,0);
        return list;
    }
    private void backTrack(List<String> list,String digits,Map<Character,String> map,StringBuffer strbf,int index){
        if(index==digits.length()){
            list.add(strbf.toString());
            return;
        }
        String str=map.get(digits.charAt(index));
        for(int i=0;i<str.length();i++){
            strbf.append(str.charAt(i));
            backTrack(list,digits,map,strbf,index+1);
            strbf.deleteCharAt(index);
        }
    }
}

46. 全排列

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

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<Integer> list = new ArrayList<>();
        List<List<Integer>> res=new ArrayList<>();
        int n=nums.length;
        boolean[] isused=new boolean[n];
        backTrack(nums,list,res,0,n,isused);
        return res;
    }
    private void backTrack(int[] nums,List<Integer>list,List<List<Integer>> res,int index,int n,boolean[] isused){
        if(index==n){
            res.add(new ArrayList<>(list));
            return;
        }
        for(int i=0;i<n;i++){
            if(isused[i]==false){
                isused[i]=true;
                list.add(nums[i]);
                backTrack(nums,list,res,index+1,n,isused);
                isused[i]=false;
                list.remove(list.size()-1);
            }
        }
    }
}

47. 全排列 II

给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
差解:用了一个Set判断

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<Integer> list = new ArrayList<>();
        List<List<Integer>> res=new ArrayList<>();
        Set<List<Integer>> set=new HashSet<>();
        int n=nums.length;
        boolean[] isused=new boolean[n];
        backTrack(nums,list,set,0,n,isused);
        return new ArrayList(set);
    }
    private void backTrack(int[] nums,List<Integer>list,Set<List<Integer>> res,int index,int n,boolean[] isused){
        if(index==n){
            res.add(new ArrayList<>(list));
            return;
        }
        for(int i=0;i<n;i++){
            if(isused[i]==false){
                isused[i]=true;
                list.add(nums[i]);
                backTrack(nums,list,res,index+1,n,isused);
                isused[i]=false;
                list.remove(list.size()-1);
            }
        }
    }
}

优解:

class Solution {
    public List<List<Integer>> permuteUnique(int[] nums) {
        List<Integer> list = new ArrayList<>();
        List<List<Integer>> res=new ArrayList<>();  
        Arrays.sort(nums);
        int n=nums.length;
        boolean[] isused=new boolean[n];//这个是说当前位置的元素是否被使用过
        backTrack(nums,list,res,0,n,isused);
        return res;
    }
    private void backTrack(int[] nums,List<Integer>list,List<List<Integer>> res,int index,int n,boolean[] isused){
        if(index==n){
            res.add(new ArrayList<>(list));
            return;
        }
        for(int i=0;i<n;i++){
            if(isused[i]||(i>0&&nums[i]==nums[i-1]&&!isused[i-1]))//可能会疑惑这里为什么要加isused[i-1}
            continue;
            isused[i]=true;
            list.add(nums[i]);
            backTrack(nums,list,res,index+1,n,isused);
            isused[i]=false;
            list.remove(list.size()-1);
            
        }
    }
}
!isused[i-1]

当看了下面的组合总和II后可能会疑惑为什么要加这一句话。
这是因为我们这里的插入空隙的选择是从0开始的,而下面的题是从index开始的
即区别以下两种状况:
—— —— ——
1.1(第一个一) 1.2 2
1.2(第二个一) 1.1 2
当从下标0开始选择,选择1.1,然后进入下一个位置(第二个位置)进行选择,而如果不写上isused[i-1]这句话的话,则会无法选择1.2
而(i>0&&nums[i]==nums[i-1)这句话的作用,就是为了让第一个位置无法选择1.2.
意思是,前一句是为了让第一个位置无法选择1.2,后一句是为了让第二个位置可以选择1.2.可以与组合综合II对比一下。

39. 组合总和

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

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

说明:

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

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<Integer> list=new ArrayList<>();
        List<List<Integer>> res=new ArrayList<>();
        backTack(candidates,target,0,list,res,0);
        return res;
    }
    private void backTack(int[] candidates,int target,int sum,List<Integer> list,List<List<Integer>> res,int index){
        if(sum==target){
            res.add(new ArrayList<>(list));
            return;
        }
        if(sum>target)
        return;
        for(int i=index;i<candidates.length;i++){
            list.add(candidates[i]);
            backTack(candidates,target,sum+candidates[i],list,res,i);
            list.remove(list.size()-1);
        }
    }

}

40. 组合总和 II

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

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

说明:

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

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<Integer> list=new ArrayList<>();
        List<List<Integer>> res=new ArrayList<>();
        backTack(candidates,target,list,res,0,0);
        return res;
    }
    private void backTack(int[] candidates,int target,List<Integer>list,List<List<Integer>> res,int sum,int index){
        if(sum==target){
            res.add(new ArrayList<>(list));
            return;
        }
        if(sum>target||index==candidates.length){
            return;
        }
        for(int i=index;i<candidates.length;i++){
            list.add(candidates[i]);
            backTack(candidates,target,list,res,sum+candidates[i],i+1);
            list.remove(list.size()-1);
        }
    }
}

无重复解:

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<Integer> list=new ArrayList<>();
        List<List<Integer>> res=new ArrayList<>();
        Arrays.sort(candidates);//排序
        backTack(candidates,target,list,res,0,0);
        return res;
    }
    private void backTack(int[] candidates,int target,List<Integer>list,List<List<Integer>> res,int sum,int index){
        if(sum==target){
            res.add(new ArrayList<>(list));
            return;
        }
        if(sum>target||index==candidates.length){
            return;
        }
        for(int i=index;i<candidates.length;i++){
            if(i>index&&candidates[i]==candidates[i-1])//判断
            continue;
            list.add(candidates[i]);
            backTack(candidates,target,list,res,sum+candidates[i],i+1);//当index=i+1时,是不允许当前元素再次使用的,而当index=i时,是允许当前元素再次使用的
            list.remove(list.size()-1);
        }
    }
}
 if(i>index&&candidates[i]==candidates[i-1])//判断
 continue;

可以将这一句理解为:
插入当前位置的时候,只能插一次

因为回溯算法和空白位置插入是差不多的。
如: —— —— —— (1,2,2)在这三个位置上进行计算求和
第一个位置可以是1,第二个位置可以是2.1(第一个二),也可以是2.2(第二个二)
在这种情况下就会产生:
1,2.1,2.2
1,2.2,2.1
这样的重复,但,加入我们在第二个位置加上限定:不允许插入和前一个元素相同的元素,那么,2.2就无法插入到第二个位置了。
就只会有1,2.1,2.2这一种选择。

77. 组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

class Solution {
    public List<List<Integer>> combine(int n, int k) {
        List<Integer> list=new ArrayList<>();
        List<List<Integer>> res=new ArrayList<>();
        backTack(n,k,list,res,1,0);
        return res;
    }
    private void backTack(int n,int k,List<Integer> list, List<List<Integer>> res,int begin,int len){
        if(len==k){
            res.add(new ArrayList<>(list));
            return;
        }
        for(int i=begin;i<=n;i++){
            list.add(i);
            backTack(n,k,list,res,i+1,len+1);
            list.remove(list.size()-1);
        }
    }
}

78. 子集

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<Integer> list=new ArrayList<>();
        List<List<Integer>> res=new ArrayList<>();
        backTack(nums,0,0,list,res);
        return res;
    }
    private void backTack(int[] nums,int index,int len,List<Integer> list,List<List<Integer>> res){
        if(len>nums.length)
        return;
        res.add(new ArrayList<>(list));
        for(int i=index;i<nums.length;i++){
            list.add(nums[i]);
            backTack(nums,i+1,len+1,list,res);
            list.remove(list.size()-1);
        }
        
    }
}

90. 子集 II

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

解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        List<Integer> list=new ArrayList<>();
        List<List<Integer>> res=new ArrayList<>();
        boolean[] isused=new boolean[nums.length];
        Arrays.sort(nums);
        backTack(nums,list,res,0,0,isused);
        return res;
    }
    private void backTack(int[] nums,List<Integer> list, List<List<Integer>> res,int index,int len,boolean[] isused){
        if(len>nums.length)
        return;
        res.add(new ArrayList<>(list));
        for(int i=index;i<nums.length;i++){
            if(i>0&&nums[i]==nums[i-1]&&!isused[i-1])
            continue;
            isused[i]=true;
            list.add(nums[i]);
            backTack(nums,list,res,i+1,len+1,isused);
            list.remove(list.size()-1);
            isused[i]=false;
        }
    }
}

60. 排列序列

给出集合 [1,2,3,…,n],其所有元素共有 n! 种排列。

按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:

“123”
“132”
“213”
“231”
“312”
“321”
给定 n 和 k,返回第 k 个排列。

class Solution {
    String str;
    int sum=0;
    public String getPermutation(int n, int k) {
        boolean[] isused=new boolean[n];
        backTrack(n,k,0,isused,new StringBuffer());
        return str;
    }
    private void backTrack(int n,int k,int index,boolean[] isused,StringBuffer sb){
        if(sum==k)
        return;
        if(index==n){
            sum++;
            if(sum==k){
                str=sb.toString();
                return;
            }
        }
        for(int i=0;i<n;i++){
            if(isused[i])
            continue;
            isused[i]=true;
            sb.append(Integer.toString(i+1));
            backTrack(n,k,index+1,isused,sb);
            sb.deleteCharAt(sb.length()-1);
            isused[i]=false;
        }

    }
}

784. 字母大小写全排列

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

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

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

输入:S = “12345”
输出:[“12345”]

class Solution {
    public List<String> letterCasePermutation(String S) {
        List<String> list=new ArrayList<>();
        backTrack(S,0,new StringBuffer(),list);
        return list;

    }
    private void backTrack(String s,int index,StringBuffer sb,List<String> list){
        if(index==s.length()){
            list.add(sb.toString());
            return;
        }
        if(s.charAt(index)>='0'&&s.charAt(index)<='9'){
            sb.append(s.charAt(index));
            backTrack(s,index+1,sb,list);
            sb.deleteCharAt(sb.length()-1);
            return;
        }

        char c=helper(s.charAt(index));
        sb.append(c);
        backTrack(s,index+1,sb,list);
        sb.deleteCharAt(sb.length()-1);

        sb.append(s.charAt(index));
        backTrack(s,index+1,sb,list);
        sb.deleteCharAt(sb.length()-1);
    }
    private char helper(char c){
        if(c>='A'&&c<='Z')
        return Character.toLowerCase(c);
        else
        return Character.toUpperCase(c);
    }
}

22. 括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:

输入:n = 1
输出:["()"]

class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> list=new ArrayList<>();
        backTrack(n,new StringBuffer(),list,0,0);
        return list;
    }
    private void backTrack(int n,StringBuffer sb,List<String> list,int left,int right){
        if(left>n||right>n||left<right)
        return;
        if(left==n&&right==n){
            list.add(sb.toString());
            return;
        }
        sb.append("(");
        backTrack(n,sb,list,left+1,right);
        sb.deleteCharAt(sb.length()-1);
        sb.append(")");
        backTrack(n,sb,list,left,right+1);
        sb.deleteCharAt(sb.length()-1);
    }
}

51. N 皇后

n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

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

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

491. 递增子序列

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是 2 。

示例:

输入:[4, 6, 7, 7]
输出:[[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]

93. 复原 IP 地址

给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 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 地址。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值