Labels

Tuesday, February 3, 2015

Palindrome Partitioning II


Palindrome Partitioning II




Given a string s, partition s such that every substring of the partition is a palindrome.
Return the minimum cuts needed for a palindrome partitioning of s.
For example, given s = "aab",
Return 1 since the palindrome partitioning ["aa","b"] could be produced using 1 cut.

(注:写的废话有点多,可以直接看我在leetcode discuss的提问https://oj.leetcode.com/discuss/24253/how-to-get-from-o-n-3-to-o-n-2-java-solution-sharing
Naive Way: 好像有上一题的DP的二维矩阵就可以很快得到最小割了。

基本一致的回溯方式,这样的算法复杂度是O(2^n),最坏的情况。然后会出现超时。

public class Solution {
    public int minCut(String s) {
        boolean opt[][] = new boolean[s.length()][s.length()+1];
        // base case
        for(int i = 0;i < s.length();i++){
            opt[i][i+1] = true;
            opt[i][i] = true;
        }
        // iteration
        for(int i = 2;i <= s.length();i++)
            for(int j = 0;j+i <= s.length();j++)
                opt[j][j+i] = s.charAt(j)==s.charAt(j+i-1) && opt[j+1][j+i-1];
        
        return search(0,opt,-1);
    }
    
    private int search(int begin, boolean[][] opt, int cut){
        int min = opt[0].length;
        if(begin==opt[0].length-1)
            return cut;
        for(int i = begin+1;i < opt[0].length;i++)
            if(opt[begin][i])
                min = Math.min(search(i,opt,cut+1),min);
        return min;
    }
}


Improved Way:这时我想到O(n)求longest palindrome substring的方法。因为那个方法能在O(n)的时间内求出所有字符的覆盖范围,那么用贪心的思想取覆盖范围大的,就可以实现最小分割数了。

实际做时,发现这种greedy的思想会产生一个bug。当String = aaaba的时候。
0  1  2  3  4  5  6  7  8  9  10
#  a  #  a   #  a  #  b  #  a  #
1  2  3  4  3  2  1  4  1  2  1

先取第一个4就会得到{"aaa","b","a"}
先取第二个4就会得到{"aa","aba"}
一个是3割一个是2割。
这个BUG只会在OJ的倒数第二种测例中出现,很容易忽略。同时,这个例子也可以说明对DP的结果进行遍历时,不能够用greedy的思想去最大长度的找一个max(j-i) 其中 opt[i][j]=true。否则就会出现这里这种先找前3个a而导致后面必须用多一个割的情况。



这时看了一眼这道题的标签,是DP耶。幡然醒悟,应该用一个更直接的DP。
基本逻辑为:
opt(i,j) //表示将String s[i-j]分成全palindrome子串的最少割数。
// base case
opt(i,i) = 0     0<= i < s.length()
// iteration
opt(i,j) =
if s[i]==s[j] && opt(i+1,j-1)==0
     0
else
     min(opt(i,t)+opt(t+1,j)+1)  for  i<= t < j

算法复杂度为O(n^3),但还是超时了。
public class Solution {
    public int minCut(String s) {
        // DP
        int opt[][] = new int[s.length()][s.length()];
        // base case 0
        // iteration
        for(int i = 1;i < s.length();i++){
            for(int j = 0;j+i < s.length();j++){
                if(s.charAt(j)==s.charAt(j+i) && opt[j+1][j+i-1]==0){
                    opt[j][j+i] = 0;
                }else{
                    opt[j][j+i] = i;
                    for(int t = j;t < j+i;t++)
                        opt[j][j+i] = Math.min(opt[j][j+i], opt[j][t]+opt[t+1][j+i]+1);
                }
            }
        }
        return opt[0][s.length()-1];
    }
}


可以不可以在O(n^2)内完成呢?如果DP只有一个参数呢?
opt[i] // 表示分割s[0-i]所需最小分割数。
 // base case
opt[0] = 0;
// iteration
opt[i] =
if(s[0-i] is palindrome)
     0
else
    min(opt[t] + s[t+1 ~ i] is palindrome?1:i-t)

这样做虽然只有O(n)的外层循环,但是每一次循环都要遍历之前所有还要同时判断当前的是否为palindrome,每一次循环就需要O(n^2),所以还是O(n^3)。

还是看了一下discuss发现自己好愚蠢,在一开始就已经在O(n^2)的时间内求出所有s[i~j]是否为palindrome了。所以每一层循环现在只需要O(n)的时间,总的时间就是O(n^2)了。


public class Solution {
    public int minCut(String s) {
        // use DP to determine any palindrome substring
        boolean opt[][] = new boolean[s.length()][s.length()+1];
        // base case
        for(int i = 0;i < s.length();i++){
            opt[i][i+1] = true;
            opt[i][i] = true;
        }
        // iteration
        for(int i = 2;i <= s.length();i++)
            for(int j = 0;j+i <= s.length();j++)
                opt[j][j+i] = s.charAt(j)==s.charAt(j+i-1) && opt[j+1][j+i-1]; 
        
        
        // use DP to determine min cut
        int cut[] = new int[s.length()+1];
        for(int i = 1;i <= s.length();i++){
            if(opt[0][i])
                cut[i] = 0;
            else{
                cut[i] = i-1;
                for(int t = 1;t < i;t++){
                    cut[i] = Math.min(cut[i],cut[t] + (opt[t][i]?1:i-t));
                }
            }
        }
        return cut[s.length()];
        
    }
}

 

Monday, February 2, 2015

Palindrome Partitioning


Palindrome Partitioning



 


Given a string s, partition s such that every substring of the partition is a palindrome.
Return all possible palindrome partitioning of s.
For example, given s = "aab",
Return
  [
    ["aa","b"],
    ["a","a","b"]
  ] 
 
 
Naive Way:第一感觉可以用recursive的方法,对String第一个palindrome及它后面的部分
分别进行recursive call求palindrome partition。具体下来就是:
 
如果一个String只有一个字符,则返回单个字符的list。
否则,遍历String,一旦遇到palindrome就提取该子串作为头,对后部分分别进行recursive call,
合并两者,加入结果中。 
 
有一个问题就是如何求包含第一个字母的所有palindrome。在longest palindrome substring中
在O(n)内可以求出以所有字符为中心最大范围的palindrome,当然用在这里肯定也可以求处包含第一个
字符的所有palindrome。当肯定不是最佳的,但是要想找出在小于O(n)的时间内求出包含第一个字符
的所有palindrome,看来是不可能的,所以,干脆就用这个方法了。
 
这里我用求longest palindrome substring中的方法求出所有包含第一个字符的palindrome的
最后位置,以此方便分割字符。
 
算法复杂度应该是f(n)+f(n-1)+f(n-2)+... 而f(n)在最坏的情况应该是O(2^n)的,所以最后的
算法复杂度是O(2^n)。不知道这样算对不对,总之是一个很差的算法复杂度。
 

public class Solution {
    public List<List<String>> partition(String s) {
        List<List<String>> gross = new ArrayList<List<String>>();
        List<Integer> num = panlindromeIndex(s);
        for(int i = 0;i < num.size();i++){
            String left = s.substring(0,num.get(i));
            List<List<String>> right = partition(s.substring(num.get(i),s.length()));
            if(right.size()==0){
                List<String> base = new ArrayList<String>();
                base.add(left);
                gross.add(base);
            }
            for(int j = 0;j < right.size();j++){
                right.get(j).add(0,left);
                gross.add(right.get(j));
            }
        }
        return gross;
    }
    
    // return a list of position(i) where [0~i] is a panlindrome
    private List<Integer> panlindromeIndex(String s){
        List<Integer> list = new ArrayList<Integer>();
        s = preProcess(s);
        int p = 0;
        int f[] = new int[s.length()];
        for(int i = 1;i < s.length();i++){
            f[i] = 1;
            if(i < p + f[p]){
                if(p+f[p] - i > f[2*p-i])
                    f[i] = f[2*p-i];
                else
                    f[i] = p+f[p]-i;
            }
            while(i-f[i] >= 0 && i + f[i] < s.length()){
                if(s.charAt(i-f[i])!=s.charAt(i+f[i]))
                    break;
                f[i]++;
            }
            if(i+f[i] > p+f[p]){p=i;}
        }
        for(int i = 1;i < f.length;i+=2){
            if(i-f[i] < 0)
                list.add((i+f[i])/2);
            if(i-1-f[i-1] < 0)
                list.add((i-1+f[i-1])/2);
        }
        return list;
    }
    
    private String preProcess(String s){
        StringBuilder rlst = new StringBuilder();
        for(int i = 0;i < s.length();i++){
            rlst.append('#');
            rlst.append(s.charAt(i));
            if(i==s.length()-1)
                rlst.append('#');
        }
        return rlst.toString();
    }
}
 
 
Improved Way:为了提高算法效率,我思考了一下以上算法的缺陷。recursive call里产生
的子字符串很多都是重复的,这里使算法效率降低了很多。有没有办法先把这些有效的子字符串
写好,然后需要的时候直接调用呢?这时我想到了求longest palindrome substring最
原始的方法——DP。DP可以用一个二维矩阵告诉我某一段子字符串是否回文,着就相当于把所有
可能的子字符串都先求出来了,那如何调用呢。DP得到的是一个二维矩阵,通过上一层的某段
是否回文,可以用DFS的方法,遍历下一层对应所有可能的回文子串,这里又是用带回溯的DFS,
就像N-Queen和Sudoku一样。
 
算法复杂度为O(n^2)。比之前提高了不少。
 
public class Solution {
    public List<List<String>> partition(String s) {
        // DP
        List<List<String>> gross = new ArrayList<List<String>>();
        Deque<Range> deque = new LinkedList<Range>();
        Stack<Range> stack = new Stack<Range>();
        boolean opt[][] = new boolean[s.length()][s.length()+1];
        // base case
        for(int i = 0;i < s.length();i++){
            opt[i][i+1] = true;
            opt[i][i] = true;
        }
        // iteration
        for(int i = 2;i <= s.length();i++)
            for(int j = 0;j+i <= s.length();j++)
                opt[j][j+i] = s.charAt(j)==s.charAt(j+i-1) && opt[j+1][j+i-1];
        // construct result using DFS
        for(int i = 1;i <= s.length();i++){
            if(opt[0][i]){
                Range range = new Range(0,i);
                stack.push(range);
            }
        }
        while(!stack.isEmpty()){
            Range range = stack.pop();
            deque.offerLast(range);
            boolean hasNext = false;
            if(range.e==s.length()){
                List<String> list = new ArrayList<String>();
                for(int i = 0;i < deque.size();i++){
                    Range next = deque.pollFirst();
                    list.add(s.substring(next.b,next.e));
                    deque.offerLast(next);
                }
                gross.add(list);
            }else{
                for(int i = range.e+1;i <= s.length();i++){
                    if(opt[range.e][i]){
                        Range sub = new Range(range.e,i);
                        stack.push(sub);
                        hasNext = true;
                    }
                }
            }
            // back trace
            if(!hasNext || range.e==s.length()){
                if(!stack.isEmpty()){
                    Range peek = stack.peek();
                    while(!deque.isEmpty()){
                        if(deque.pollLast().b == peek.b)
                            break;
                    }
                }
            }
        }
        return gross;
    }
    

    class Range{
        int b;
        int e;
        Range(int begin, int end){
            b = begin;
            e = end;
        }
    }
} 
 
 
看了看自己第一次的做法,发现比这种用DFS遍历的方法高明多了,是根据DP的二维矩阵从后往前
进行遍历的方法。运行时间也是最短的。
 
public class Solution {
    public List<List<String>> partition(String s) {
        List<List<String>> lst = new ArrayList<List<String>>();
        List<String> temp = new ArrayList<String>();
        int len = s.length();
        boolean opt[][] = new boolean[len][len];
        
        // initialize
        for(int i = 0;i < len;i++){Arrays.fill(opt[i], false);}
        for(int i = 0;i < len;i++){opt[i][i] = true;}
        
        // recurrence
        for(int u = 1;u < len;u++){
         for(int i = 0;i+u < len;i++){
          if(u >= 2){
           opt[i][i+u] = (opt[i+1][i+u-1] && s.charAt(i)==s.charAt(i+u));
          }else if(u == 1){
           opt[i][i+u] = s.charAt(i) == s.charAt(i+u)? true:false;
          }
         }
        }
        
        // generate result according to opt
        generateList(0,len,s,opt,temp,lst);

        //show result
        //System.out.print(lst);
        return lst;

    }

 

 private boolean generateList(int i, int len, String s, boolean opt[][], List<String> temp,List<List<String>> lst){
  for(int j = 0;j < len; j ++){
   if(opt[i][j]){
    List<String> newCombo = new ArrayList<String>(temp);
    newCombo.add(s.substring(i, j+1));
    if(j == len-1){
     lst.add(newCombo);
    }else{
     generateList(j+1,len,s,opt,newCombo,lst);
    }
   }
  }
  return true;
 }

} 
 
 
 
突然发现我的算法课老师Michael讲的如何回溯DP得到的矩阵,得到正式的结果的那种从尾到头的方法,其实是一种DFS。 


Simplify Path



Simplify Path



 


Given an absolute path for a file (Unix-style), simplify it.
For example,
path = "/home/", => "/home"
path = "/a/./b/../../c/", => "/c"

Naive Way: 这个看上去好复杂啊,我第一做的时候完全不懂题目是什么意思,因为没有学过cmd语句。现在做就觉得一目了然了。每一个"."表示当前文档位置,就是说停在原地不动的意思,简化的时候需要舍去,因为有没有都一样。每一个".."表示返回上级,就是说前一个有效文件路径可以作废。另外还要处理"//"的情况。

首先因为每一个"/"把每一级文件都分割开了,做好的做法当然是能把被"/"分割的每一个看成一个单位。
这是就想到了String.split()这个函数,这样可以对付"//"的情况。

然后考虑"."和"..","."当然是不要了,那么".."就要去掉上一级的文件,如果没有上一级,就要写下来。
这里实际测试的时候发现OJ居然认为"/.."要简化成"/",不管了,就是".."就要去掉上一级,如果没有上一级,也不用写下来。

一开始我用的stack,发现最后输出要再倒转一次,直接用deque(双向队列)就可以了。

public class Solution {
    public String simplifyPath(String path) {
        String s = "/";
        String[] gross = path.split("/");
        Deque<Integer> deque = new LinkedList<Integer>();
        for(int i = 0;i < gross.length;i++){
            if(gross[i].equals(".") || gross[i].equals("")){continue;}
            else if(gross[i].equals("..")){
                if(!deque.isEmpty())
                    deque.pollLast();
            }else{
                deque.offerLast(i);
            }
        }
        while(!deque.isEmpty()){
            s += gross[deque.pollFirst()];
            if(!deque.isEmpty())
                s += "/";
        }
        return s;
    }
}


 

Maximum Subarray


Maximum Subarray



 


Find the contiguous subarray within an array (containing at least one number) which has the largest sum.
For example, given the array [−2,1,−3,4,−1,2,1,−5,4],
the contiguous subarray [4,−1,2,1] has the largest sum = 6.

Naive Way: 一个感觉觉得跟sell stock那题很像,可以用DP吧,先试一试。

基本逻辑为:
opt[i,j] // 表示从i到j的maximum subarray。
才写第一句就发现这样一来就是O(n^2)的复杂度了。但是感觉这一题应该是能在O(n)的时间内算出来的。

一个比较直观的想法就是用指针控制记录有效区段首尾。一旦遇到整数,就有可能要替换最大值,一旦遇到负数,只要当前和仍大于零,就可以将该负数加入有效区段内。这里自己还漏掉了全是负数的情况 ,但这种情况其实很简单,全是负数时最大的和就是其中一个负数,每次取到负数都与最大值进行比较则可将该情况囊括。


public class Solution {
    public int maxSubArray(int[] A) {
        int begin = 0, end = 0;
        if(A.length == 0){return 0;}
        int max = Integer.MIN_VALUE;
        int sum = 0;
        for(int i = 0;i < A.length;i++){
            if(A[i] >= 0){
                if(sum <= 0){
                    while(begin!=end){begin++;}
                    sum = 0;
                }
                max = Math.max(sum + A[i], max);
            }else{
                max = Math.max(max,A[i]);
            }
            end++;
            sum += A[i];
        }
        return max;
    }
}


最后看来一个人的discuss发现跟我的一模一样,就是没有begin 和 end。这才发现begin和end在这段代码中没有作用,删除掉。

public class Solution {
    public int maxSubArray(int[] A) {
        if(A.length == 0){return 0;}
        int max = Integer.MIN_VALUE;
        int sum = 0;
        for(int i = 0;i < A.length;i++){
            if(A[i] >= 0){
                if(sum <= 0)
                    sum = 0;
                max = Math.max(sum + A[i], max);
            }else{
                max = Math.max(max,A[i]);
            }
            sum += A[i];
        }
        return max;
    }
}

最终版本应该是这样的:(来自leetcode用户 AlexTheGreat)

public int maxSubArray(int[] A) {
    int max = Integer.MIN_VALUE, sum = 0;
    for (int i = 0; i < A.length; i++) {
        if (sum < 0) 
            sum = A[i];
        else 
            sum += A[i];
        if (sum > max)
            max = sum;
    }
    return max;
}
 
 
Improved Way: 看见leetcode上有人提问这道题用divide and conquer做,
一位大神回答了说divide and conquer是 O(nlogn)。那么就懂如果要
divide and conquer是怎么回事,每次都得对比两边的最大subarray sum和
中间部分有可能的subarray sum,想想都觉得很麻烦,还是先不写了。





 

Sunday, February 1, 2015

Rotate List

Rotate List


Given a list, rotate the list to the right by k places, where k is non-negative.
For example:
Given 1->2->3->4->5->NULL and k = 2,
return 4->5->1->2->3->NULL.

Naive Way: 这里联想到了找链表的中点的方法 (龟兔赛跑的启示)。两个指针,如果一个指针一次走一步,一个指针一次走两步,一直走知道某一个遇到null, 走的慢的指针就会正好走到链表的中点。这里也可以用。先把第二个指针走出k步,然后两个指针同时往前走,第二个指针遇到null的时候,第一个指针正好走到我们想要做为新head的地方,再把原来的头接到第二个指针的尾部。

这里我自己做这种指针链表的题目喜欢新设一个头,相当于增加了一个-1的位置,可以避免head=null单独写,正常步数循环的情况会获得父节点instead of子节点,感觉这样方便点。

实际做的时候发现循环到头会自动从head开始继续循环数,所以添加让兔子自动从尾部找到头部的条件。这也是自己没读懂题意。

算法复杂度是O(max(l,n))

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode rotateRight(ListNode head, int n) {
        ListNode newHead = new ListNode(0);
        newHead.next = head;
        ListNode hare = newHead;
        ListNode tortoise = hare;
        int i = 0;
        while(i < n){
            if(hare.next==null)
                hare = newHead;
            hare = hare.next;
            i++;
        }
        if(hare==tortoise){return newHead.next;}
        while(hare!=null && hare.next!=null){
            tortoise = tortoise.next;
            hare = hare.next;
        }
        if(tortoise!=newHead){
            newHead.next = tortoise.next;
            hare.next = head;
            tortoise.next=null;
        }
        return newHead.next;
    }
}


