Leetcode 周賽#201 題解
1545 找出第N個二進位制字串的第K位 #分治
題目連結
題意
給定正整數\(n(\leq 20)\)與\(k\),二進位制串\(S_n\)形成規則有:
-
\(S_1 = “0”\)
-
當\(i>1\)時,\(S_i = S_{i-1}+“1”+reverse(invert(S_{i-1}))\)
其中\(reverse(x)\)表示左右反轉字串
x
,\(invert(x)\)表示翻轉x中的每一位(0->1,1->0)
現要返回\(S_n\)的第\(k\)位字元。
如:\(n=3,k=1\),可以得到\(S_3=“0111001”\),其第一位為"0",故返回"0"
分析
本來想打表,但最後的串實在是長。我們不必從n=1一步步模擬整個過程,而是自頂而下深入遞迴,只關心第\(k\)位屬於上一步形成的01串的哪個位置哪個字元。
我們容易推出,對於\(S_n\)形成的串為\(2^n-1\)長度的01串,我們比較\(k\)與\(2^{n-1}\)的大小:
- 如果\(k=2^{n-1}\),它在串最中間,字元為"1",直接返回即可
- 如果\(k<2^{n-1}\),它在當前串的左部分,由串的形成規則可知,串左部分是經上一輪的串直接複製得到的,那麼遞迴\(n-1\)次操作的第\(k\)位即可
- 如果\(k>2^{n-1}\),由串的形成規則,串右部分是經過上一輪串的反轉+翻轉得到的,那麼當前的第k位是由上一輪的\(2^n-1-k+1\)
class Solution { public: char Trans(char now) { return (now == '1') ? '0' : '1'; } char findKthBit(int n, int k) { if (n == 1) return '0'; int len = 1 << (n - 1); if (k == len) return '1'; else if (k < len) return findKthBit(n - 1, k); else { return Trans(findKthBit(n - 1, (len << 1) - k)); } } };
1546 和為目標值的最大數目不重疊非空子陣列數目 #字首和 #雜湊表 #線性DP
題目連結
題意
給定陣列 nums
(長度不大於\(1e5\)) 和一個整數 target
。現要返回 非空不重疊 子陣列的最大數目,且每個子陣列中數字和都為 target
。
樣例
nums = [-1,3,5,1,4,2,-9], target = 6
,總共有 3 個子陣列和為 6 。 $([5,1], [4,2], [3,5,1,4,2,-9]) $但只有前 2 個是不重疊的。
分析
dp[i]
表示前i
位滿足要求的子陣列個數;sum
表示[1, size]
的字首和(先假定從1計數)
當\(i>0\)顯然dp[0] = 0
;當\(i>0\)時,有兩種情況:
-
存在這樣的\(pos(\leq i) st.sum[i]-sum[pos]==target\),
- 我們找到\([pos, i]\)的合法子陣列,於是
dp[i]
可以由dp[pos]+1
轉移 [pos, i]
的子陣列長度可能太長,以至於覆蓋了該區間的幾個合法子陣列,那麼dp[i]
也可以由dp[i-1]
轉移
即得到轉移方程:\(dp[i] = max(dp[i-1], dp[pos]+1)\)
- 我們找到\([pos, i]\)的合法子陣列,於是
-
不存在這樣的\(pos\),顯然轉移方程只能為\(dp[i] = dp[i-1]\)
class Solution {
private:
int dp[100005] = {0};
public:
int maxNonOverlapping(vector<int>& nums, int target) {
map<int, int> mymap;
int sum = 0; mymap[0] = 0;
for (int i = 1; i <= nums.size(); i++) {
sum += nums[i - 1];
if (mymap.count(sum - target)) { //是否存在sum[pos]滿足sum[i]-sum[pos]=target
int pos = mymap[sum - target];
dp[i] = max(dp[i - 1], dp[pos] + 1);
}
else {
dp[i] = dp[i - 1];
}
mymap[sum] = i; //記錄字首和sum的最新位置
}
return dp[nums.size()];
}
};
1547 切棍子的最小成本 #區間DP
題目連結
題意
給定長度為\(n\)個單位的木棍,及記錄你要將棍子切開的位置陣列\(cuts[i]\),現要你按\(cuts[i]\)記錄的位置按一定順序切割木棍,使得成本最小,並求其值。其中每次切割的成本是當前要切割的棍子的長度。
分析
顯然是石子合併的變式,區間DP題,不過我們需要預處理下每個切割位置之間的長度(該位置的序號-前一位置的序號),同時將代價陣列sum[]
從1計數,便於DP
class Solution {
private:
int sum[105] = { 0 };
int dp[105][105];
public:
int cost(int lo, int hi) {
return sum[hi] - sum[lo];
}
void Init(int maxlen, vector<int>& cuts, int n) {
sort(cuts.begin(), cuts.end());
for (int i = 1; i <= maxlen; i++)
for (int j = 1; j <= maxlen; j++)
dp[i][j] = 0x3f3f3f3f;
sum[1] = cuts[0]; dp[1][1] = dp[maxlen][maxlen] = 0;
for (int i = 2; i <= cuts.size(); i++) {
dp[i][i] = 0;
sum[i] = sum[i - 1] + cuts[i - 1] - cuts[i - 2];
}
sum[maxlen] = sum[maxlen - 1] + n - cuts[cuts.size() - 1];
}
int minCost(int n, vector<int>& cuts) {
int maxlen = cuts.size() + 1;
Init(maxlen, cuts, n);
for (int len = 2; len <= maxlen; len++) {
for (int i = 1; i + len - 1 <= maxlen; i++) {
int j = i + len - 1;
for (int k = i; k < j; k++)
dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + cost(i - 1, j));
}
}
return dp[1][maxlen];
}
};