算法之美-树形问题的递归与回溯法
递归调用的重要特征,每一层递归调用结束后要返回上一层继续调用,直到根节点的递归调用所有可能性都尝试完成,则整个递归完成,也被称为回溯。
沿着一条路径寻找答案,一旦找到了答案或者没有找到答案,一旦到底回去,继续寻找的过程,回溯法是暴力解法的主要实现实现手段。
经典案例
回溯法搜索整个数查找某个解
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例:
输入:"23"
输出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
说明:
尽管上面的答案是按字典序排列的,但是你可以任意选择答案输出的顺序。
思路一:
方法:回溯
回溯是一种通过穷举所有可能情况来找到所有解的算法。如果一个候选解最后被发现并不是可行解,回溯算法会舍弃它,并在前面的一些步骤做出一些修改,并重新尝试找到可行解。
给出如下回溯函数 backtrack(combination, next_digits)
,它将一个目前已经产生的组合 combination
和接下来准备要输入的数字 next_digits
作为参数。
如果没有更多的数字需要被输入,那意味着当前的组合已经产生好了。 如果还有数字需要被输入: 遍历下一个数字所对应的所有映射的字母。 将当前的字母添加到组合最后,也就是 combination = combination + letter 。 重复这个过程,输入剩下的数字: backtrack(combination + letter, next_digits[1:])
。
class Solution {
Map<String, String> phone = new HashMap<String, String>() {{
put("2", "abc");
put("3", "def");
put("4", "ghi");
put("5", "jkl");
put("6", "mno");
put("7", "pqrs");
put("8", "tuv");
put("9", "wxyz");
}};
List<String> output = new ArrayList<String>();
public void backtrack(String combination, String next_digits) {
// if there is no more digits to check
if (next_digits.length() == 0) {
// the combination is done
output.add(combination);
}
// if there are still digits to check
else {
// iterate over all letters which map
// the next available digit
String digit = next_digits.substring(0, 1);
String letters = phone.get(digit);
for (int i = 0; i < letters.length(); i++) {
String letter = phone.get(digit).substring(i, i + 1);
// append the current letter to the combination
// and proceed to the next digits
backtrack(combination + letter, next_digits.substring(1));
}
}
}
public List<String> letterCombinations(String digits) {
if (digits.length() != 0)
backtrack("", digits);
return output;
}
}
思路二:
与字符串相关,需要考虑字符串的边界问题(字符串的合法性,空字符串)
多个解的顺序
树形问题的递归与回溯
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Comb {
static String[] LetterMap= {
" ", //0
"", //1
"abc", //2
"def", //3
"ghi", //4
"jkl", //5
"mno", //6
"pqrs", //7
"tuv", //8
"wxyz" //9
};
static List<String> reStrings;
@SuppressWarnings("resource")
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String digits = scanner.next();
if(digits.equals("")) {
return;
}
reStrings = new ArrayList<>();
findCombination(digits,0,"");
System.out.println(reStrings.toString());
}
private static void findCombination(String digits, int i, String string) {
// TODO Auto-generated method stub
if(i==digits.length()) {
reStrings.add(string);
return;
}
Character character = digits.charAt(i);
assert character.compareTo('0')>=0&&character.compareTo('9')<=0&&character.compareTo('1')!=0;
String letters = LetterMap[character-'0'];
for(int j=0;j<letters.length();j++) {
findCombination(digits,i+1,string+letters.charAt(j));
}
return;
}
}
输出:
回溯法案例
给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
示例:
输入: "25525511135"
输出: ["255.255.11.135", "255.255.111.35"]
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class IpAddress {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String string = scanner.next();
if(string==null||string.length()<4) {
System.out.println(string);
}
List<String> res = new ArrayList<>();
restoreIpAddress(string.toCharArray(),0,0,new ArrayList<>(),res);
System.out.println(res);
}
/*
* ["0.10.0.10","0.100.1.0"] 注意这个case
*/
private static void restoreIpAddress(char[] chars, int index, int size, List<String> temp, List<String> res) {
// TODO Auto-generated method stub
if(index>=chars.length) {
return;
}
if(size==3) {
if(check255(chars,index,chars.length-1)) {
res.add(temp.get(0)+"."+temp.get(1)+"."+temp.get(2)+"."+new String(chars,index,chars.length-index));
}
return;
}
int offset = index;
if(chars[offset]=='0') {
temp.add(new String(chars, index, offset-index+1));
restoreIpAddress(chars,offset+1,size+1,temp,res);
temp.remove(temp.size()-1);
offset++;
}else {
while(offset<=chars.length-1&&check255(chars,index,offset)) {
temp.add(new String(chars, index, offset-index+1));
restoreIpAddress(chars,offset+1,size+1,temp,res);
temp.remove(temp.size()-1);
offset++;
}
}
}
private static boolean check255(char[] chars, int start, int end) {
// TODO Auto-generated method stub
if(chars[start]=='0') {
if(start==end) {
return true;
}else {
return false;
}
}
int sum=0;
for(int i=start;i<=end;i++) {
sum = sum*10+(chars[i]-'0');
if(sum>255) {
return false;
}
}
return true;
}
}
输出:
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class IP {
static List<String> res;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String string = scanner.next();
if(scanner==null||string.length()<4)
{
System.out.println(string);
return;
}
res = new ArrayList<>();
DFS(string,0,0,new String());
System.out.println(res);
}
private static void DFS(String string, int index, int N, String temp) {
// TODO Auto-generated method stub
if(index==string.length()) {
if(N==4) {
String newStr = new String(temp.substring(0, temp.length()-1));
res.add(newStr);
}
return;
}
if(N>4) return;
//如果当前为0,那么必须占用一次机会
if(string.charAt(index)=='0') DFS(string,index+1,N+1,temp+string.charAt(index)+".");
else {
for(int i=1;i<=3;i++) {
if(index+i<=string.length()) {
String tempString = string.substring(index, index+i);
int a = Integer.parseInt(tempString);
if(a<255) DFS(string,index+i,N+1,temp+tempString+".");
}
}
}
}
}
回溯法案例
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例:
输入: "aab" 输出: [ ["aa","b"], ["a","a","b"] ]
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Palindrome {
static String string;
static List<List<String>> totallsit;
public static void main(String[] args) {
Scanner scanner= new Scanner(System.in);
string = scanner.next();
totallsit = new ArrayList<>();
DFS(new ArrayList<>(),0);
System.out.println(totallsit);
}
private static void DFS(List<String> arrayList, int index) {
// TODO Auto-generated method stub
if(index==string.length()) {
totallsit.add(new ArrayList<>(arrayList));
return;
}
for(int i=index;i<string.length();i++) {
if(isPalindrome(index,i)) {
arrayList.add(string.substring(index, i+1));
DFS(arrayList,i+1);
arrayList.remove(arrayList.size()-1);
}
}
}
private static boolean isPalindrome(int start, int end) {
// TODO Auto-generated method stub
while(start<end) {
if(string.charAt(start)!=string.charAt(end)) return false;
start++;
end--;
}
return true;
}
}
输出: