天天看点

leetcode回溯算法总结

17. 电话号码的字母组合

46. 全排列

47. 全排列 II

39. 组合总和

40. 组合总和 II

491. 递增子序列

77. 组合

78. 子集

90. 子集 II

60. 排列序列

784. 字母大小写全排列

22. 括号生成

51. N 皇后

491. 递增子序列

93. 复原 IP 地址

回溯算法入门级详解 + 练习(持续更新)

文章目录

    • 17. 电话号码的字母组合
    • 46. 全排列
    • 47. 全排列 II
    • 39. 组合总和
    • 40. 组合总和 II
    • 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);
            
        }
    }
}
           

当看了下面的组合总和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” 和 “[email protected]” 是 无效 IP 地址。