LeetCode第32场双周赛
第一題 1539. 第 k 個缺失的正整數(shù)
用一個數(shù)組missed記錄從小到大、所有arr數(shù)組缺失的正整數(shù)可以把所有出現(xiàn)過的元素放入一個set里,然后從
1~2000開始枚舉(因為arr長度和k的大小最大都是1000,所以答案一定小于2000),如果當前枚舉到的這個數(shù)在
set中沒有出現(xiàn),說明arr數(shù)組中缺失這個數(shù),把這個數(shù)加入到missed數(shù)組中,如果missed數(shù)組的大小為k,說明找到了第k
個缺失的正整數(shù),返回即可。
class Solution {
public:
int findKthPositive(vector<int>& arr, int k) {
vector<int> missed;
set<int> hash;
for(auto &a : arr) {
hash.insert(a);
}
for(int i = 1; i < 2010; ++i) {
if(hash.count(i) == 0) {
missed.push_back(i);
if(missed.size() == k) {
return missed.back();
}
}
}
return missed.back();
}
};
第二題 1540. K 次操作轉(zhuǎn)變字符串
如果s能轉(zhuǎn)換為t,說明s的size和t的size相同,又由于轉(zhuǎn)換的字母在s中的位置和在t中的位置是一樣的,所以我們可以一個O(n)同時遍歷一下s和t,
看一下每個位置上的s[i]要轉(zhuǎn)換成t[i]需要多少步,然后再判斷這些步數(shù)能否在給定的k內(nèi)完成。
我們開一個哈希表vector Hash(27); Hash[i]表示需要移位i步的操作的次數(shù),(i從1到26)。
然后我們統(tǒng)計一下所有移位操作的次數(shù)。
最后根據(jù)哈希表中各個移位操作的次數(shù)和k的大小比較一下,就可以判斷出來是否能將s轉(zhuǎn)換成t。
比如,如果k<=26,說明移位1~k的操作每個操作只能有一次,如果對于一個需要移位i步的操作Hash[i] > 1(1 <= i <= k),
也就是說我們需要一次以上的移位i步操作才可以將s轉(zhuǎn)換成t,這是不可以的,因為k小于等于26,所以每個移位操作最多進行一次,
這樣我們就返回false。
同樣的,如果k<=26,我們有一個需要移位i步的操作Hash[i] > 0 (k + 1 <= i < 26),也就是說有需要移位的步數(shù)大于k的情況,
也返回false,因為我們無法移位那么多。
如果k > 26,那么我們的選擇余地就大一些了,因為一位i步操作有多次的話,我們可以視為分別移步i步,i + 26步,i + 52步。。。。。
所以我們只需要對于所有的i(1 <= i < 26),判斷是否i + 26 * (Hash[i] - 1) <= k即可,Hash[i] - 1的減一是因為,第一次移位i步在1 ~ 25范圍內(nèi).
i + 26 * (Hash[i] - 1) <= k如果成立,代表所有移位i步的操作都可以在k次移步操作內(nèi)完成,如果成功就返回false。
否則,如果i + 26 * (Hash[i] - 1) > k,則說明k次移位操作內(nèi)無法滿足所有要移步i步的操作,返回false。
代碼如下:
class Solution {
public:
bool canConvertString(string s, string t, int k) {
if(s == t) {
return true;
}
if(s.size() != t.size()) { //如果長度都不相等,就不用判斷了
return false;
}
if(k == 0) { //不能移步,返回false
return false;
}
int size = s.size();
vector<int> Hash(27); //Hash[i]表示需要切換i次的操作的個數(shù)
for(int i = 0; i < size; ++i) {
if(s[i] == t[i]) {
continue;
} else if(s[i] < t[i]) {
int shiftTimes = (t[i] - s[i]);
++Hash[shiftTimes];
} else if(s[i] > t[i]) {
int shiftTimes = 26 - (s[i] - t[i]);
++Hash[shiftTimes];
}
}
if(k <= 26) {
for(int i = 0; i <= k; ++i) {
if(Hash[i] > 1) {
return false;
}
}
for(int i = k + 1; i <= 26; ++i) {
if(Hash[i] > 0) {
return false;
}
}
} else {
for(int i = 1; i < 26; ++i) {
if(i + 26 * (Hash[i] - 1) > k) {
return false;
}
}
}
return true;
}
};
第三題 1541. 平衡括號字符串的最少插入次數(shù)
由于一個左括號需要與兩個連續(xù)的右括號匹配,且左括號必須要在和他匹配的兩個連續(xù)的右括號之前,所以我們可以這樣,
用一個int變量singleLeft來記錄當前還未與兩個右括號匹配的左括號數(shù)量,再用一個res變量記錄需要插入的括號的次數(shù)。
然后我們遍歷一下字符串s,每一個字符只有兩種可能。
(1)如果當前字符是左括號'(',則我們增加singleLeft,表示當前還未匹配的左括號多了一個。
(2)如果當前字符是右擴號')',由于兩個右括號才能算是一個“匹配單位”,我們先判斷一下下一位是否也是右擴號,
* 如果下一位是右擴號,好,我們就把這兩個右擴號當作一起的,讓i++,表示跳過下一個字母(因為我們知道下一個字母是右括號了,
我們已經(jīng)把他和當前這個“第一個右括號”綁定了)。
* 如果下一位不是右括號,沒事,我們就讓res++,表示需要下一位插入一個右括號,才可以讓兩個右括號連續(xù)以便和之前的左括號匹配。
經(jīng)過上面兩步,我們現(xiàn)在肯定有了兩個連續(xù)的右括號(就算原來沒有我們也插入了一個,使得有了兩個連續(xù)的右括號),然后我們看一下
當前未匹配的左括號的數(shù)量singleLeft是否大于0,如果是,則singleLeft--,也就是讓一個左括號和剛剛的兩個連續(xù)的右括號匹配。
而如果singleLeft<=0,說明前面沒有左括號和我們剛剛這兩個連續(xù)的右括號匹配了,則我們需要在前面插入一個左括號進行匹配(因為題目要求
左括號必須在右括號之前)。所以我們讓res++。
遍歷完字符串s之后,singleLeft可能不為0,表示左括號多余了,還差singleLeft * 2個右括號才能讓s平衡。所以res += singleLeft * 2.
代碼如下:
class Solution {
public:
int minInsertions(string s) {
int singleLeft = 0, res = 0;
for(int i = 0; i < s.size(); ++i) {
if(s[i] == '(') {
++singleLeft;
} else if(s[i] == ')'){
if(i + 1 < s.size() && s[i + 1] == ')') {
++i;
} else {
++res;
}
if(singleLeft > 0) {
--singleLeft;
} else {
++res;
}
}
}
res += singleLeft * 2;
return res;
}
};
第四題 1542. 找出最長的超贊子字符串
最簡單的做法是暴力找出所有子串,然后更新可以調(diào)整為回文子串的字符串的最大長度,但是這樣會超時。需要考慮狀態(tài)壓縮。
我們注意到,可以調(diào)整為回文子串的字符串都有這么個性質(zhì):字符串中每個數(shù)字的出現(xiàn)次數(shù)都為偶數(shù)** 或者 只有一個數(shù)字出現(xiàn)次數(shù)為奇數(shù),
其余數(shù)字的出現(xiàn)次數(shù)為偶數(shù)。
這很好理解,如果所有數(shù)字出現(xiàn)次數(shù)都是偶數(shù),則字符串可以調(diào)整為沿中間兩個元素中間的點作為對稱軸對稱的一個回文字符串。
如果一個數(shù)字出現(xiàn)次數(shù)為奇數(shù),其他數(shù)字出現(xiàn)次數(shù)為偶數(shù),則字符串可以將最中間的數(shù)字(必然是出現(xiàn)次數(shù)為奇數(shù)的數(shù)字)作為對稱軸,剩下的數(shù)字對稱的
放到這個數(shù)字的兩邊,也能組成回文字符串。
因此,問題就轉(zhuǎn)換為,在原來的字符串中s中找到最大的子串,使得子串中所有數(shù)字的出現(xiàn)次數(shù)滿足上面兩個性質(zhì)其中之一。
由于子串只能由數(shù)字組成,而且我們只需要知道子串中每個數(shù)字出現(xiàn)次數(shù)的奇偶性(而無需知道具體出現(xiàn)次數(shù)),因此我們可以對所有數(shù)字的出現(xiàn)次數(shù)進行狀態(tài)
壓縮,用一個長度為10的二進制數(shù)字表示各個數(shù)字出現(xiàn)次數(shù)的奇偶性(1表示出現(xiàn)次數(shù)為奇數(shù),0表示出現(xiàn)次數(shù)為偶數(shù)),這個二進制數(shù)字從低位到高位分別表示
對應(yīng)數(shù)字的出現(xiàn)次數(shù)的奇偶性,比如第0位為1表示0出現(xiàn)奇數(shù)次,第7位為0表示7出現(xiàn)偶數(shù)次。我們用一個int型變量記錄出現(xiàn)次數(shù),由于最大只有2的10次方,
因此用int型記錄出現(xiàn)次數(shù)足夠了。
有了所有數(shù)字的出現(xiàn)次數(shù),如何判斷字符串能否調(diào)整為一個回文字符串呢?
首先,我們開一個dp數(shù)組,dp[bits] == i表示狀態(tài)(各數(shù)字的出現(xiàn)次數(shù))為bits(bits是一個整數(shù))的最小的i是多少,比如bits = 0,二進制就是0000000000,
表示從s的第一個位置到第i個位置的子串中,所有數(shù)字出現(xiàn)次數(shù)都為偶數(shù)次。
這樣,我們?nèi)绻^續(xù)往后遍歷,可以快速計算出以當前位置為終點的子串的狀態(tài)(所有數(shù)字的出現(xiàn)次數(shù)的奇偶性),比如,我們之前計算好了s[1....i] = bits_i,
也就是說bits_i是表示子串s[1...i]的所有數(shù)字的出現(xiàn)次數(shù)的整數(shù),然后我們往后遍歷到了第j個位置,我們可以先計算出s[1....j] = bits_j,
然后用bits_j ^ bits_{i - 1}(前j個字母組成的子串的狀態(tài)異或前i-1個字母組成的子串)得到s[i....j]的狀態(tài)。
這是因為異或的性質(zhì)是兩個相同的數(shù)字異或為0,不同的數(shù)字異或為1,和這里奇偶性的計算性質(zhì)正好符合。
anyway,我們可以計算出所有子串的狀態(tài)(各數(shù)字的出現(xiàn)次數(shù))了。
比如我們知道了s[1...i]的狀態(tài)是bits_i,s[1...k]的狀態(tài)是bits_k。(i < k),
我們還知道通過bits_k ^ bits_i可以計算出s[i+1...k]的狀態(tài),
然后我們判斷s[i+1...k]的狀態(tài)是否滿足(1)bits_i == bits_k,即s[i+1...k]中所有數(shù)字的出現(xiàn)次數(shù)都是偶數(shù)次 或
(2)bits_i == bits_k ^ (1 << c),其中0<=c<=9,表示s[i+1...k]中只有一個數(shù)字出現(xiàn)奇數(shù)次,其他數(shù)字都出現(xiàn)偶數(shù)次。
這個式子的意思是這樣的,bits_k ^ (1 << c)改變了bits_k的(從右到左從低到高)第c位數(shù)字的奇偶性,然后改變了這個數(shù)字的
奇偶性之后bits_i和bits_k兩個狀態(tài)相等了,說明s[i+1...k]中只有一個數(shù)字出現(xiàn)奇數(shù)次,其他數(shù)字都出現(xiàn)偶數(shù)次。
然后我們就是尋找所有子串的狀態(tài)了,滿足狀態(tài)的子串,考慮更新一下長度。
代碼如下:
class Solution {
public:
int dp[1030];
int longestAwesome(string s) {
int n = s.size();
for(int i = 0; i < 1030; ++i) { //初始化
dp[i] = n + 1;
}
int bits = 0, res = 0; //bits記錄從1~當前遍歷到的位置的狀態(tài),也就是說bits的二進制表示上的1和0就是各個數(shù)字出現(xiàn)次數(shù)的奇偶性
dp[0] = 0;
for(int i = 1; i <= n; ++i) {
int c = s[i - 1] - '0'; //當前遍歷到哪一個數(shù)字
bits ^= (1 << c); //更新一下bits, bits ^= (1 << c)表示更改一下第c位(即數(shù)字c的出現(xiàn)次數(shù)的奇偶性)的奇偶性,原來是0改成1,原來是1改成0
dp[bits] = min(dp[bits], i); //更新最小的以bits為狀態(tài)的前綴,方便后面的計算
res = max(res, i - dp[bits]); //如果前面有一個位置,假設(shè)為j吧(j == dp[bits]),滿足s[1...j]的狀態(tài)是bits(和s[1...i]的狀態(tài)一樣),說明字符串s[j+1...i]中所有數(shù)字出現(xiàn)次數(shù)都為偶數(shù)次,更新長度,這個字符串長度就是i - dp[bits]
for(int k = 0; k < 10; ++k) { //還需要計算是不是有只有一個數(shù)字出現(xiàn)次數(shù)為奇數(shù)的子串
res = max(res, i - dp[bits ^ (1 << k)]); //bits ^ (1 << k)就是更改原來的bits的第k位數(shù)字的奇偶性,和上面一樣,如果找到了只有一個數(shù)字出現(xiàn)次數(shù)為奇數(shù)的子串,也要更新長度
}
}
return res;
}
};
個人覺得,這題的位運算和狀態(tài)表示、狀態(tài)壓縮、狀態(tài)轉(zhuǎn)移以及長度的更新真的很難理解(對于我這種不熟悉位運算和狀態(tài)壓縮dp的人來說),建議大家去看一下B站up主喂你腳下有坑第四題的題解
總結(jié)
以上是生活随笔為你收集整理的LeetCode第32场双周赛的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 思科3750交换机堆叠配置指南
- 下一篇: windows下快速启动或关闭系统服务方