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;
}
}
来源:https://www.cnblogs.com/lz87/p/11458240.html