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),执行结束后需要对 temp 进行回溯;如果不取,则直接执行 dfs(cur+1,nums)

class Solution {
    List<List<Integer>> res = new ArrayList<>() ;
    List<Integer> temp = new ArrayList<>() ;
    public List<List<Integer>> subsets(int[] nums) {
      dfs(0,nums) ;
      return res ;
    }
    public void dfs(int cur, int [] nums){
        if(cur==nums.length){
            res.add(new ArrayList<>(temp)) ;
            return ;
        }
        temp.add(nums[cur]) ;
        dfs(cur+1,nums) ;
        temp.remove(temp.size()-1) ;
        dfs(cur+1,nums) ;

    }
}

在这里插入图片描述
8-子集II
题目链接:题目链接戳这里!!!

思路:这题与上一题相同,在递归搜索时,若发现没有选择上一个数,且当前数字与上一个数相同,则可以跳过当前生成的子集。

class Solution {
    List<List<Integer>> res = new ArrayList<>() ;
    List<Integer> temp = new ArrayList<>() ;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums) ;
        dfs(false,nums,0) ;
        return res ;
    }
    public void dfs(boolean vis, int [] nums, int cur){
        if(cur==nums.length){
            res.add(new ArrayList<>(temp)) ;
            return ;
        }
        dfs(false,nums,cur+1) ;
    if(!vis && cur>0 && nums[cur]==nums[cur-1]){ //上一个元素未被选取且当前元素等于上一个元素
            return ;
        }
        temp.add(nums[cur]) ;
        dfs(true,nums,cur+1) ;
        temp.remove(temp.size()-1) ;
       
    }
}

在这里插入图片描述
9-组合种数III
题目链接:题目链接戳这里!!!

思路:这里我们要做的是做一个「在 9 个数中选择 k 个数」的组合枚举,对于枚举到的所有组合,判断这个组合内元素之和是否为 n。

class Solution {
    List<List<Integer>> res = new ArrayList<>() ;
    List<Integer> temp = new ArrayList<>() ;
    public List<List<Integer>> combinationSum3(int k, int n) {
        dfs(1,9,k,n) ;
        return res ;
    }
    public void dfs(int cur, int n, int k, int sum){
        if(temp.size()>k || temp.size()+(n-cur+1)<k){
            return ;
        }
        if(temp.size()==k){
            int s = 0 ;
            for(int num : temp){
                s += num ;
            }
            if(s == sum){
                res.add(new ArrayList<>(temp)) ;
                return ;
            }
        }
        temp.add(cur) ;
        dfs(cur+1,n,k,sum) ;
        temp.remove(temp.size()-1) ;
        dfs(cur+1,n,k,sum) ;
    }
}

在这里插入图片描述
10-分割回文串
题目链接:题目链接戳这里!!!

思路1:回溯+记忆化搜索
通过记忆化搜索判断任意的 s[i…j]是否为回文串,如果 s[i…j] 是回文串,那么就将其加入答案数组 temp中,并以j+1 作为新的 i 进行下一层搜索,并在未来的回溯时将 s[i…j] 从 temp 中移除。

class Solution {
    List<List<String>> res = new ArrayList<>() ;
    List<String> temp = new ArrayList<>() ;
    int [][] f ;
    public List<List<String>> partition(String s) {
        int n = s.length() ;
        f = new int [n][n] ;
        dfs(0,s) ;
        return res ;
    }
    public void dfs(int i, String s){
        if(i==s.length()){
            res.add(new ArrayList<>(temp)) ;
            return ;
        }
        for(int j=i; j<s.length(); j++){
            if(isPalindrome(s,i,j)==1){
                temp.add(s.substring(i,j+1)) ;
                dfs(j+1,s) ;
                temp.remove(temp.size()-1) ;
            }
        }
    }
    public int isPalindrome(String s, int i, int j){
        if(f[i][j]!=0){
            return 1 ;
        }
        if(i>=j){
            f[i][j] =  1 ;
        }else if(s.charAt(i)==s.charAt(j)){
            return isPalindrome(s,i+1,j-1) ;
        }else{
            f[i][j] = 0 ;
        }
        return f[i][j] ;
    }
}

在这里插入图片描述

思路2 :动态规划预处理+回溯
用动态规划预处理字符串,判断字符串是否是回文,递推表达式如下,如果 s[i…j] 是回文串,那么就将其加入答案数组 temp中,并以j+1 作为新的 i 进行下一层搜索,并在未来的回溯时将 s[i…j] 从 temp 中移除。
在这里插入图片描述

class Solution {
    List<List<String>> res = new ArrayList<>() ;
    List<String> temp = new ArrayList<>() ;
    boolean [][] f ;
    public List<List<String>> partition(String s) {
        int n = s.length() ;
        f = new boolean [n][n] ;
        for(int i=0; i<n; i++){
            for(int j=0; j<=i; j++){
                f[i][j] = true ;
            }
        }
        for(int i=n-1; i>=0; i--){
            for(int j=i+1; j<n; j++){
                f[i][j] = (s.charAt(i)==s.charAt(j)) && f[i+1][j-1] ;
            }
        }
        dfs(0,s) ;
        return res ;
    }
    public void dfs(int i, String s){
        if(i==s.length()){
            res.add(new ArrayList<>(temp)) ;
            return ;
        }
        for(int j=i; j<s.length(); j++){
            if(f[i][j]){
                temp.add(s.substring(i,j+1)) ;
                dfs(j+1,s) ;
                temp.remove(temp.size()-1) ;
            }
        }
    }
}

在这里插入图片描述

以梦为马的少年,终将拥有抵达宇宙星辰的答案!!!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

nuist__NJUPT

给个鼓励吧,谢谢你

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

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

打赏作者

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

抵扣说明:

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

余额充值