leetcode回溯——22/78/77/46

零、回溯算法

回溯算法是在一棵树上的 深度优先遍历(因为要找所有的解,所以需要遍历)
基本思想是,从一条路往前走,能进则进,不能进则退回来,换一条路再试。

一、22括号生成

1.题目

https://leetcode-cn.com/problems/generate-parentheses/
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

2.思路

回溯算法

3.代码

class Solution {
    public List<String> generateParenthesis(int n) {
        //回溯算法
        List<String> ans = new ArrayList<>();
        //stringBuider和String的区别是:
        /*
        参考:https://developer.aliyun.com/article/787097
        1.String是被final修饰的,无法被继承,是不可变的,不能插入等操作。java new一个String的原理是:,JVM会先到字符串池中查找是否有相同的,有就直接引用 ,没有的话就在堆中创建。
        2.String可用+来拼接字符串
        3.StringBuilder和StringBuffer是可变,且StringBuffer是线程安全的,可提供insert、append等操作
        4.StringBuilder转为String可用toString()
        5.String转为StringBuilder可用 
        String str = "abc";
        Stringbuilder stringbuilder = new StringBuilder(str);
        https://blog.csdn.net/rulaixiong/article/details/109857089
		*/
        return generateMain(new StringBuilder() , ans, 0, 0, n);
        //return ans;
    }
    public List<String> generateMain (StringBuilder cur, List<String> ans, int open, int close, int max){
        if (cur.length() == max*2){
            ans.add(cur.toString());
            return ans ;
        }
        if (open<max){
            cur.append("(");
            generateMain(cur, ans, open+1, close,max);
            //deleteCharAt删除索引位置的元素
            cur.deleteCharAt(cur.length()-1);
        }
        if(close < open){
            cur.append(")");
            generateMain(cur, ans, open, close+1,  max);
            cur.deleteCharAt(cur.length()-1);
        }
        return ans;

    }
}

二、78 子集

1.题目

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

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
https://leetcode-cn.com/problems/subsets/

2.思路

dfs思想,穷尽一条路的所有情况后再返回到上一个节点,探索别的路然后穷尽,重复之前的操作直到所有的路都遍历完

3.代码

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

    List<Integer> t = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        dfs(0,nums);
        return ans;
    }

    public void dfs(int cur, int[]nums){
        if (cur == nums.length) {
            ans.add(new ArrayList<Integer>(t));
            return;
        }
        
        t.add(nums[cur]);
        //当前节点的之后节点做深度遍历,此条路上的所有情况都穷尽
        dfs(cur+1,nums);
        //去重,去掉已经遍历的当前节点的情况(剪枝),回溯到上一节点向下深层遍历
        t.remove(t.size() -1);
        dfs(cur+1,nums);

    }
}

三、77组合

1.题目

https://leetcode-cn.com/problems/combinations/
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。

2.思路

回溯+剪枝
https://leetcode-cn.com/problems/combinations/solution/hui-su-suan-fa-jian-zhi-python-dai-ma-java-dai-ma-/
剪枝的思路:搜索起点的上界 + 接下来要选择的元素个数 - 1 = n,所以可以得到搜索起点的上界 = n - (k - path.size()) + 1

3.代码

class Solution {
    public List<List<Integer>> combine(int n, int k) {
        //回溯+剪枝
        //用于保存最后结果
        List<List<Integer>> ans = new ArrayList<>();
        //base case
        if (n<k || k<=0) return ans;
        //start用于保存起始位置,deque用于保存中间变量
        int start = 1;
        Deque<Integer> deque = new ArrayDeque<Integer>();
        dfs(n,k,deque, 1, ans);
        return ans;
    }

    public void dfs(int n, int k , Deque<Integer> deque,int start , List<List<Integer>> ans){
        //终止条件为长度为看
        if (deque.size()==k){
            ans.add(new ArrayList<>(deque));
            return ;
        }
        //剪枝,剪去个数不满足k的数组 start=n-(k-deque.size())+1
        for(int i = start; i<=n-(k-deque.size())+1; i++){
            //先把起始位置加上,然后递归找到该起始位置下符合条件的队列,然后移除,从下一个位置开始重复,直到遍历到上界为止
            deque.addLast(i);
            dfs(n,k,deque,i+1,ans);
            deque.removeLast();
        }

    }
}

四、46全排列

1.题目

https://leetcode-cn.com/problems/permutations/
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

2.思路

回溯+标志已经遍历过的数字
https://leetcode-cn.com/problems/permutations/solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liweiw/

3.代码

class Solution {
    public List<List<Integer>> permute(int[] nums) {
        //回溯算法
        //储存结果
        List<List<Integer>> res = new ArrayList<>();
        int len = nums.length;
        //存放是否遍历过的标志
        boolean[] uesdId = new boolean[len];
        //存放的中间节点
        List<Integer> path = new ArrayList<>();
        bfs(nums, len, 0, res, uesdId, path);
        return res;

    }

    private void bfs(int[] nums, int len, int depth, List<List<Integer>> res, boolean[] uesdId, List<Integer>path){
        
        //base case
        if (depth == len){
            res.add(new ArrayList<>(path));
            return;
        }
           // 在非叶子结点处,产生不同的分支,这一操作的语义是:在还未选择的数中依次选择一个元素作为下一个位置的元素,这显然得通过一个循环实现。
        for(int i =0;i<len;i++){
            if(!uesdId[i]){
                path.add(nums[i]);
                uesdId[i] = true;
                bfs(nums, len, depth+1, res, uesdId, path);
                 // 注意:下面这两行代码发生 「回溯」,回溯发生在从 深层结点 回到 浅层结点 的过程,代码在形式上和递归之前是对称的
                uesdId[i] = false;
                path.remove(path.size()-1);
            }
        }


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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值