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