搞定字串類面試題-Palindrome

_auguskong_發表於2019-02-07

409 Longest Palindrome[Easy]

  • 題目: 給出一個字串,求這個字串能夠組合出來的最長迴文串
  • 思路: 不能直接用一個hash陣列來做記錄奇偶數,因為會出現多個總數為奇數的字元和一個總數為奇數的長字元。
  • 還要進行判斷建一個HashSet需要挑出最長的奇數字符串 沒有將奇數字符串中的成對出現的偶數部分加入到最後的結果中。
class Solution {
    public int longestPalindrome(String s) {
        if (s == null || s.length() == 0) {
            return 0;
        }
        HashSet<Character> set = new HashSet<>();
        int count = 0;
        for (int i = 0; i < s.length(); i++) {
            if (set.contains(s.charAt(i))) {
                set.remove(s.charAt(i));
                count++;
            } else {
                set.add(s.charAt(i));
            }
        }
        if (set.isEmpty()) {
            return count * 2;
        }
        return count * 2 + 1;
    }
}
複製程式碼

9. Palindrome Number[Easy]

  • 題目: 判斷一個整數是不是迴文的
  • 思路: 先將整數轉為一個字串陣列,兩個指標從兩端向中間走
class Solution {
    public boolean isPalindrome(int x) {
        String nums = Integer.toString(x);
        int left = 0;
        int right = nums.length() - 1;
        while (left < right) {
            if (nums.charAt(left) != nums.charAt(right)) {
                return false;
            } else {
                left++;
                right--;
            }
        }
        return true;
    }
}
複製程式碼

234. Palindrome Linked List[Easy]

  • 題目: 判斷一個單向連結串列是否是迴文串
  • 思路: 複製一個相同的連結串列,進行一個reverse操作,然後用兩個指標從兩個連結串列的頭分別向後移動並逐個比對 O(n)的空間複雜度 O(n)的時間複雜度。 優化:先用快慢指標找到該連結串列的中點,將後半段reverse,然後用兩個指標同向移動
class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode slow = head;
        ListNode fast = head;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
        }
        if (fast != null) { //這裡需要做一個奇偶的判斷,如果fast不為null,那麼slow需要向前移動一位,跳過中間值
            slow = slow.next;
        }
        slow = reverse(slow);
        ListNode left = head;
        ListNode right = slow;
        while (right != null) {
            if (left.val != right.val) {
                return false;
            }
            left = left.next;
            right = right.next;
        }
        return true;
    }
    
    private ListNode reverse(ListNode node) {
        ListNode prev = null;
        ListNode curr = node;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }
}
複製程式碼

49. Group Anagrams[Medium]

  • 思路: 用一個hashtable來存 陣列來存一個集合? 如何能找到不同字母組合的一致性? 只需要包含相同的字母即可, 順序沒有關係
  • 需要的是一個flag來做記錄 兩個選擇: 1. 類似a1b2c3的結構 2.直接排序得到有序結果
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        if (strs == null || strs.length == 0) {
            return new ArrayList<>();
        }
        List<List<String>> res = new ArrayList<>();
        Map<String, List<String>> map = new HashMap<>();
        int[] count = new int[26];
        
        for (String str : strs) {
            Arrays.fill(count, 0);
            for (char c : str.toCharArray()) {
                count[c - `a`]++;
            }
            StringBuilder sb = new StringBuilder();
            for (int i = 0; i < 26; i++) {
                if (count[i] != 0) {
                    sb.append((char)(i + `a`)).append(count[i]);
                }
            }
            String flag = sb.toString();
            if (!map.containsKey(flag)) {
                map.put(flag, new ArrayList<>());
            }
            map.get(flag).add(str);
        }
        return new ArrayList<>(map.values());
    }
}
複製程式碼

266.Palindrome Permutation[Easy]

  • 題目:給出一個字串,判斷該字串的permutation能否組成給一個迴文串
  • 思路: 開一個長度256的陣列來記錄每個字元出現的次數,最多僅允許存在一個出現奇數次的字元
class Solution {
    public boolean canPermutePalindrome(String s) {
        if (s.length() == 0) {
            return true;
        }
        boolean oneOdd = false;
        int[] counts = new int[256];
        for (int i = 0; i < s.length(); i++) {
            counts[s.charAt(i)]++; //這裡直接用s.charAt(i)作為ASCII編碼
        }
        
        for (int k = 0; k < counts.length; k++) {
            if (counts[k] % 2 != 0) {                
                if (oneOdd == true) {
                    return false;
                }
                else {
                    oneOdd = true;
                }
            }
        }
        
        return true;
    }
}
複製程式碼

267. Palindrome PermutationII [Medium]

  • 思路: Permutation 如何找到? DFS + backtracking, 迴文串的permuration是通過兩個指標進行簡單的映象對稱來實現的 在找到的所有的permutation當中判斷出來哪些是Palindrome放到res當中。效率太低。

需要判斷的是字串的奇偶性, 用一個oneOdd變數來做標記,如果是奇數個, 最中間一定是一個單字元, 雙指標的起點是i-1i+1,如果是偶數個, 起點為ii + 1

public class Solution {
    public List<String> generatePalindromes(String s) {
        int[] hash = new int[256];
        for(int i = 0; i < s.length(); i++){
            int index = (int) s.charAt(i);
            hash[index] ++;
        }
        char center = `.`;
        boolean oneOdd = false;
        for(int i = 0; i < 256; i++){
            if(hash[i] % 2 == 0){
                continue;
            } else {
                if(oneOdd) return new ArrayList<String>(); //奇數字符多於一個直接return空字元
                oneOdd = true;
                center = (char) i;
                hash[i] --;
            }
        }

        char[] array = new char[s.length()];
        List<String> list = new ArrayList<String>();
        if(oneOdd) {
            array[s.length() / 2] = center;
            dfs(list, array, hash, s.length() / 2 - 1, s.length() / 2 + 1);
        } else {
            dfs(list, array, hash, s.length() / 2 - 1, s.length() / 2);    
        }

        return list;
    }

    private void dfs(List<String> list, char[] array, int[] hash, int left, int right){
        if(left < 0 || right >= array.length){
            list.add(new String(array));
            return;
        }
        for(int i = 0; i < 256; i++){
            if(hash[i] <= 0) continue;

            array[left] = (char) i;
            array[right] = (char) i;
            hash[i] -= 2;

            dfs(list, array, hash, left - 1, right + 1);

            array[left] = `.`;
            array[right] = `.`;
            hash[i] += 2;
        }
    }
}
複製程式碼

5. Longest Palindromic Substring[Medium]

  • 題目: 找出最長的迴文子串
  • 思路: 中心點列舉直接做,寫一個得到迴文串長度的function, Corner Case是隻有一個字元和只有兩個字元時的情況。while迴圈的終點根據指標最後停下的位置來確定。
class Solution {
    public String longestPalindrome(String s) {
        if (s == null || s.length() == 0) {
            return "";
        }
        int start = 0;
        int longest = 0;
        int currLen = 0;
        for (int i = 0; i < s.length(); i++) {
            currLen = calculatePalindromeLength(s, i, i);
            if (currLen > longest) {
                longest = currLen;
                start = i - currLen / 2;
            }
            currLen = calculatePalindromeLength(s, i, i+1);
            if (currLen > longest) {
                longest = currLen;
                start = i - currLen / 2 + 1;
            }
        }
        
        return s.substring(start, start + longest);
    }
    
    private int calculatePalindromeLength(String s, int left, int right) {
        int len = 0;
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            len += left == right ? 1 : 2;
            left--;
            right++;
        }
        return len;
    }
}
複製程式碼

647. Palindromic Substrings

  • 題目: 給一個字串,找到所有的迴文子串,可以包含重複元素
  • 思路: 和Palindrome Partitioning一樣, 找出所有的子串, subset,判斷每個子串是否是Palindrome,不需要使用回溯找subset,直接用中心點列舉遍歷所有substring即可
public class Solution {
    int count = 0;
    
    public int countSubstrings(String s) {
        if (s == null || s.length() == 0) return 0;
        
        for (int i = 0; i < s.length(); i++) { // i is the mid point
            extendPalindrome(s, i, i); // odd length;
            extendPalindrome(s, i, i + 1); // even length
        }
        
        return count;
    }
    
    private void extendPalindrome(String s, int left, int right) {
        while (left >=0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            count++; 
            left--; 
            right++;
        }
    }
}
複製程式碼

131. Palindrome Partitioning[Medium]

  • 題目: 給出一個字串s,求出切分s得到所有的s都是迴文串,並將迴文串新增到List當中返回
  • 思路: 使用backtracking,找出不同的切分方法
class Solution {
    public List<List<String>> partition(String s) {
        List<List<String>> res = new ArrayList<>();
        helper(s, res, new ArrayList<>(), 0);
        return res;
    }
    
    public void helper(String s, 
                       List<List<String>> res, 
                       List<String> currList, 
                       int start) {
        if (currList.size() > 0 && start >= s.length()) {
            res.add(new ArrayList(currList));
        }
        for (int i = start; i < s.length(); i++) {
            if (isPalindrome(s, start, i)) {
                currList.add(s.substring(start, i + 1));
                helper(s, res, currList, i + 1);
                currList.remove(currList.size() - 1);
            }
            
        }
    }
    
     private boolean isPalindrome(String s, int i, int j) {
        while (i < j) {
            if (s.charAt(i) != s.charAt(j)) {
                return false;
            }
            i++;
            j--;
        }
        return true;
    }
}
複製程式碼

132. Palindrome Partitioning II

  • 題目: 給出一個字串s, 求出最少需要切割幾次才能夠讓所有的substring都是迴文串
  • 思路:

