题目:Given a list of unique words, find all pairs of distinct indices (i, j) in the given list, so that the concatenation of the two words, i.e. words[i] + words[j] is a palindrome.
Example 1: Given words = [“bat”, “tab”, “cat”] Return [[0, 1], [1, 0]] The palindromes are [“battab”, “tabbat”]
Example 2: Given words = [“abcd”, “dcba”, “lls”, “s”, “sssll”] Return [[0, 1], [1, 0], [3, 2], [2, 4]] The palindromes are [“dcbaabcd”, “abcddcba”, “slls”, “llssssll”]
有题目可知,需要求出所有拼接后满足回文字符串的字符串组合。且考虑字符串的先后顺序。那么最简单的思路就是写个嵌套循环,判断两个字符串交替拼接后是否满足回文条件即可。这里先把程序中会重复使用的判断一个字符串是否为回文的函数写下来,如下:
public static boolean isPalindrome (String word){ for(int i=0, j=word.length()-1; i<j; i++, j--){ if(word.charAt(i) == word.charAt(j)) continue; else return false; } return true; }思路一:嵌套循环,这种方法时间复杂度很高,提交代码会提示超时。
public static List<List<Integer>> palindromePairs(String[] words) { List<List<Integer>> res = new ArrayList<>(); for(int i=0; i<words.length-1; i++) for(int j=i+1; j<words.length; j++){ String word = words[i] + words[j]; if(isPalindrome(word)){ List<Integer> tmp = new ArrayList<>(); tmp.add(i); tmp.add(j); res.add(tmp); } String word1 = words[j] + words[i]; if(isPalindrome(word1)){ List<Integer> tmp = new ArrayList<>(); tmp.add(j); tmp.add(i); res.add(tmp); } } return res; }那么就改进一下,我们首先使用map来保存所有字符串,然后再遍历,对每个字符串判断其头和尾(分别对应前后两种拼接方式)的反转是否已经存在于map中,若存在,则接着判断该字符串剩下的一部分是否满足回文条件,若满足则记录,否则返回判断下一个。代码入下,这种方法可以击败10%左右的用户。
public static List<List<Integer>> palindromePairs1(String[] words) { List<List<Integer>> res = new ArrayList<>(); Map<String, Integer> map = new HashMap<>(); for(int i=0; i<words.length; i++) map.put(words[i], i); for(int i=0; i<words.length; i++){ int l=0, r=0; while(l<=r){ String s = words[i].substring(l, r); Integer j = map.get(new StringBuilder(s).reverse().toString()); if(j != null && i != j && isPalindrome(words[i].substring(l == 0 ? r : 0, l == 0 ? words[i].length() :l))) res.add(Arrays.asList(l == 0 ? new Integer []{i, j} : new Integer[]{j, i})); if(r < words[i].length()) ++r; else ++l; } } return res; }上面这种方法运行时间较长的原因主要是其在判断一个字符串是使用了两个游标l,r。这导致我们对每个字符串都需要便利两边才可以。所以一种改进的方法就是只使用一个下标,然后同时得到0->j和j->n两个子串。这样提高了效率,可以击败68%的用户。代码入下:
public List<List<Integer>> palindromePairs2(String[] words) { List<List<Integer>> ret = new ArrayList<>(); if (words == null || words.length < 2) return ret; Map<String, Integer> map = new HashMap<String, Integer>(); for (int i=0; i<words.length; i++) map.put(words[i], i); for (int i=0; i<words.length; i++) { // System.out.println(words[i]); for (int j=0; j<=words[i].length(); j++) { // notice it should be "j <= words[i].length()" String str1 = words[i].substring(0, j); String str2 = words[i].substring(j); if (isPalindrome(str1)) { String str2rvs = new StringBuilder(str2).reverse().toString(); if (map.containsKey(str2rvs) && map.get(str2rvs) != i) { List<Integer> list = new ArrayList<Integer>(); list.add(map.get(str2rvs)); list.add(i); ret.add(list); // System.out.printf("isPal(str1): %s\n", list.toString()); } } if (isPalindrome(str2)) { String str1rvs = new StringBuilder(str1).reverse().toString(); // check "str.length() != 0" to avoid duplicates if (map.containsKey(str1rvs) && map.get(str1rvs) != i && str2.length()!=0) { List<Integer> list = new ArrayList<Integer>(); list.add(i); list.add(map.get(str1rvs)); ret.add(list); // System.out.printf("isPal(str2): %s\n", list.toString()); } } } } return ret; }