算法总结+力扣hot100|回溯

回溯二刷:

组合总和nums无重复元素---同一层不可重复取---纵向可以重复取

nums无重复元素--所以横向不用去重

但纵向重复取?---单层循环中,i不能从0开始。否则:(2,2,3)与(3,2,2)

不能从i+1开始(下一层递归传入的),否则取不到重复的

组合总和iinums有重复元素---同一层不可重复取---纵向可以重复取(因为有重复元素)

纵向可以重复取(因为有重复元素)?----从i+1开始(下一层递归传入的)

横向去重---排序,相同的跳过

组合总和ii

回溯三刷:

组合剪枝问题:

电话号码的组合:字符串的处理问题

递增子序列:不排列的情况下,使用哈希对同一层去重

一:回溯算法

纯暴力搜素算法

为什么还要用?能搜出来就不错

为什么不用for嵌套?n层排列组合--n个for嵌套,代码很繁琐

而且有的时候n是变长,无法用for嵌套

这个时候只能用回溯算法暴力解出来

回溯本质还是用了递归函数--10个数字排列组合-要找第10个数字,先找到前9个数字的(10的排列组合)

带你学透回溯算法(理论篇)| 回溯法精讲!_哔哩哔哩_bilibili

1.解决的问题

 无法用for嵌套,只能回溯法暴力搜素出来

2.回溯模板

为什么要有回溯操作?

如下:我们在1后面加了2,要回溯(撤回加2这个操作),又变回1,

后面加3,就可以实现13, 否则会一直加下去,变成123

二.组合问题

77题

for循环部分为单层搜素逻辑,后面的剪枝部分也只优化这部分

无剪枝下,单层循环里,i<=n

剪枝下,i<=n-(k-path.size)+1

组合问题有重复的,1234 与 4321 是相同,剪枝掉4321

3.77组合

class Solution {
    List<List<Integer>> ans = new ArrayList<>();//返回结果
    private LinkedList<Integer> path=new LinkedList<>();//路径
    
    public List<List<Integer>> combine(int n, int k) {
        backTrace(n, k, 1);
        return ans;
    }

    public void backTrace(int n,int k,int startIndex){
        //终止条件
        if (k==path.size()) {
            ans.add(new ArrayList<>(path));
            return ;
        }
        //单层处理, i <= n-(k-path.size())+1剪枝操作
        for (int i = startIndex; i <= n-(k-path.size())+1; i++) {
            path.addLast(i);//添加元素
            backTrace(n, k, i+1);//递归
            path.removeLast();
            //回溯
        }
    }
}

4.组合总和iii

纵:递归次数

横:单层里的for循环次数

剪枝:

class Solution {
    List<List<Integer>> ans =new ArrayList<>();
    ArrayList<Integer> path =new ArrayList<>();

    public List<List<Integer>> combinationSum3(int k, int n) {
        backTracking(n, k, 1, 0);
        return ans;
    }

    public void backTracking(int sum,int k,int index, int cur_sum){
        //两个终止条件
        if (cur_sum>sum)  return;
        if (k==path.size()) {  
            if (cur_sum==sum) {
                ans.add(new ArrayList<>(path));
                return;
            }
            return;
        }

        //单层逻辑
        for (int i = index; i <= 9-(k-path.size())+1; i++) {
            path.add(i);
            cur_sum+=i;
            backTracking(sum, k, i+1, cur_sum);
            cur_sum-=i;
            path.removeLast();
        }
    }
}

5.电话号码的字母组合

class Solution {
    List<String> ans=new ArrayList<>();//返回结果
    StringBuilder path =new StringBuilder();//保存每次回溯的最终结果

