leetcode之回溯刷题总结1

Posted nuist__NJUPT

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了leetcode之回溯刷题总结1相关的知识,希望对你有一定的参考价值。

leetcode之回溯刷题总结1

回溯法:一种通过探索所有可能的候选解来找出所有的解的算法。如果候选解被确认不是一个解(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化抛弃该解,即回溯并且再次尝试。

1-全排列
题目链接:题目链接戳这里!!!

思路:这个问题可以看作有 n 个排列成一行的空格,我们需要从左往右依此填入题目给定的 n 个数,每个数只能使用一次。那么很直接的可以想到一种穷举的算法,即从左往右每一个位置都依此尝试填入一个数,看能不能填完这 n 个空格,在程序中我们可以用「回溯法」来模拟这个过程。

我们定义递归函数full(k, nums) 表示从左往右填到第k个位置,如果填到最后一个位置,则代表一个排列,存入集合。
如果 k==nums.length,说明我们已经填完了 n个位置(注意下标从 0 开始),找到了一个可行的解,我们将当前排列放入答案数组中,递归结束。
如果 k<nums.length,我们要考虑这第 k 个位置我们要填哪个数。根据题目要求我们肯定不能填已经填过的数,因此很容易想到的一个处理手段是我们定义一个标记数组vis[] 来标记已经填过的数,那么在填第k 个数的时候我们遍历题目给定的 n 个数,如果这个数没有被标记过,我们就尝试填入,并将其标记,继续尝试填下一个位置,即调用函数full。回溯的时候要撤销这一个位置填的数以及标记,并继续尝试其他没被标记过的数。

AC代码如下:

class Solution 
    List<List<Integer>> res = new ArrayList<>() ;
    List<Integer> temp = new ArrayList<>() ;
    public List<List<Integer>> permute(int[] nums) 
        full(0,nums) ;
        return res ;
    
    public void full(int k, int [] nums)
        if(k==nums.length)
            for(int t : nums)
                temp.add(t) ;
            
            res.add(temp) ;
            temp = new ArrayList<>() ;
        
        for(int i=k; i<nums.length; i++)
            swap(nums, i,k) ;
            full(k+1,nums) ;
            swap(nums, i,k) ;
        
    
    public void swap(int [] nums, int i, int k)
        int temp = nums[i] ;
        nums[i] = nums[k] ;
        nums[k] = temp ;
    


当然这样写的话,看着更舒服,提前把数组变成集合。
不过这个效率反而低一点。

class Solution 
    List<List<Integer>> res = new ArrayList<>() ;
    List<Integer> output = new ArrayList<>() ;
    public List<List<Integer>> permute(int[] nums) 
        int n = nums.length ;
        for(int num : nums)
            output.add(num) ;
        
         full(0,output,n) ;
        return res ;
    
    public void full(int k, List<Integer> output, int n)
        if(k==n)
            res.add(new ArrayList<>(output)) ;
        
        for(int i=k; i<n; i++)
            Collections.swap(output, i,k) ;
            full(k+1,output,n) ;
            Collections.swap(output, i,k) ;
        
    

2-全排列II
题目链接:题目链接戳这里!!!

思路:
我们定义递归函数f,表示当前排列为 temp,下一个待填入的位置是第k 个位置(下标从 0 开始)。那么整个递归函数分为两个情况:

如果 k==n,说明我们已经填完了 n 个位置,找到了一个可行的解,我们将temp放入答案数组中,递归结束。
如果 k<n,我们要考虑第k 个位置填哪个数。根据题目要求我们肯定不能填已经填过的数,因此很容易想到的一个处理手段是我们定义一个标记数组 vis 来标记已经填过的数,那么在填第k 个数的时候我们遍历题目给定的 n 个数,如果这个数没有被标记过,我们就尝试填入,并将其标记,继续尝试填下一个位置,即调用函数 f。搜索回溯的时候要撤销该个位置填的数以及标记,并继续尝试其他没被标记过的数。

要解决重复问题,我们只要设定一个规则,保证在填第 k
个数的时候重复数字只会被填入一次即可.

class Solution 
    boolean [] vis ;
    public List<List<Integer>> permuteUnique(int[] nums) 
       List<List<Integer>> res = new ArrayList<>() ;
       List<Integer> temp = new ArrayList<>() ;
       vis = new boolean[nums.length] ;
       Arrays.sort(nums) ;
       f(res,0,temp,nums) ;
       return res ; 
    
    public void f(List<List<Integer>> res, int k, List<Integer> temp, int [] nums)
        if(k==nums.length)
            res.add(new ArrayList<>(temp)) ;
            return ;
        
        for(int i=0; i<nums.length; i++)
            if(vis[i]  || (i-1>=0 && nums[i]==nums[i-1] && !vis[i-1]))
                continue ;
            
            temp.add(nums[i]) ;
            vis[i] = true ;
            f(res, k+1, temp, nums) ;
            vis[i] = false ;
            temp.remove(k) ;
        
    


3-组合种数
题目链接:题目链接戳这里!!!

思路:搜索+剪支+回溯

1、遍历数组中的每一个数字。
2、递归枚举每一个数字可以选多少次,递归过程中维护一个target变量。如果当前数字小于等于target,我们就将其加入我们的路径数组temp中,相应的target减去当前数字的值。也就是说,每选一个分支,就减去所选分支的值。
3、当target == 0时,表示该选择方案是合法的,记录该方案,将其加入res数组中。

class Solution 
     List<List<Integer>> res = new ArrayList<>() ;
    public List<List<Integer>> combinationSum(int[] candidates, int target) 
       
        List<Integer> temp = new ArrayList<>() ;
        backTrace(candidates,target,0, temp) ;
        return res ;
    
    public void backTrace(int [] candidates, int target, int k, List<Integer> temp)
        if(target<0)
            return ;
        
        if(target==0)
            res.add(new ArrayList<>(temp)) ;
            return ;
        
  
        for(int i=k; i<candidates.length; i++)
                temp.add(candidates[i]) ;
                backTrace(candidates,target-candidates[i],i,temp) ;
                temp.remove(temp.size()-1) ;
        
    


4-总和种数II
题目链接:题目链接戳这里!!!

思路:搜索+回溯
搜索所有满足条件的数字,每个数字只用一次,如果在有序情况下出现重复使用的数字,则中止当前递归,否则后面递归得到的回有重复的集合。

class Solution 
    List<List<Integer>> res = new ArrayList<>() ;
    List<Integer> path = new ArrayList<>() ;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) 
        Arrays.sort(candidates) ;
        dfs(candidates,target,0) ;
        return res ;
    
    public void dfs(int [] candidates, int target, int k)
        if(target<0)
            return ;
        
        if(target==0)
            res.add(new ArrayList<>(path)) ;
            return ;
        
        for(int i=k; i<candidates.length; i++)
            if(i-1>=k && candidates[i]==candidates[i-1])
                continue ;
            
            path.add(candidates[i]) ;
            dfs(candidates,target-candidates[i],i+1) ;
            path.remove(path.size()-1) ;
        
    


