1. 程式人生 > 實用技巧 >Leetcode 周賽#201 題解

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\),顯然轉移方程只能為\(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];
    }
};