Hard
You are given a 0-indexed string s
having an even length n
.
You are also given a 0-indexed 2D integer array, queries
, where queries[i] = [ai, bi, ci, di]
.
For each query i
, you are allowed to perform the following operations:
s[ai:bi]
, where 0 <= ai <= bi < n / 2
.s[ci:di]
, where n / 2 <= ci <= di < n
.For each query, your task is to determine whether it is possible to make s
a palindrome by performing the operations.
Each query is answered independently of the others.
Return a 0-indexed array answer
, where answer[i] == true
if it is possible to make s
a palindrome by performing operations specified by the ith
query, and false
otherwise.
s[x:y]
represents the substring consisting of characters from the index x
to index y
in s
, both inclusive.Example 1:
Input: s = “abcabc”, queries = [[1,1,3,5],[0,2,5,5]]
Output: [true,true]
Explanation: In this example, there are two queries:
In the first query:
a0 = 1, b0 = 1, c0 = 3, d0 = 5.
So, you are allowed to rearrange s[1:1] => abcabc and s[3:5] => abcabc.
To make s a palindrome, s[3:5] can be rearranged to become => abccba.
Now, s is a palindrome. So, answer[0] = true.
In the second query:
a1 = 0, b1 = 2, c1 = 5, d1 = 5.
So, you are allowed to rearrange s[0:2] => abcabc and s[5:5] => abcabc.
To make s a palindrome, s[0:2] can be rearranged to become => cbaabc.
Now, s is a palindrome. So, answer[1] = true.
Example 2:
Input: s = “abbcdecbba”, queries = [[0,2,7,9]]
Output: [false]
Explanation: In this example, there is only one query.
a0 = 0, b0 = 2, c0 = 7, d0 = 9.
So, you are allowed to rearrange s[0:2] => abbcdecbba and s[7:9] => abbcdecbba.
It is not possible to make s a palindrome by rearranging these substrings because s[3:6] is not a palindrome.
So, answer[0] = false.
Example 3:
Input: s = “acbcab”, queries = [[1,2,4,5]]
Output: [true]
Explanation: In this example, there is only one query.
a0 = 1, b0 = 2, c0 = 4, d0 = 5.
So, you are allowed to rearrange s[1:2] => acbcab and s[4:5] => acbcab.
To make s a palindrome s[1:2] can be rearranged to become abccab.
Then, s[4:5] can be rearranged to become abccba.
Now, s is a palindrome. So, answer[0] = true.
Constraints:
2 <= n == s.length <= 105
1 <= queries.length <= 105
queries[i].length == 4
ai == queries[i][0], bi == queries[i][1]
ci == queries[i][2], di == queries[i][3]
0 <= ai <= bi < n / 2
n / 2 <= ci <= di < n
n
is even.s
consists of only lowercase English letters.import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("java:S6541")
public class Solution {
private int n;
// get associated index in the other half
private int opp(int i) {
return n - 1 - i;
}
public boolean[] canMakePalindromeQueries(String s, int[][] queries) {
int[] fq = new int[26];
int m = queries.length;
boolean[] ret = new boolean[m];
n = s.length();
// check that both halves contain the same letters
for (int i = 0; i < n / 2; i++) {
fq[s.charAt(i) - 'a']++;
}
for (int i = n / 2; i < n; i++) {
fq[s.charAt(i) - 'a']--;
}
for (int em : fq) {
if (em != 0) {
return ret;
}
}
// find the first and the last characters in the first half
// that do not match with their associated character in
// the second half
int problemPoint = -1;
int lastProblem = -1;
for (int i = 0; i < n / 2; i++) {
if (s.charAt(i) != s.charAt(opp(i))) {
if (problemPoint == -1) {
problemPoint = i;
}
lastProblem = i;
}
}
// if already a palindrome
if (problemPoint == -1) {
Arrays.fill(ret, true);
return ret;
}
// the idea is that at least one of the intervals in the
// query has to cover the first pair of different characters.
// But depending on how far the other end of that interval
// goes, the requirements for the other interval are lessened
int[] dpFirst = new int[n / 2 + 1];
int[] dpSecond = new int[n + 1];
Arrays.fill(dpFirst, -1);
Arrays.fill(dpSecond, -1);
// assuming the first interval covers the first problem,
// and then extends to the right
int rptr = opp(problemPoint);
Map<Character, Integer> mp = new HashMap<>();
for (int i = problemPoint; i < n / 2; i++) {
mp.compute(s.charAt(i), (k, v) -> v == null ? 1 : v + 1);
// the burden for the left end of the second interval does not change;
// it needs to go at least until the last problematic match. But the
// requirements for the right end do. If we can rearrange the characters
// in the left half to match the right end of the right interval, this
// means we do not need the right end of the right interval to go too far
while (mp.containsKey(s.charAt(rptr))
|| (rptr >= n / 2 && s.charAt(rptr) == s.charAt(opp(rptr)) && mp.size() == 0)) {
mp.computeIfPresent(s.charAt(rptr), (k, v) -> v == 1 ? null : v - 1);
rptr--;
}
dpFirst[i] = rptr;
}
// mirrored discussion assuming it is the right interval that takes
// care of the first problematic pair
int lptr = problemPoint;
mp.clear();
for (int i = opp(problemPoint); i >= n / 2; i--) {
mp.compute(s.charAt(i), (k, v) -> v == null ? 1 : v + 1);
while (mp.containsKey(s.charAt(lptr))
|| (lptr < n / 2 && s.charAt(lptr) == s.charAt(opp(lptr)) && mp.size() == 0)) {
mp.computeIfPresent(s.charAt(lptr), (k, v) -> v == 1 ? null : v - 1);
lptr++;
}
dpSecond[i] = lptr;
}
for (int i = 0; i < m; i++) {
int a = queries[i][0];
int b = queries[i][1];
int c = queries[i][2];
int d = queries[i][3];
// if either interval the problematic interval on its side, it does not matter
// what happens with the other interval
if (a <= problemPoint && b >= lastProblem
|| c <= opp(lastProblem) && d >= opp(problemPoint)) {
ret[i] = true;
continue;
}
// if the left interval covers the first problem, we use
// dp to figure out if the right one is large enough
if (a <= problemPoint
&& b >= problemPoint
&& d >= dpFirst[b]
&& c <= opp(lastProblem)) {
ret[i] = true;
}
// similarly for the case where the right interval covers
// the first problem
if (d >= opp(problemPoint)
&& c <= opp(problemPoint)
&& a <= dpSecond[c]
&& b >= lastProblem) {
ret[i] = true;
}
}
return ret;
}
}