336. Palindrome Pairs[Hard]

  • 題目: 給出一個String List words,找出所有能夠拼接為一個迴文串的兩個String的index並存在列表之中返回
  • 思路: 如何判斷兩個String能夠拼接為一個迴文串? 如何找到所有的字串?
    • 如何判斷兩個String能夠拼接為一個迴文串? 先拼接 再直接雙指標遍歷做判斷即可 分類討論:
      • case 1: blank string, 如果words當中存在迴文串,那麼該回文串+空串就可以作為一對結果
      • case 2: 對稱拼接 s2 是 s1 的reverse
      • case 3: 非對稱拼接: s2的一部分和s1的一部分對稱 或 s1的一部分和s2的一部分對稱
 class Solution {
    public List<List<Integer>> palindromePairs(String[] words) {
        
        List<List<Integer>> res = new ArrayList<>();
        HashMap<String, Integer> map = new HashMap<>();
        int n = words.length;
        for (int i = 0; i < n; i++) {
            map.put(words[i], i);
        }
        // case 1: blank 
        if (map.containsKey("")) {
            int blankIdx = map.get("");
            for (int i = 0; i < n; i++) {
                if (isPalindrome(words[i])) {
                    if (i == blankIdx) {
                       continue; 
                    }
                    res.add(Arrays.asList(blankIdx, i));
                    res.add(Arrays.asList(i, blankIdx));
                }
            }
        }
        
        // case 2: found the reverse string
        for (int i = 0; i < n; i++) {
            String reverseStr = reverse(words[i]);
            if (map.containsKey(reverseStr)) {
                int found = map.get(reverseStr);
                if (found == i) continue;
                res.add(Arrays.asList(i, found));
            }
        }
        
        //case 3: nonsymmetric 
        for (int i = 0; i < n; i++) {
            String cur = words[i];
            for (int cut = 1; cut < cur.length(); cut++) {
                if (isPalindrome(cur.substring(0, cut))) {
                    String reverseCut = reverse(cur.substring(cut));
                    if(map.containsKey(reverseCut)) {
                        int found = map.get(reverseCut);
                        if (found == i) {
                            continue;
                        }
                        res.add(Arrays.asList(found, i));
                    }
                }
                
                if (isPalindrome(cur.substring(cut))) {
                    String reverseCut = reverse(cur.substring(0, cut));
                    if(map.containsKey(reverseCut)) {
                        int found = map.get(reverseCut);
                        if (found == i) {
                            continue;
                        }
                        res.add(Arrays.asList(i, found));
                    }
                }
            }
        }
        return res;
    }
    
    private String reverse(String s) {
        StringBuilder sb = new StringBuilder(s);
        return sb.reverse().toString();
    }
    
    private boolean isPalindrome(String s) {
        int i = 0;
        int j = s.length() - 1;
        while (i < j) {
            if (s.charAt(i) != s.charAt(j)) {
                return false;
            }
            i++;
            j--;
        }
        return true;
    }
}
複製程式碼

相關題目:

46. Permutations [Medium]

  • 思路: Backtracking, 使用一個輔助的used陣列來進行判斷
class Solution {
    public List<List<Integer>> permute(int[] nums) {
        List<List<Integer>> res = new ArrayList<>();
        if (nums == null || nums.length == 0) {
            return res;
        }
        int n = nums.length;
        boolean[] used = new boolean[n];
        helper(res, new ArrayList<>(), used, nums);
        return res;
    }
    
    private void helper(List<List<Integer>> res, List<Integer> curr, boolean[] used, int[] nums) {
        if (curr.size() == nums.length) {
            res.add(new ArrayList(curr));
        }
        for (int i = 0; i < nums.length; i++) {
            if (!used[i]) {
                curr.add(nums[i]);
                used[i] = true;
                helper(res, curr, used, nums);
                used[i] = false;
                curr.remove(curr.size() - 1);
            }
        }
    }
}
複製程式碼

有重複元素

Arrays.sort(nums) // 排序這樣所有重複的數
if (i > 0 && nums[i] == nums[i - 1] && used[i - 1]) { continue; } // 跳過會造成重複的情況
複製程式碼

78. Subsets [Medium]

  • 思路: for 迴圈的起點不同,Permutation是必須利用每一個字元的,所以每次迴圈都從字串的起點i = 0開始遍歷,而Subset問題是不能回頭的,所以不管之前的字元是否使用過,都只能從當前字元往後走,每次迴圈的起點是i = start, 同時遞迴function當中的起點也應該是i + 1
class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> results = new ArrayList<>();
    	Arrays.sort(nums);
    	helper(results, new ArrayList<>(), nums, 0);
    	return results;       
    }
// 1.遞迴的定義: 在nums中找到所有的subset,每個元素都有兩個選擇, 加入和不加入
    private void helper(List<List<Integer>> results,
                        List<Integer> subset,
                        int[] nums,
                        int start) {
    //2. 遞迴的拆解 deep copy -> create new subset ArrayList
    //每一層都加當前的新元素
    //新增順序: [] [1] [1,2] [1,2,3] [1,3] [2] [2,3] [3]
    
    results.add(new ArrayList(subset));
    for (int i = start; i < nums.length; i++) {
    	//[] -> [1] or [1] -> [1, 2]
    	subset.add(nums[i]);
    	helper(results, subset, nums, i + 1);
    	subset.remove(subset.size() - 1);
    }
    // 3. 遞迴的出口
    // 當for迴圈的條件不滿足時,直接什麼都不執行 => return 
    }
}
複製程式碼

相關文章