Improved Way: 由于运行时间排的很靠后,有必要思考一下这样的做法是不是好了。如果不需要把头尾相接似乎很方便,那是不是先令n = n%l (l为链表长度)呢?这样试了试。居然运行时间提高了很多。但方法没变化的。

算法复杂度是O(min(l,n))

public class Solution {
    public ListNode rotateRight(ListNode head, int n) {
        ListNode newHead = new ListNode(0);
        newHead.next = head;
        ListNode hare = newHead;
        ListNode tortoise = hare;
        int i = 0,length = 0;
        while(hare.next!=null){
            hare = hare.next;
            length++;
        }
        hare = tortoise;
        while(i < (length==0?0:n%length)){
            hare = hare.next;
            i++;
        }
        if(hare==tortoise){return newHead.next;}
        while(hare!=null && hare.next!=null){
            tortoise = tortoise.next;
            hare = hare.next;
        }
        if(tortoise!=newHead){
            newHead.next = tortoise.next;
            hare.next = head;
            tortoise.next=null;
        }
        return newHead.next;
    }
}


后来在discuss上又看到一个比较好的点子,先把链表连成一个圈,然后循环跳步,最后再断开。但实际运行起来发现是完全一样的,只不过连成圈将 n > length的情况包括进去,和我的第一种方法是一样的。