    public List<String> letterCombinations(String digits) {
        String [] numStrings={"","","abc","def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
        if (digits.length()==0||digits==null) {
            return ans;
        }
        backTracking(digits, 0, numStrings);
        return ans;
    }

    public void backTracking(String digits, int index,String [] numStrings){
        if (index==digits.length()) {
            ans.add(path.toString());
            return ;
        }
        //处理digits,index层数,纵向,i横向 
        String curStr=numStrings[digits.toCharArray()[index]-'0'];
        // 单层循环i : 0--> curStr.length(),该层的curStr的字符都遍历
        for (int i = 0; i < curStr.length(); i++) {
            path.append(curStr.charAt(i));//处理节点
            backTracking(digits, index+1, numStrings);//递归
            path.deleteCharAt(path.length()-1);//回溯,删除最后一个
        }
        
    }
}

6.组合总和

有重复的,没剪枝

class Solution {
    List<List<Integer>> ans =new ArrayList<>();
    ArrayList<Integer> path =new ArrayList<>();

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        backTracking(target, candidates, 0,0);
        return ans;
    }

    public void backTracking(int target,int[] candidates,int cur_sum,int index){
        if (cur_sum>target) return ;//终止条件
        if (cur_sum==target) {
            ans.add(new ArrayList<>(path));
            return ;
        }

        for (int i = index; i < candidates.length; i++) {
            //从0开始有重复,前面的重复了
            path.add(candidates[i]);
            cur_sum+=candidates[i];
            // 关键点:不用i+1了,表示可以重复读取当前的数
            // 递归下去可以重复取后面的数
            backTracking(target, candidates, cur_sum,i);
            cur_sum-=candidates[i];
            path.removeLast();
        }
    }
}

7.组合总和ii--树去重

index是纵向的指标

i是横向的指标

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    ArrayList<Integer> path = new ArrayList<>();

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        // 为了将重复的数字都放到一起,所以先进行排序
        Arrays.sort(candidates);
        backTracing(target, candidates, 0, 0);
        return ans;
    }

    public void backTracing(int target, int[] candidates, int cur_sum, int index) {
        if (target < cur_sum)
            return;// 终止条件
        if (target == cur_sum) {
            ans.add(new ArrayList<>(path));
            return;
        }

        for (int i = index; i < candidates.length; i++) {
            // 正确剔除重复解的办法
            // 跳过同一树层使用过的元素
            if (i > index && candidates[i] == candidates[i - 1]) {
                continue;
            }
            path.add(candidates[i]);
            cur_sum += candidates[i];
            backTracing(target, candidates, cur_sum, i + 1);
            cur_sum -= candidates[i];
            path.removeLast();
        }
    }
}

三:分割问题

8.131. 分割回文串

分割思想:

回溯实现分割代码:

终止条件:

能走到最后的都是符合的,不符合的提前停止了

单层逻辑:

是回文子串才切割

判断回文子串:

class Solution {
    List<List<String>> ans =new ArrayList<>();
    ArrayList<String> path =new ArrayList<>();

    public List<List<String>> partition(String s) {
        backTracking(s, 0);
        return ans;
    }

    public void backTracking(String s,int index_start){
        if (index_start>=s.length()) {
            ans.add(new ArrayList<>(path));
            return;
        }

        for (int i = index_start; i < s.length(); i++) {
            if (isPali(s, index_start, i)) {//是回文,切割
                // 获取[startIndex,i]在s中的子串
                String str =s.substring(index_start,i+1 );
                path.add(str);
            }else{continue;} //不是,直接跳过,不切割
            backTracking(s, i+1);
            path.removeLast();
        }

    }

    public boolean isPali(String s,int start,int end){
        for (int i = start,j=end; i < j; i++,j--) {
            if (s.charAt(i)!=s.charAt(j)) return false;
        }
        return true;
    }
}

9.93复原 IP 地址

代码随想录 (programmercarl.com)

四:子集

图中红线部分,可以看出遍历这个树的时候,把所有节点都记录下来,就是要求的子集集合

每一层都有保存结果

之前只保存最后一层的结果

存放结果在最上面,一进入下一层,九巴上一层收集到的结果存起来

9.78. 子集

class Solution {
    List<List<Integer>> ans = new ArrayList<>();// 存放符合条件结果的集合
    LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果
    public List<List<Integer>> subsets(int[] nums) {
        backTracking(nums, 0);
        return ans;
    }

    public void backTracking(int[] nums,int start){
        ans.add(new ArrayList<>(path));
        if(start>=nums.length) return ;//终止条件
        for (int i = start; i < nums.length; i++) {
            path.add(nums[i]);
            backTracking(nums, i+1);
            path.removeLast();
        }
    }
}

