回溯算法
一、回溯算法框架
回溯算法的框架:
result = []
def backtrack(路径, 选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择
backtrack(路径, 选择列表)
撤销选择
二、相关题目
1.全排列
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
代码如下(示例):
class Solution {
//全排列 回溯算法二刷
//记录全部的排列
List<List<Integer>> res = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
// 回溯算法例子
// 定义路径
LinkedList<Integer> track = new LinkedList<>();
backtrack(nums,track);
return res;
}
void backtrack(int[] nums, LinkedList<Integer> track){
// 判断回溯结束条件 如果num值全在track里
if(track.size() == nums.length){
// 注意track是一个对象引用 必须重新new一个
res.add(new LinkedList<Integer>(track));
return;
}
for(int i = 0;i < nums.length;i++){
//如果num[i]值已经被选择了
if(track.contains(nums[i])){
continue;
}
//做选择
track.add(nums[i]);
//进入下一层决策递归
backtrack(nums,track);
//撤销选择
track.removeLast();
}
}
}
2.子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
画图理解 注意选择列表 是用索引区分的
代码:
class Solution {
//回溯法 注意和全排列的回溯有些不一样 要加一个索引参数
List<List<Integer>> res = new LinkedList<>();
List<Integer> list = new LinkedList<>();
public List<List<Integer>> subsets(int[] nums) {
bfs(0,nums);
return res;
}
public void bfs(int j, int[] nums){
res.add(new LinkedList(list));
for(int i = j;i < nums.length;i++){
list.add(nums[i]);
bfs(i + 1,nums);
list.remove(list.size() - 1);
}
}
}
3.字符串排列 剑指offer
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:
输入:s = “abc”
输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]
代码如下(示例):
import java.util.*;
class Solution {
// 理解参考https://www.cnblogs.com/cxjchen/p/3932949.html
//递归实现
/*
固定第一个字符,递归取得首位后面的各种字符串组合;
再把第一个字符与后面每一个字符交换,并同样递归获得首位后面的字符串组合; *
递归的出口,就是只剩一个字符的时候,
递归的循环过程,就是从每个子串的第二个字符开始依次与第一个字符交换,然后继续处理子串。
假如有重复值呢?
如abb aba
全排列中去掉重复的规则:
去重的全排列就是从第一个数字起,每个数分别与它后面非重复出现的数字交换。
若重复 不交换
*/
//存储全排列结果
ArrayList<String> list = new ArrayList<String>();
public String[] permutation(String s) {
char[] arr = s.toCharArray();
permutation_all(0,arr);
String[] result = new String[list.size()];
Collections.sort(list);
for(int i = 0;i < result.length;i++){
result[i] = list.get(i);
}
return result;
}
//递归函数 获得首位后面的字符串组合
public void permutation_all(int index, char[] arr){
if(index == arr.length - 1){
//若是全排列组合数目 count++
list.add(String.valueOf(arr));
return;
}
//字符与后面每一个字符交换
Set<Character> set = new HashSet<Character>();
for(int i = index;i < arr.length;i++){
//判断是否有重复元素 若当前交换字符和之前固定字符串有相同 不用交换
if(!set.contains(arr[i])){
set.add(arr[i]);
exchange(index,i,arr);
permutation_all(index + 1,arr);
exchange(i,index,arr);
}
}
}
public void exchange(int a,int b,char[] arr){
char temp = arr[a];
arr[a] = arr[b];
arr[b] = temp;
}
}
剑指offer 字符串排列的解法可用于全排列2 当然也可以用来全排列1
4.全排列2 比全排列多了重复数字
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
代码如下(参考字符串的排列 这个路径和选择列表都是一个):
import java.util.*;
class Solution {
class Solution {
List<List<Integer>> res = new LinkedList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
// 参考剑指offer 字符串的排列
permute_all(0,nums);
return res;
}
public void permute_all(int index, int[] nums){
if(index == nums.length - 1){
List<Integer> list = new LinkedList<>();
for(int i:nums){
list.add(i);
}
res.add(list);
return;
}
Set<Integer> set = new HashSet<>();
for(int i = index;i < nums.length;i++){
if(!set.contains(nums[i])){
set.add(nums[i]);
swap(index,i,nums);
permute_all(index + 1,nums);
swap(i,index,nums);
}
}
}
public void swap(int a,int b,int[] nums){
int temp = nums[a];
nums[a] = nums[b];
nums[b] = temp;
}
}
5.格雷编码
格雷编码是一个二进制数字系统,在该系统中,两个连续的数值仅有一个位数的差异。
示例 1:
输入: 2
输出: [0,1,3,2]
解释:
00 - 0
01 - 1
11 - 3
10 - 2
画图理解:
代码如下:
class Solution {
//回溯法 注意用到一个小技巧
//做选择时 两个连续的数值仅有一个位数的差异
List<Integer> res = new LinkedList<>();
public List<Integer> grayCode(int n) {
bfs(n,new StringBuffer(),new int[]{0,1});
return res;
}
public void bfs(int n,StringBuffer sb,int[] nums){
if(sb.length() == n){
res.add(Integer.valueOf(sb.toString(),2));
return;
}
sb.append(nums[0]);
bfs(n,sb,new int[]{0,1});
sb.deleteCharAt(sb.length() - 1);
sb.append(nums[1]);
bfs(n,sb,new int[]{1,0});
sb.deleteCharAt(sb.length() - 1);
}
}
总结
以上就是做回溯算法的一些题目总结