Search in Rotated Sorted Array II


Search in Rotated Sorted Array II



 


Follow up for "Search in Rotated Sorted Array":
What if duplicates are allowed?
Would this affect the run-time complexity? How and why?
Write a function to determine if a given target is in the array.

Naive Way: 在原来的基础上增加了数字可重复的条件。这一条件带来什么影响,会不会影响算法复杂度。这种时候我发现一个比较高明的方法是考虑最坏的情况。

这是是 Search in Rotated Sorted Array I 的代码。算法复杂度是O(logn)。

public class Solution {
    public int search(int[] A, int target) {
        return search(A,0,A.length-1,target);
    }
    
    private int search(int[] A, int begin, int end, int target){
        if(begin > end)
            return -1;
        int middle = (begin+end)/2;
        if(A[middle]==target)
            return middle;
        if(A[middle] > A[end]){
            if(A[middle] > target && A[begin] <= target)
                return search(A, begin, middle-1, target);
            else
                return search(A, middle+1, end, target);
        }else{
            if(A[middle] < target && A[end] >= target)
                return search(A, middle+1, end, target);
            else
                return search(A, begin, middle-1, target);
        }
    }
}

如果有重复,最坏的情况是全是同一个数,比如

[1  1  1  1  1]

这样只有1可以做target,找的时候第一下就返回了,不能说明情况。那么退一步只有一个数是不重复的

