Wednesday, November 25, 2015

Airbnb: Palindromic pair of a list of strings

Given a list of strings, find all the palindromic pairs of the string and output the concatenated palindrome.

e.g. [abc, cba], output is abccba, cbaabc.
e.g. [aabc, cb], output is cbaabc



Understand the problem:

The brute-force solution to this problem is very simple. For each word, we search all the others and see if it can form a palindrome. Assume that the ave. length of each word is m and there are totally n words in the list, the time complexity would be O(n^2 * m). 

Solution:
1. Put all the reversed order of the input string into a Map. The key is the reversed order of the string, and the value is the indices of the word
2. For each word, get all its prefixes, If the prefix is in the map AND the rest of the string is a palindrome, then we can find a pair where the first half is the current word, and the second half is the reversed order of prefix.
3. For each word, get all its postfixes. If the postfix is in the map AND the first half of the string is palindrome, then we can find a pair where the reversed order of the postfix is the first part, and the word is the second part of the pair. 

The reason why we need to track the position of each word is to avoid the situation like ["c"], i.e. the word itself is a palindrome. Then we may mistakely concatenate a "cc" as a palindrome. So we can concatenate two words IFF
1. The key in the map is different from the current word
2. If they are the same, they must have different indices. 

Code (Java):

import java.io.*;
import java.util.*;

public class Solution{
  List<String> getPalindromaticPairs(String[] input) {
    Set<String> result = new HashSet<>();
    if (input == null || input.length == 0) {
      return new ArrayList<>();
    }
    
    // Step 1: put the reversed order of each word into the map
    Map<String, List<Integer>> map = new HashMap<>();
    
    for (int i = 0; i < input.length; i++) {
      String str = input[i];
      String reStr = reverse(str);
      if (!map.containsKey(reStr)) {
        List<Integer> indices = new ArrayList<>();
        indices.add(i);
        map.put(reStr, indices);
      } else {
        List<Integer> indices = map.get(reStr);
        indices.add(i);
      }
    }
    
    // Step 2: Iterate each word
    for (int i = 0; i < input.length; i++) {
      String str = input[i];
      
      // Get all the prefix of str, and append to the end
      for (int j = 1; j <= str.length(); j++) {
        String prefix = str.substring(0, j);
        String postfix = str.substring(j);
        
        if (map.containsKey(prefix) && isPalindrome(postfix)) {
          if (map.get(prefix).size() > 1 || !map.get(prefix).equals(str)) {
            String palin = str + reverse(prefix);
            result.add(palin);
          }
        }
      }
      
      // Get all postfix of the str, and insert to front
      for (int j = str.length() - 1; j >= 0; j--) {
        String postfix = str.substring(j);
        String prefix = str.substring(0, j);
        
        if (map.containsKey(postfix) && isPalindrome(prefix)) {
          if (map.get(postfix).size() > 1 || !map.get(postfix).equals(str)) {
            String palin = reverse(postfix) + str;
            result.add(palin);
          }
        }
      }
    }
    
    return new ArrayList<String>(result);
  }
  
  private String reverse(String s) {
    if (s == null || s.length() <= 1) {
      return s;
    }
    
    char[] array = s.toCharArray();
    int lo = 0;
    int hi = array.length - 1;
    while (lo < hi) {
      char temp = array[lo];
      array[lo] = array[hi];
      array[hi] = temp;
      
      lo++;
      hi--;
    }
    
    return new String(array);
  }
  
  private boolean isPalindrome(String s) {
    if (s == null || s.length() <= 1) {
      return true;
    }
    
    int lo = 0;
    int hi = s.length() - 1;
    
    while (lo < hi) {
      if (s.charAt(lo) != s.charAt(hi)) {
        return false;
      }
      
      lo++;
      hi--;
    }
    
    return true;
  }
  
  public static void main(String[] args) {
    Solution solution = new Solution();
    String[] input = new String[]{"abc", "cba", "c", "c"};
    
    List<String> result = solution.getPalindromaticPairs(input);
    
    for (String s : result) {
      System.out.println(s);
    }
  }
}


6 comments:

  1. Hi, I think in your code, line 37, "!map.get(prefix).equals(str)", you may miswrite something since map.get(prefix) is List, which cannot be equal to str. I think you may change it to "map.get(prefix).get(0) != i". Am I right? Thanks for your posts!

    ReplyDelete
  2. Hi, I think in your code, line 37, "!map.get(prefix).equals(str)", you may miswrite something since map.get(prefix) is List, which cannot be equal to str. I think you may change it to "map.get(prefix).get(0) != i". Am I right? Thanks for your posts!

    ReplyDelete
  3. If O(n^2) is ok with no extra space, why couldnt you do this?

    /*Airbnb: Palindromic pair of a list of strings
    Given a list of strings, find all the palindromic pairs of the string and output the concatenated palindrome.

    e.g. [abc, cba], output is abccba, cbaabc.
    e.g. [aabc, cb], output is cbaabc
    */

    import java.util.*;
    public class Palindrome {


    public Collection getAllPairs(String[] input){
    Set result = new HashSet();

    for (int i = 0 ; i< input.length-1; i++){
    for (int j = i+1; j< input.length; j++) {
    checkPal(input[i], input[j], result);
    }
    }

    return result;
    }

    public void checkPal(String a, String b, Set result){
    if (isPalindrome(a+b)){
    result.add(a+b);
    }
    if (isPalindrome(b+a)){
    result.add(b+a);
    }
    }

    boolean isPalindrome(String str){
    if (str == null || str.length() == 0 ) return false;

    int i = 0;
    int j = str.length()-1;
    while (i result = solution.getAllPairs(input);

    for (String s : result) {
    System.out.println(s);
    }
    }
    }

    ReplyDelete
    Replies
    1. This algorithm is also O(N^2), is it not? The very first thing you do is double for loops, which results in O(N^2) runtime.

      Delete
    2. If the average length of each word is m, so instead of O(N^2), the runtime is O(Nm), a little improve, but if N is large and m is small, it could make a big different.

      Delete