回溯算法
组合(剪枝)
特别注意剪枝,k-temp.size()表示剩余的需要组合的个数 n-(k-temp.size())表示i至多到哪个位置
class Solution {
List<List<Integer>> result=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
backTracking(n,k,1);
return result;
}
public void backTracking(int n,int k,int startIndex){
//终止条件
if (temp.size()==k){
result.add(new ArrayList<>(temp));
return;
}
for (int i = startIndex; i <= n-(k-temp.size())+1; i++) {
//单层逻辑
temp.add(i);
backTracking(n,k,i+1);
//回溯
temp.remove(temp.size()-1);
}
}
}
组合III
循环条件不为i<n n是总和,i范围是1-9,当n大于9时不值当
class Solution {
List<List<Integer>> result=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backTracking(n,k,1,0);
return result;
}
public void backTracking(int n,int k,int startIndex,int curSum){
//终止条件
if (temp.size()==k){
if (curSum==n){
result.add(new ArrayList<>(temp));
}
return;
}
for (int i = startIndex; i <= 9-(k-temp.size())+1; i++) {
temp.add(i);
backTracking(n,k,i+1,curSum+i);
//curSum不用回溯 因为是局部变量
temp.remove(temp.size()-1);
}
}
}
电话号码的字母组合
回溯依赖递归,需要明确终止条件,一定别忘了将答案回溯
String[] mapping=new String[]{null,null,"abc","def","ghi","jkl","mno","pqrs","tuv","wyxz"};
StringBuilder temp=new StringBuilder();
List<String> result=new ArrayList<>();
public List<String> letterCombinations(String digits) {
if (digits==null||digits.length()==0){
return result;
}
int length = digits.length();
backTrack(digits,length,0);
return result;
}
public void backTrack(String digits,int length,int num){
if (num==length){
result.add(temp.toString());
return;
}
int buttom = digits.charAt(num)-'0';
String s = mapping[buttom];
for (int i=0;i<s.length();i++){
temp.append(s.charAt(i));
backTrack(digits,length,num+1);
//回溯
temp.deleteCharAt(temp.length()-1);
}
}
分割回文串
注意:截取字符串是substring(startIndex,i+1),而不是i,左闭右开
判断是否是回文,末尾指针为s.length()-i-1,不是s.legth()-i,当i为0时,超出指针范围。
static List<List<String>> result=new ArrayList<>();
static List<String> temp=new ArrayList<>();
public static List<List<String>> partition(String s) {
backTracking(s,0);
return result;
}
public static void backTracking(String s,int startIndex){
if (startIndex>=s.length()){
result.add(new ArrayList<>(temp));
}
for (int i = startIndex; i < s.length(); i++) {
String subs=s.substring(startIndex,i+1);
if (isP(subs)){
temp.add(subs);
backTracking(s,i+1);
temp.remove(temp.size()-1);
}
}
}
public static Boolean isP(String s){
StringBuffer sb=new StringBuffer(s);
for (int i = 0; i < sb.length() / 2; i++) {
if (sb.charAt(i)!=sb.charAt(sb.length()-i-1)){
return false;
}
}
return true;
}
复原IP地址
- 引入pointNum,当pontNum==3时,为终止条件,终止条件中对临时结果的值进行了添加,所以也需要回溯
- 回溯的位置确定,两个地方的回溯传入的位置不一样
- 对截取的字符串进行合理性判断时,直接用Int,会出现超内存的情况
static List<String> result=new ArrayList<>(); static StringBuffer sb=new StringBuffer(); public static List<String> restoreIpAddresses(String s) { backTracking(s,0,0); return result; } public static void backTracking(String s,int pointNum,int startIndex){ //终止条件 if (pointNum==3){ String end = s.substring(startIndex,s.length()); if (isEffective(end)){ sb.append(end); result.add(sb.toString()); //※ sb.delete(sb.length()-end.length(),sb.length()); } return; } //单层循环 for (int i = startIndex; i<s.length(); i++) { String str=s.substring(startIndex,i+1); if (isEffective(str)){ sb.append(str+"."); backTracking(s,pointNum+1,i+1); //回溯时不能使用startIndex,sb前面也插入了小数点 位置不仅后面改变 sb.delete(sb.length()-str.length()-1,sb.length()); } } } private static boolean isEffective(String substring) { if (substring.length()==0){ return false; } if (substring.length()>1&& substring.charAt(0)=='0'){ return false; } //※ if ( Double.parseDouble(substring)>255){ return false; } return true; }
子集
坑:不能直接result.add(temp),result里面的所有值都会变成一样,为当前temp的值。
递归里是i+1而不是startIndex+1;
List<List<Integer>> result=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
backTracking(nums,0);
return result;
}
public void backTracking(int[] nums,int startIndex){
result.add(new ArrayList<>(temp));
if (startIndex>=nums.length){
return;
}
for (int i = startIndex; i < nums.length; i++) {
temp.add(nums[i]);
backTracking(nums,i+1);
temp.remove(temp.size()-1);
}
}
没用回溯套路
List<List<Integer>> result=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> subsets(int[] nums) {
backTracking(nums,nums.length,0);
return result;
}
public void backTracking(int[] nums,int length,int index){
if (length==index){
result.add(new ArrayList(temp));
return;
}
//不选当前数字
backTracking(nums,length,index+1);
//选当前数字
temp.add(nums[index]);
backTracking(nums,length,index+1);
temp.remove(temp.size()-1);
}
去掉子集中重复的元素
使用used数组,通过画树形图发现此题是树层去重,树枝不去重,且树层去重时判断条件为used[i-1]==false
static List<List<Integer>> result=new ArrayList<>();
static List<Integer> temp=new ArrayList<>();
public static List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
boolean[] used=new boolean[nums.length];
Arrays.fill(used,false);
backTracking(nums,0,used);
return result;
}
public static void backTracking(int[] nums,int startIndex){
result.add(new ArrayList<>(temp));
//单层循环
for (int i = startIndex; i < nums.length; i++) {
//此题是树层去重 但树枝不去重
if (i>0 && nums[i-1]==nums[i] &&used[i-1]==false){
continue;
}
temp.add(nums[i]);
used[i]=true;
backTracking(nums,i+1,used);
temp.remove(temp.size()-1);
used[i]=false;result.add(new ArrayList<>(temp));
//单层循环
for (int i = startIndex; i < nums.length; i++) {
//此题是树层去重 但树枝不去重
if (i>0 && nums[i-1]==nums[i] &&used[i-1]==false){
continue;
}
temp.add(nums[i]);
used[i]=true;
backTracking(nums,i+1,used);
temp.remove(temp.size()-1);
used[i]=false;
}
非递减子序列
序列不能排序,且并不是初始就单调递增,没法使用used数组,使用hashset来判断同层相同元素是否已经遍历过
List<List<Integer>> result=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
bacTracking(nums,0);
return result;
}
public void bacTracking(int[] nums,int startIndex){
if (temp.size()>=2){
result.add(new ArrayList<>(temp));
}
HashSet<Integer> hs=new HashSet<>();
for (int i = startIndex; i < nums.length; i++) {
if(temp.size()!=0&& temp.get(temp.size()-1)>nums[i] || hs.contains(nums[i])){
continue;
}
temp.add(nums[i]);
hs.add(nums[i]);
bacTracking(nums,i+1);
temp.remove(temp.size()-1);
}
}
全排列
排列和组合不一样,排列相同元素不同顺序不一样也算新的,不用startIndex,组合用startIndex来去重
List<List<Integer>> result=new ArrayList<>();
List<Integer> temp=new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
boolean[] used=new boolean[nums.length];
Arrays.fill(used,false);
backTracking(nums,used);
return result;
}
public void backTracking( int[] nums,boolean[] used){
if(temp.size()==nums.length){
result.add(new ArrayList(temp));
}
for(int i=0;i<nums.length;i++){
if(used[i]){
continue;
}
temp.add(nums[i]);
used[i]=true;
backTracking(nums,used);
used[i]=false;
temp.remove(temp.size()-1);
}
}
重复数字的全排列II
必须也要先排序,同层剪枝不能使用i>startIdex,因为它是排列不是组合!!要用used[i-1]
class Solution {
List<List<Integer>> result=new ArrayList();
List<Integer> temp=new ArrayList();
public List<List<Integer>> permuteUnique(int[] nums) {
boolean[] used=new boolean[nums.length];
Arrays.fill(used,false);
//*
Arrays.sort(nums);
backTracking(nums,used);
return result;
}
public void backTracking(int[] nums,boolean[] used){
if(temp.size()==nums.length){
result.add(new ArrayList(temp));
}
for(int i=0;i<nums.length;i++){
//*
if(used[i] || (i>0&&nums[i]==nums[i-1]&& !used[i-1])){
continue;
}
temp.add(nums[i]);
used[i]=true;
backTracking(nums,used);
used[i]=false;
temp.remove(temp.size()-1);
}
}
}
总结:全排列里剪枝用used[i-1]判断


浙公网安备 33010602011771号