[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.

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 }

 

前缀和prefix sum题目总结

LeetCode 题目总结

posted @ 2021-03-03 05:12  CNoodle  阅读(73)  评论(0编辑  收藏  举报