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.
Constraints:
1 <= s.length, queries.length <= 10^5
0 <= queries[i][0] <= queries[i][1] < s.length
0 <= queries[i][2] <= s.length
-
s
only contains lowercase English letters.
构建回文串检测。
给你一个字符串 s,请你对 s 的子串进行检测。
每次检测,待检子串都可以表示为 queries[i] = [left, right, k]。我们可以 重新排列 子串 s[left], ..., s[right],并从中选择 最多 k 项替换成任何小写英文字母。
如果在上述检测过程中,子串可以变成回文形式的字符串,那么检测结果为 true,否则结果为 false。
返回答案数组 answer[],其中 answer[i] 是第 i 个待检子串 queries[i] 的检测结果。
注意:在替换时,子串中的每个字母都必须作为 独立的 项进行计数,也就是说,如果 s[left..right] = "aaa" 且 k = 2,我们只能替换其中的两个字母。(另外,任何检测都不会修改原始字符串 s,可以认为每次检测都是独立的)
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/can-make-palindrome-from-substring
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路是前缀和 + 位运算。题意是根据每个不同的query,你截取出一个子串,判断这个子串能否在至多替换掉 K 个字母之后形成一个回文串。这个题判断回文的条件没有那么苛刻,因为在截取子串之后,题目是允许你对子串内的字母顺序进行调换的。既然是回文,那么涉及到的字母必然大多是出现偶数次,只有至多一个字母的出现次数是奇数次。这道题的暴力解是在截取了子串之后,用一个 hashset 遍历子串,在遇到重复字母的时候,把这个字母从 hashset 中 remove,这样当扫描完毕之后,hashset 内就只剩下出现次数为奇数的字母了。如果此时K能允许你替换掉hashset中一半的字母,则这个子串可以被重新组合成一个回文。举个例子,如果子串是 abcde,K = 2。这里涉及到的五个字母的出现次数其实都是一次,但是如果我们可以把de替换成ba,那么最后的结果 abcba 是可以组成一个合法的回文串的。但是由于input字符串有可能非常长,所以这个思路是会超时的。
这里优化的方法是,我们对于需要判断的子串中不同字母的出现次数的奇偶情况,用前缀和来判断。类似560题,我们创建一个长度为 s.length() + 1 的数组来判断前 i 个字母组成的子串的二进制的前缀和。这里利用到二进制是因为对于同样一个字母,如果出现一次,那么他的二进制表达就是 1,但是出现两次的时候,我们可以通过 XOR 异或运算,使那一位又变为0同时不产生进位。举个例子,如果 i 位置上出现一个字母a,那么count[i] = 1 << s.charAt(i) - 'a'。如果 i + 1 位置上又出现一个字母a,因为是同样的字母,所以左移的次数是一样的,所以count[i + 1] = count[i] ^ (1 << s.charAt(i) - 'a')。那么二进制数字上记录 a 的那一位就又变回0了。
用这样的方式记录好input字符的二进制前缀和之后,我们对于需要求的子串,就能以O(1)的代价得到这个子串内字母的二进制表达。之后我们统计一下这个子串内有多少个1(也就是在统计这个子串内有多少个落单的字母),如果其中有一半能被K替换掉,则这个子串就可以满足题意变成回文串。
时间O(n)
空间O(n)
Java实现
1 class Solution { 2 public List<Boolean> canMakePaliQueries(String s, int[][] queries) { 3 List<Boolean> res = new ArrayList<>(); 4 int[] count = new int[s.length() + 1]; 5 for (int i = 0; i < s.length(); i++) { 6 count[i + 1] = count[i] ^ (1 << s.charAt(i) - 'a'); 7 } 8 9 for (int[] q : queries) { 10 int sum = 0; 11 sum = Integer.bitCount(count[q[1] + 1] ^ count[q[0]]); 12 res.add(sum / 2 <= q[2]); 13 } 14 return res; 15 } 16 }