5-电话号码的字母组合
题目链接:题目链接戳这里!!!

思路:对应数字映射成字符串,搜索每个键对应的值,添加到combination中,直到字符串搜索完,将当前组合combination放入集合combinations,每次搜索完成回溯到原始状态。

class Solution 
    List<String> combinations = new ArrayList<>() ;
    public List<String> letterCombinations(String digits) 
        if(digits.length()==0)
            return combinations ;
        
        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");
        dfs(map,combinations,0,digits, new StringBuilder()) ;
        return combinations ;
    
    public void dfs(Map<Character,String> map, List<String> combinations, int k, String digits, StringBuilder combination)
        if(k==digits.length())
            combinations.add(combination.toString()) ;
        else
            char key = digits.charAt(k) ;
            String s = map.get(key) ;
            for(int i=0; i<s.length(); i++)
                combination.append(s.charAt(i)) ;
                dfs(map,combinations,k+1,digits, combination) ;
                combination.deleteCharAt(k) ;
            
        

    


6-组合
题目链接:题目链接戳这里!!!

思路:搜索并记录路径,每轮搜索完,需要将组合数存入res集合,同时需要回溯到原始状态。

class Solution 
    List<List<Integer>> res = new ArrayList<>() ; 
    List<Integer> path = new ArrayList<>() ;
    public List<List<Integer>> combine(int n, int k) 
        dfs(n,k,1) ;
        return res ;
    
    public void dfs(int n,  int k, int idx)
        if(path.size()==k)
            res.add(new ArrayList<>(path)) ;
            return ;
        
        for(int i=idx; i<=n; i++)
            path.add(i) ;
            dfs(n,k,i+1) ;
            path.remove(path.size()-1) ;
        
    


7-子集
题目链接:题目链接戳这里!!!

思路1:从钱向后遍历数组,每次将数组元素加到已有子集中去形成新的子集,新的子集继续存入res集合,直至遍历结束。

class Solution 
    public List<List<Integer>> subsets(int[] nums) 
        List<List<Integer>> res = new ArrayList<>() ;
        res.add(new ArrayList<>()) ;
        for(int i=0; i<nums.length; i++)
            int len = res.size() ;
            for(int j=0; j<len; j++)
                List<Integer> temp = new ArrayList<>(res.get(j)) ;
                temp.add(nums[i]) ;
                res.add(temp) ;
            
        
        return res ;
    


思路2:搜索+回溯
dfs(cur,nums) 参数表示当前位置是cur,原序列为nums。原序列的每个位置在答案序列中的状态有被选中和不被选中两种,我们用 temp 数组存放已经被选出的数字。
对于cur 位置,我们需要考虑 nums[cur] 取或者不取,如果取,我们需要把nums[cur] 放入一个临时的答案数组中(即上面代码中的 temp),再执行dfs(cur+1,nums),执行结束后

以上是关于leetcode之回溯刷题总结1的主要内容,如果未能解决你的问题,请参考以下文章

leetcode之回溯刷题总结3

leetcode之并查集+记忆化搜索+回溯+最小生成树刷题总结1

leetcode刷题之回溯法

Leetcode之深度遍历递归与回溯法汇总

leetcode刷题总结401-450

Leetcode 动态规划刷题总结