10.90. 子集 II--树去重

不能有[1,2,1],[1,1,2].

树去重

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    ArrayList<Integer> path = new ArrayList<>();

    public List<List<Integer>> subsetsWithDup(int[] nums) {
        if (nums.length==0) {
            ans.add(path);
            return ans;
        }
        //排序,重复数字放一块
        Arrays.sort(nums);
        backTracking(nums, 0);
        return ans ;
    }

    public void backTracking(int [] nums, int start){
        ans.add(new ArrayList<>(path));//每一层的节点结果都保存
        if (start>=nums.length) return;
        for (int i = start; i < nums.length; i++) {
            if (i>start&&nums[i]==nums[i-1]) {
                continue;  //去重
            }
            path.add(nums[i]);
            backTracking(nums, i+1);
            path.removeLast();
        }
    }
}

11.递增子序列

错误:

关于树去重:

纵向(树枝)上是可以取重复的

横向同一层级不可以

回溯算法精讲,树层去重与树枝去重 | LeetCode:491.递增子序列_哔哩哔哩_bilibili

错误2:if (i>start&&nums[i]==nums[i-1]) {
                continue;  //去重,横向
            }

横向去重这块:,因为num里面是无序的,比较的不是前后两个元素,而是当前num[i]是否出现在path里

所以这里需要一个map映射,用于存储path里添加的元素,用map去维护去重

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    ArrayList<Integer> path = new ArrayList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
        backTracking(nums, 0);
        return ans ;
    }

    public void backTracking(int [] nums, int start){
        //ans.add(new ArrayList<>(path));//每一层的节点结果都保
        //类似分割,不保存长为1和空的
        if(path.size() > 1) ans.add(new ArrayList<>(path));
        if (start>=nums.length) return;
        for (int i = start; i < nums.length; i++) {
            if (i>start&&nums[i]==nums[i-1]) {
                continue;  //去重,横向
            }
            if (path.size()!=0&&nums[i]<path.getLast()) {
                continue;  //,纵向,不满足递增,结束
            }
            path.add(nums[i]);
            backTracking(nums, i+1);
            path.removeLast();
        }
    }
}

 正确:用map去维护去重

替换:

五:排列

1.46. 全排列

纵向去重问题

排列,有序

全排列:只保存最后一层

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> permute(int[] nums) {
        
        backTracking(nums, 0);
        return ans ;
    }

    public void backTracking(int [] nums, int start){
        //只保存最后一层
        if(nums.length==path.size()){
            ans.add(new ArrayList<>(path));
            return;
        } 
        // i从0开始,[1,2]  [2,1]都遍历到
        for (int i = 0; i < nums.length; i++) {
            if (path.size()!=0&&path.contains(nums[i])) {
                continue;  //纵向去重
            }
            
            path.add(nums[i]);
            backTracking(nums, i+1);
            path.removeLast();
        }
    }
}

2.47. 全排列 II

横向检查不能重复

纵向比较复杂,

力扣:括号生成

class Solution {
    // 全局变量,减少函数参数
    List<String> list = new ArrayList<>();
    public List<String> generateParenthesis(int n) {
        // String指向内容不可变,但是其引用可以变,所以我们用String
        // 不要用StringBuilder,虽然拼接快,但是回溯时删除效率低
        String sb = "";
        helper(sb,n,0,0);
        return list;
    }
    // l,r用来统计左右括号的数量
    public void helper(String sb,int n,int l,int r){
         if(l==n && r==n) {
             list.add(sb);            
             return;
         }
         if(l>n||r>n||r>l) return;    
            //  先左括号
            helper(sb+"(",n,l+1,r); 
            //  后右括号
            helper(sb+")",n,l,r+1); 
            return;                       
    }
}

力扣:单词搜素--类似岛屿搜素

79. 单词搜索 - 力扣(LeetCode)

dfs这个函数,因为每一个点我们都可以往他的4个方向查找,所以我们可以把它想象为一棵4叉树,就是每个节点有4个子节点,而树的遍历我们最容易想到的就是递归,我们来大概看一下

力扣:八皇后

算法转开发day7 |JAVA基础_JAVA小实践

之前总结的递归和回溯

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值