[1  1  1  2  1  1]

这时有一个重大发现就是这个2摆在哪里都可以,都是rotated array。如果2摆在最前面

[2  1  1  1  1  1]

经过原来的方法会二分的执行算法直到找到2在Index=0的位置。如果2摆在最后,可想而知也会符合O(logn)的时间的。最后把2摆在中间。

[1  1  1  2  1  1]

问题来了,会出现b1 == e1 == b2 == e2的情况。对于这种情况,是无法正确找出2的半区的,因为2既有可能在前半部分,也有可能在后半部分。


这是否说明第一次的算法在这里不能用了呢。仔细走一遍第一次的算法,到了选择分区时:

if(A[middle] > A[end]){
            if(A[middle] > target && A[begin] <= target)
                // 搜索前半部分
            else
                //搜索后半部分
        }else{
            if(A[middle] < target && A[end] >= target)
                //搜索后半部分
            else
                //搜索前半部分
        }

第一个选择条件A[middle] > A[end]不成立,第二个选择条件A[middle] < target && A[end] >= target也不成立,就会自动进入搜索前半部分。这时会想如果能去除一头一尾的重复部分呢?
这样原数组就会变成[1 2 1],此时过一遍算法发现是可以正确找到2的。那么一个假设诞生了:每次都先去除一头一尾的重复部分,再运行算法是否就可以了呢? 经过测试是可以的,并且因为去除一头一尾的意义其实是打破b1==e1==b2==e2的平衡性,任意去除尾部或者头部都可以打破这种平衡,使数组重心改变,一旦数组重心改变,原来的算法其实就是正确的找出下一个半区。

