LeetCode算法题12:递归和回溯-字符串中的回溯问题
文章目錄
- 一、電話號碼的字母組合
- 回溯 :
- 二、括號生成
- 回溯 :
- 總結
- 關于回溯和遞歸
- 括號生成的遞歸描述 :
- 電話號碼的字母組合的遞歸描述 :
- 括號生成的BFS描述 :
一、電話號碼的字母組合
??????題目鏈接:https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/
??????題目描述:
給定一個僅包含數字 2-9 的字符串,返回所有它能表示的字母組合。答案可以按 任意順序 返回。
給出數字到字母的映射如下(與電話按鍵相同)。注意 1 不對應任何字母。
回溯 :
??????思路,本題為組合問題,從 n 個集合中每次選取一個字符構成一個新的字符串,然后列出所有的這些字符串。那就每次從某一個集合中依次取出一個字符,回溯時刪除該字符即可。采用回溯算法常用的框架即可。
??????參考代碼如下(帶注釋):
public List<String> letterCombinations(String digits) {char[] cc=new char[26];for(int i=0;i<26;i++)cc[i]=(char)(i+'a');//cc 保存26個小寫字符int len=digits.length();List<String> re=new ArrayList<>();//返回得到的結果。if(len==0)return re;List<char[]> getIn=new ArrayList<>(len);//將 digits 中的字符轉換為對應的字符數組存儲起來。for(int i=0;i<len;i++){char tmp=digits.charAt(i);if(tmp<'2'||tmp>'9')continue;if(tmp>='2'&&tmp<'7'){int from=(int)(tmp-'0')-2;getIn.add(Arrays.copyOfRange(cc,from*3,from*3+3));}else if(tmp=='7')getIn.add(new char[]{'p','q','r','s'});else if(tmp=='8')getIn.add(new char[]{'t','u','v'});elsegetIn.add(new char[]{'w','x','y','z'});}solve(getIn,getIn.size(),re,new char[getIn.size()],0);return re;}//參數 size:一共有多少個集合可供選擇,tmp:一個存儲組合結果的字符數組,count:記錄tmp中已經選定的字符數,也標記當前正在哪一個集合中選取字符。public void solve(List<char[]> getIn,int size,List<String> re,char[] tmp,int count){if(count==size){//此時得到了一個合格的組合結果,保存起來。re.add(new String(tmp));return;}int len=getIn.get(count).length;//每次遞歸選取由count對應的哪個集合。for(int i=0;i<len;i++){tmp[count]=getIn.get(count)[i];solve(getIn,size,re,tmp,count+1);//在下一個集合中選取字符。//無須多余的回退到上一個狀態的代碼,在此采用的是字符數組,直接覆蓋即可。}}?????? 上面程序的執行順序:從最后一個集合依次選取每一個字符,在從倒數第二個集合開始選取…直到第一個集合。然后程序結束。對輸入的處理自己采用了字符數組(代碼看著有些復雜),當然也可以采用別的方式。
?????? 關于處理輸入:用map也可以,但是懶的敲那么字符。比如這樣:
String[] input = {"abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};?????? 或者這樣:
Map<Character, String> phoneMap = new HashMap<Character, String>();phoneMap.put('2', "abc");phoneMap.put('3', "def");phoneMap.put('4', "ghi");phoneMap.put('5', "jkl");phoneMap.put('6', "mno");phoneMap.put('7', "pqrs");phoneMap.put('8', "tuv");phoneMap.put('9', "wxyz");二、括號生成
??????題目鏈接:https://leetcode-cn.com/problems/generate-parentheses/
??????題目描述:
數字 n 代表生成括號的對數,請你設計一個函數,用于能夠生成所有可能的并且 有效的 括號組合。
回溯 :
??????思路,本題的難點個人覺得應該是在約束條件的處理上,即左右括號一定要對應,特別的:每次在添加右括號之前必須有一個相對應的左括號存在。用棧也可以,但最簡單的方法是統計左右括號的個數。左括號可以隨意添加,只要其個數不要超過n就好;右括號添加時特別要注意必須有一個左括號和它對應。
??????參考代碼如下(帶注釋):
public List<String> generateParenthesis(int n) {int left=0,right=0;//初始時均為0,表示一個組合中已有的左右括號的個數。每次可以添加一個右括號的前提是left必須大于rightList<String> re=new ArrayList<>();StringBuilder tmp=new StringBuilder();//用來保存一個可能的組合solve(re,n,left,right,tmp);return re;}public void solve(List<String> re,int n,int left,int right,StringBuilder tmp){if(right==n){//結束條件為添加最后一個右括號時re.add(new String(tmp));return;}if(left<n){tmp.append('(');solve(re,n,left+1,right,tmp);//對下一個位置的元素做選擇tmp.deleteCharAt(tmp.length()-1);//回退}if(left>right){tmp.append(')');solve(re,n,left,right+1,tmp);tmp.deleteCharAt(tmp.length()-1);}}??????上面這個解法和官方題解思路完全一致哈。
總結
關于回溯和遞歸
括號生成的遞歸描述 :
??????遞歸解法,依照約束條件來分類遞歸。它和回溯的區別僅在于它不需要回退,解決問題的思想基本一致,都是采用了深度優先遍歷的方式。參考代碼如下:
public List<String> generateParenthesis(int n) {int left=0,right=0;//初始時均為0,表示一個組合中已有的左右括號的個數。每次可以添加一個右括號的前提是left必須大于rightList<String> re=new ArrayList<>();solve(re,n,left,right,"");return re;}public void solve(List<String> re,int n,int left,int right,String tmp){if(right==n){//結束條件為添加最后一個右括號時。re.add(tmp);return;}if(left<n)solve(re,n,left+1,right,tmp+"(");if(left>right)solve(re,n,left,right+1,tmp+')');}電話號碼的字母組合的遞歸描述 :
??????很簡單的,就可以將本題的回溯代碼轉換為遞歸實現,如下:
public List<String> letterCombinations(String digits) {char[] cc=new char[26];for(int i=0;i<26;i++)cc[i]=(char)(i+'a');//cc 保存26個小寫字符int len=digits.length();List<String> re=new ArrayList<>();//返回得到的結果。if(len==0)return re;List<char[]> getIn=new ArrayList<>(len);//將 digits 中的字符轉換為對應的字符數組存儲起來。for(int i=0;i<len;i++){char tmp=digits.charAt(i);if(tmp<'2'||tmp>'9')continue;if(tmp>='2'&&tmp<'7'){int from=(int)(tmp-'0')-2;getIn.add(Arrays.copyOfRange(cc,from*3,from*3+3));}else if(tmp=='7')getIn.add(new char[]{'p','q','r','s'});else if(tmp=='8')getIn.add(new char[]{'t','u','v'});elsegetIn.add(new char[]{'w','x','y','z'});}solve(getIn,getIn.size(),re,"",0);return re;}public void solve(List<char[]> getIn,int size,List<String> re,String tmp,int count){if(count==size){re.add(tmp);return;}int len=getIn.get(count).length;for(int i=0;i<len;i++)solve(getIn,size,re,tmp+getIn.get(count)[i],count+1);}??????在 leetcode 中測試之后可以發現,遞歸解法的效率要低于回溯,可能是因為需要不斷地創建字符串吧。
括號生成的BFS描述 :
??????采用 BFS 的解法需要保存每一種中間結點的 left 和 right 值,在遞歸(DFS)中不需要,因為它們是由系統棧進行存儲的。參考代碼如下:
//BFS//需要新建一個結點類,因為每個結點需要保存自己的 left 和 right 值。private static class Node{String value;int left,right;//初始時均為0,表示一個組合中已有的左右括號的個數。每次可以添加一個右括號的前提是left必須大于rightpublic Node(){value="";left=right=0;};public Node(String s,int le,int ri){value=s;left=le;right=ri;};}public List<String> generateParenthesis(int n) {LinkedList<Node> re=new LinkedList<>();re.add(new Node());Node tmp;while(!re.isEmpty()){tmp=re.poll();if(tmp.left<n)re.add(new Node(tmp.value+"(",tmp.left+1,tmp.right));if(tmp.left>tmp.right)re.add(new Node(tmp.value+")",tmp.left,tmp.right+1));if(re.peek().value.length()==2*n)break;}List<String> ans=new ArrayList<>();for(Node node:re)ans.add(node.value);return ans;}總結
以上是生活随笔為你收集整理的LeetCode算法题12:递归和回溯-字符串中的回溯问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LeetCode算法题11:递归和回溯-
- 下一篇: LeetCode算法题13:DFS/BF