[LeetCode 1177] Can Make Palindrome from Substring

Given a string s, we make queries on substrings of s.

For each query queries[i] = [left, right, k], we may rearrange the substring s[left], ..., s[right], and then choose up to k of them to replace with any lowercase English letter. 

If the substring is possible to be a palindrome string after the operations above, the result of the query is true. Otherwise, the result is false.

Return an array answer[], where answer[i] is the result of the i-th query queries[i].

Note that: Each letter is counted individually for replacement so if for example s[left..right] = "aaa", and k = 2, we can only replace two of the letters.  (Also, note that the initial string s is never modified by any query.)

Example :

Input: s = "abcda", queries = [[3,3,0],[1,2,0],[0,3,1],[0,3,2],[0,4,1]]
Output: [true,false,false,true,true]
Explanation:
queries[0] : substring = "d", is palidrome.
queries[1] : substring = "bc", is not palidrome.
queries[2] : substring = "abcd", is not palidrome after replacing only 1 character.
queries[3] : substring = "abcd", could be changed to "abba" which is palidrome. Also this can be changed to "baab" first rearrange it "bacd" then replace "cd" with "ab".
queries[4] : substring = "abcda", could be changed to "abcba" which is palidrome.

Because we can freely rearrange any letters in a given substring, for a query on s[i, j], its result is determined by the relation between the parity sum of all letters in s[i, j] and k. If ParitySum / 2 <= k, the result is true; otherwise the result is false.  Another property of this problem is that since there are only at most 26 unique letters, if k >= 13, the query result will always be true. We can use this to prune queries with k >= 13. 

 

There are a few solutions to solve this problem. 

 

Solution 1.  Binary Search; O(s.length + queries.length * 26 * log(s.length)) runtime; O(s.length) space

1. For each letter from a to z, create a sorted list of their index in s. 

2. For each query on s[i, j], find the total sum of letters whose ocurrence count is odd. Do a binary search on each letter's index list to find the occurence count.

3. if sum / 2 <= k, query returns true otherwise false.

class Solution {
    public List<Boolean> canMakePaliQueries(String s, int[][] queries) {
        List<Boolean> res = new ArrayList<>();
        List<Integer>[] indices = new List[26];
        for(int i = 0; i < 26; i++) {
            indices[i] = new ArrayList<>();
        }
        //O(s.length)
        for(int i = 0; i < s.length(); i++) {
            indices[s.charAt(i) - 'a'].add(i);
        }
        //O(queries.length * 26 * log(s.length))
        for(int i = 0; i < queries.length; i++) {
            res.add(canMake(indices, queries[i][0], queries[i][1], queries[i][2]));
        }
        return res;
    }
    private boolean canMake(List[] indices, int left, int right, int k) {
        int sum = 0;
        //O(26 * log(s.length))
        for(int i = 0; i < 26; i++) {
            List<Integer> list = indices[i];
            int r = getRightBound(list, right);
            int l = getLeftBound(list, left);
            if(l >= 0 && r >= 0 && l <= r) {
                sum += (r -l + 1) % 2;
            }
        }
        return sum / 2 <= k;
    }
    private int getLeftBound(List<Integer> list, int target) {
        if(list.size() == 0) {
            return -1;
        }
        int left = 0, right = list.size() - 1;
        while(left < right - 1) {
            int mid = left + (right - left) / 2;
            if(list.get(mid) < target) {
                left = mid + 1;
            }
            else {
                right = mid;
            }
        }
        if(list.get(left) >= target) {
            return left;
        }
        else if(list.get(right) >= target) {
            return right;
        }
        return -1;
    }
    private int getRightBound(List<Integer> list, int target) {
        if(list.size() == 0) {
            return -1;
        }
        int left = 0, right = list.size() - 1;
        while(left < right - 1) {
            int mid = left + (right - left) / 2;
            if(list.get(mid) > target) {
                right = mid - 1;
            }
            else {
                left = mid;
            }
        }
        if(list.get(right) <= target) {
            return right;
        }
        else if(list.get(left) <= target) {
            return left;
        }
        return -1;
    }    
}

 

Solution 2. TreeMap; Same idea with Solution 1 but use TreeMap to find the occurence count of a letter in s[i, j]. 

Each treemap stores the mapping relation from a letter's index in s to this index's relative position in current letter's occurence throughout s. 

