问题:
给定一个字符串s,
和一个字串操作数组queries
[i, j, k]
即对字符串s的i~j字符组成的子串,进行重新排列,且可从中最多(up to)选取k个字母,替换成任意字母,
使得子串能够成为回文字符串。
如果可以返回true,否则返回false。
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.
解法:
回文字符串:
特点:中间对称,前后两元素两两相同。
由于本问题中,子字符串可以进行重新排序,因此对子字符串的顺序就不需要考虑了。
只计算子字符串中,出现的字母个数,是否两两可相消。
最后不能相互抵消的字母(假如有n个)中一半(n/2),替换成已存在的字母,形成新的相消对,即可成为回文字符串。
最多需要n/2次字母替换,n/2<=k,则满足题意,可返回true。
两两相消:
这里我们想到了异或^运算:两两不同=1,两两相同=0
使用bit位法,标记字母a~z:1<<(A[i]-'a')
求字母出现的个数是否两两相消:
求前缀字符串不能相消的字母标记:prefix ^= 1<<(A[i]-'a')
i~j的子字符串:
我们想到前缀求和的算法:substring(i,j)=presum(j)-presum(i-1)
ps->presum的简写:
ps(0)=0;
ps(1)=sum(0,0);
ps(2)=sum(0,1);
ps(3)=sum(0,3);
因此:sum(i,j)=ps(j+1)-ps(i)
本题,在字母计数中,前缀求和即为:子字符串i~j,计算不能抵消的字母个数
sum(i,j) = ps(j+1) ^ ps(i)
抵消相同前缀。
得到的sum(i,j)即为子字符串中出现奇数次(不能相消)的字母标记,
要求有多少个这样的字母,即求这个标记中有多少个 1 即可。
__builtin_popcount:该函数求参数变量中有多少个 1
/2与k比较即可。
代码参考:
1 class Solution { 2 public: 3 vector<bool> canMakePaliQueries(string s, vector<vector<int>>& queries) { 4 vector<bool> res; 5 int prefix=0; 6 vector<int> ps(1); 7 //保存前缀字符串0~j=prefix 8 //presum(0)=0, sum(i~j)=presum(j)-presum(i) 9 for(char c:s){ 10 ps.push_back(prefix ^= 1<<(c-'a'));//去掉偶数个相同的字母 11 } 12 for(vector<int>q : queries){ 13 int odds = __builtin_popcount(ps[q[1]+1]^ps[q[0]]); 14 //用异或^去掉:相同的部分前缀0~i-1 15 //字符串sum(i~j)中,奇数个字母的个数 16 res.push_back(odds/2<=q[2]); 17 } 18 return res; 19 } 20 };