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對子串進行查詢。查詢塊包含三個信息,left,right 和k,其中 left 和 right 定義了子串的范圍,k表示可以進行替換字母的個數。這里希望通過替換可以將子串變為回文串,關於回文串想必各位刷題老司機都不陌生吧,就是正讀反讀都一樣,比如 noon,bob 等等。題目中還說了可以事先給子串進行排序,這個條件一加,整個性質就不一樣了,若不能排序,那么要替換的字母可能就很多了,因為對應的位置上的字母要相同才行。而能排序之后,只要把相同的字母盡可能的排列到對應的位置上,就可以減少要替換的字母,比如 hunu,若不能重排列,則至少要替換兩個字母才行,而能重排順序的話,可以先變成 uhnu,再替換中間的任意一個字母就可以了。而仔細觀察,需要替換的情況都是字母出現次數為奇數的情況,偶數的字母完全不用擔心,所以只要統計出出現次數為奇數的字母的個數,其除以2就是要替換的次數。那可能有的童鞋會問了,萬一是奇數怎么辦,除以2除不盡怎么辦,這是個好問題。若出現次數為奇數的字母的個數為奇數,則表明該子串的長度為奇數,而奇數回文串最中間的字母是不需要有對稱位置的,所以自然可以少替換一個,所以除不盡的部分就自動舍去了,並不影響最終的結果。講到這里,應該就不難做了,但博主最先寫的解法超時 TLE 了,做法是取出每個要查詢的子串,然后統計出現奇數次的字母個數,再除以2跟k比較。這種方法並不高效,可能會存在大量的重復計算。就比如求子數組之和一樣,若對於每個給定的子數組,都遍歷一遍求和,確實不高效,一般都是建立累加和數組來做。這里也可以借鑒類似的想法,不過這里是對每個子串都建立字母出現次數的映射,所以這里用一個二維數組,大小為 n+1 by 26,因為限定了只有小寫字母。然后遍歷字符串s進行更新,每次先將 cnt[i+1] 賦值為 cnt[i],然后在對應的字母位置映射值自增1。累加好了之后,對於任意區間 [i, j] 的次數映射數組就可以通過 cnt[j+1] - cnt[i] 來表示,但數組之間不好直接做減法,可以再進一步訪問每個字母來分別進行處理,快速得到每個字母的出現次數后除以2,將結果累加到 sum 中,就是出現奇數次字母的個數了,再除以2和k比較即可,參見代碼如下:
class Solution {
public:
vector<bool> canMakePaliQueries(string s, vector<vector<int>>& queries) {
vector<bool> res;
vector<vector<int>> cnt(s.size() + 1, vector<int>(26));
for (int i = 0; i < s.size(); ++i) {
cnt[i + 1] = cnt[i];
++cnt[i + 1][s[i] - 'a'];
}
for (auto &query : queries) {
int sum = 0;
for (int i = 0; i < 26; ++i) {
sum += (cnt[query[1] + 1][i] - cnt[query[0]][i]) % 2;
}
res.push_back(sum / 2 <= query[2]);
}
return res;
}
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/1177
參考資料:
https://leetcode.com/problems/can-make-palindrome-from-substring/