此时算法复杂度变为O(n)。

public class Solution {
    public boolean search(int[] A, int target) {
        return search(A,0,A.length-1,target);
    }
    
    private boolean search(int[] A, int begin, int end, int target){
        while(begin+1 < end){
            if(A[begin+1]!=A[begin])
                break;
            begin++;
        }
        if(begin > end)
            return false;
        int middle = (begin+end)/2;
        if(A[middle]==target)
            return true;
        if(A[middle] > A[end]){
            if(A[middle] > target && A[begin] <= target)
                return search(A, begin, middle-1, target);
            else
                return search(A, middle+1, end, target);
        }else{
            if(A[middle] < target && A[end] >= target)
                return search(A, middle+1, end, target);
            else
                return search(A, begin, middle-1, target);
        }
    } 
}

总结一下,就是Binary search这种算法的核心思想是利用O(1)的时间发现左右半区的不平衡性,从而发现决定focus on哪一个半区,所以一旦有可能出现完全平衡的情况,binary search就无法正确运行,此时可能是目标情况,也可能是边缘情况,创造新的不平衡性可使算法运行下去。

类似binary search的题目还有Search-in-rotated-sorted-array, Search-insert-position

Letter Combinations of a Phone Number


Letter Combinations of a Phone Number



 


Given a digit string, return all possible letter combinations that the number could represent.
A mapping of digit to letters (just like on the telephone buttons) is given below.

