搞定字符串类面试题-Palindrome

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 
    }
}
复制代码

转载于:https://juejin.im/post/5c51823ce51d4556940c282a

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值