zoukankan      html  css  js  c++  java
  • [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;
        }
    }


  • 相关阅读:
    记录一次 Linux crontab 执行django 脚本 失败 的经历和解决办法
    python3 使用 django-xadmin 遇到的许多坑
    简要说明 django restframework 的交互式文档
    No application found. Either work inside a view function or push an application context.
    解决Docker容器内访问宿主机MySQL数据库服务器的问题
    《计算机网络自顶向下方法-第七版》第三章总结
    《计算机网络自顶向下方法-第七版》第七章总结
    《计算机网络自顶向下方法-第七版》第二章总结
    《计算机网络自顶向下方法-第七版》第一章总结
    深入理解Docker容器执行引擎runC
  • 原文地址:https://www.cnblogs.com/lz87/p/11458240.html
Copyright © 2011-2022 走看看