Input:Digit string "23"
Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
Note:
Although the above answer is in lexicographical order, your answer could be in any order you want.

Naive Way: 先初始化list,遍历String中所有字符,每经过一个都在原有的list<String>里加入新字符,把每一个新String都加入到list里,然后删除原来list长度的String。这样描述下来,貌似用一个queue来存string会更好。但是运行时间对比下来用queue反而更慢些。

下面是不用queue的。

public class Solution {
    public List<String> letterCombinations(String digits) {
        List<String> list = new ArrayList<String>();
        String zero = "";
        list.add(zero);
        for(int i = 0;i < digits.length();i++){
            String chars = digit2chars(digits.charAt(i));
                int preLen = list.size();
                for(int t = 0;t < preLen;t++){
                    for(int j = 0;j < chars.length();j++){
                        String s = new String(list.get(t)+chars.charAt(j));
                        list.add(s);
                    }
                }
                for(int t = 0;t < preLen;t++){
                    list.remove(0);
                }
        }
        return list;
    }
    
    private String digit2chars(char num){
        switch(num){
            case '1':
                return "";
            case '2':
                return "abc";
            case '3':
                return "def";
            case '4':
                return "ghi";
            case '5':
                return "jkl";
            case '6':
                return "mno";
            case '7':
                return "pqrs";
            case '8':
                return "tuv";
            case '9':
                return "wxyz";
            default:
                return "";
        }
    }
}


下面是用queue的。

public class Solution {
    public List<String> letterCombinations(String digits) {
        List<String> list = new ArrayList<String>();
        Queue<String> queue = new LinkedList<String>();
        String empty = "";
        queue.add(empty);
        for(int i = 0;i < digits.length();i++){
            String chars = digit2chars(digits.charAt(i));
                int preLen = queue.size();
                for(int t = 0;t < preLen;t++){
                    String pre = queue.poll();
                    for(int j = 0;j < chars.length();j++){
                        String s = new String(pre+chars.charAt(j));
                        queue.add(s);
                    }
                }
        }
        list.addAll(queue);
        return list;
    }
}