class Solution {
    public List<Boolean> canMakePaliQueries(String s, int[][] queries) {
        List<Boolean> res = new ArrayList<>();
        TreeMap<Integer, Integer>[] indices = new TreeMap[26];
        for(int i = 0; i < 26; i++) {
            indices[i] = new TreeMap<>();
        }
        //O(s.length)
        for(int i = 0; i < s.length(); i++) {
            int idx = s.charAt(i) - 'a';
            indices[idx].put(i, indices[idx].size());
        }
        //O(queries.length * 26 * log(s.length))
        for(int i = 0; i < queries.length; i++) {
            res.add(canMake(indices, queries[i][0], queries[i][1], queries[i][2]));
        }
        return res;
    }
    private boolean canMake(TreeMap[] indices, int left, int right, int k) {
        int sum = 0;
        //O(26 * log(s.length))
        for(int i = 0; i < 26; i++) {
            TreeMap<Integer, Integer> map = indices[i];
            Map.Entry<Integer, Integer> leftBound = map.ceilingEntry(left);
            Map.Entry<Integer, Integer> rightBound = map.floorEntry(right);
            if(leftBound != null &&  rightBound != null && leftBound.getValue() <= rightBound.getValue()) {
                sum += ((rightBound.getValue() -leftBound.getValue() + 1) % 2);
            }          
        }
        return sum / 2 <= k;
    } 
}

 

Solution 3. PrefixSum; O(26 * s.length + 26 * queries.length) runtime; O(26 * s.length) space

 

1. Compute prefix sum of occurences for all 26 letters; prefixSum[i][j] represents the occurence count of letter 'a' + i in substring s[0, j]. To find out the occurence count of a letter in substring s[j1, j2], we do prefixSum[i][j2] - prefixSum[i][j1 - 1] or 0 if j1 == 0.

2. For each query, sum up the number of letters whose count is odd within the substring.

3. if sum / 2 <= k, query returns true otherwise false.

 
class Solution {
    public List<Boolean> canMakePaliQueries(String s, int[][] queries) {
        List<Boolean> res = new ArrayList<>();
        int n = s.length();
        int[][] prefixSum = new int[26][n];
        for(int i = 0; i < n; i++) {
            int idx = s.charAt(i) - 'a';
            for(int j = 0; j < 26; j++) {
                if(j == idx) {
                    prefixSum[j][i] = (i == 0 ? 0 : prefixSum[j][i - 1]) + 1;
                }
                else {
                    prefixSum[j][i] = (i == 0 ? 0 : prefixSum[j][i - 1]);
                }
            }
        }
        
        for(int i = 0; i < queries.length; i++) {
            if(queries[i][2] >= 13) {
                res.add(true);
                continue;
            }
            int sum = 0;
            for(int j = 0; j < 26; j++) {
                sum += (prefixSum[j][queries[i][1]] - (queries[i][0] == 0 ? 0 : prefixSum[j][queries[i][0] - 1])) % 2;
            }
            res.add(sum / 2 <= queries[i][2]);
        }
        return res;
    }
}

 

Solution 4. Xor bitwise operation.

 Same idea with prefix sum of each letter's occurence count in a substring. Because there are only 26 lower case English letters and we only care about the parity of each letter in a substring, we can use bitmap of an integer instead of using a 2D array for each letter. Each bit represents a letter's parity, 1 for odd, 0 for even. To get a letter's parity in s[i, j], we do prefixParity[j] ^ prefixParity[i - 1]. 

Why it works? 

prefixParity[i]: the parity of all letters in s[0, i]. Given s[i, j], for each letter, we have two possible cases:

Either it has odd or even parity in both s[0, i] and s[0, j]. In this case, s[i, j] must have a parity of 0 for this letter. (even - even = even, odd - odd = even)

Or it does not have the same parity. In this case, s[i, j] must have a parity of 1 for this letter. (odd - even = odd, even - odd = odd)

For two bits, xor returns 1 if they are different, 0 otherwise.

 

class Solution {
    public List<Boolean> canMakePaliQueries(String s, int[][] queries) {
        List<Boolean> res = new ArrayList<>();
        int n = s.length();
        int[] prefixParity = new int[n];
        prefixParity[0] = (1 << (s.charAt(0) - 'a'));
        for(int i = 1; i < n; i++) {
            prefixParity[i] = prefixParity[i - 1] ^ (1 << (s.charAt(i) - 'a'));
        }
        
        for(int i = 0; i < queries.length; i++) {
            if(queries[i][2] >= 13) {
                res.add(true);
            }
            else {
                int parity = queries[i][0] == 0 ? prefixParity[queries[i][1]] : prefixParity[queries[i][1]]^prefixParity[queries[i][0] - 1];
                res.add(Integer.bitCount(parity) / 2 <= queries[i][2]);             
            }
        }
        return res;
    }
}

 



posted @ 2019-09-04 23:28  Review->Improve  阅读(862)  评论(0编辑  收藏  举报