LeetCode 力扣周赛 188

133 阅读2分钟

5404. 用栈操作构建数组

因为 target 是严格升序的,且只包含 1 到 n 之间的数字,即 target 是 list 的一个子序列,所以一定有解。对于 list 中的每个数字 x:

  • 如果 x 在 target 中,则需执行一次 Push。
  • 如果 x 不在 target 中,且小于target 中的最大值,则需执行一个 Push 和 Pop。
  • 如果 x 不在 target 中,且大于target 中的最大值,则无需处理。

按照上述规则,依次处理 list 中的元素即可。代码如下:

class Solution {
public:
    vector<string> buildArray(vector<int>& target, int n) {
        vector<string> res;
        int last = 0;
        for(int i = 0; i < target.size(); i++) {
            for(int j = last+1; j < target[i]; j++) {
                res.push_back("Push");
                res.push_back("Pop");
            }
            res.push_back("Push");
            last = target[i];
        }
        return res;
    }
};

5405. 形成两个异或相等数组的三元组数目

首先暴力枚举三元组(i,j,k),时间复杂度 O(n^3)。 然后利用异或运算的性质,O(1)的计算区间 [i,j-1],[j,k] 的异或值。 异或运算有如下性质:

  1. a ⊕ a = 0
  2. a ⊕ b = b ⊕ a
  3. a ⊕b ⊕ c = a ⊕ (b ⊕ c) = (a ⊕ b) ⊕ c;
  4. d = a ⊕ b ⊕ c 可以推出 a = d ⊕ b ⊕ c.
  5. a ⊕ b ⊕ a = b.

利用第五条性质,可以先O(n)的预处理所有前缀 pre(i) 的异或值。 当要计算[i,j]的异或值时,有:

  • 当 i = 0 时,结果为 pre(i)。
  • 当 i 不为 0 时,结果为 pre(j)⊕pre(i-1) 。

代码如下:

class Solution {
    int get(const vector<int> &val, int L, int R) {
        if(L < 0) {
            return val[R];
        }
        return val[R]^val[L];
    }
public:
    int countTriplets(vector<int>& arr) {
        vector<int> val; //预处理前缀
        for(int a = 0, i = 0 ; i < arr.size(); i++) {
            a ^= arr[i];
            val.push_back(a);
        }
        int cnt = 0; //暴力枚举三元组
        for(int i = 0; i < arr.size(); i++) {
            for(int j = i+1; j < arr.size(); j++) {
                for(int k = j; k < arr.size(); k++) {
                    // 根据预处理,O(1)计算异或值
                    if(get(val, i-1, j-1) == get(val, j-1, k)) {
                        cnt++;
                    }
                }
            }
        }
        return cnt;
    }
};

5406. 收集树上所有苹果的最少时间

设 dfs(i) 表示收集以 i 为根节点的子树,然后返回到 i 的父节点的时间花费。

  • 当子树 i 没有苹果时,显然 dfs(i) = 0。因为不需要到达 i,所以也不需要返回 i 的 父节点。
  • 当子树有苹果时,dfs(i) = sum(dfs(son(i)))。

那么最终的答案为,dfs(0) - 2。

代码如下:

class Solution {
    vector<int> E[100000];
public:
    int dfs(int root, int pre, const vector<bool> &hasApple) {
        int sum = 0;
        for(auto v : E[root]) {
            if(pre == v) { //pre 为父节点。
                continue;
            }
            // 计算遍历所有子树的时间花费。
            sum += dfs(v, root, hasApple);
        }
        // sum != 0 说明 root 的子树有苹果
        // hasApple[root] == true,说明root结点有苹果。
        // 说明此时需要到达 root 结点,sum += 2 是从root 回到 pre 的花费。
        if((sum != 0 || hasApple[root])) {
            sum += 2;
        }
        return sum;
    }
    int minTime(int n, vector<vector<int>>& edges, vector<bool>& hasApple) {
        //先根据边表,构造邻接表,方便dfs。
        for(const auto &e : edges) {
            E[e[0]].push_back(e[1]);
            E[e[1]].push_back(e[0]);
        }
        // 0 没有父节点,所有也不需要花费 2 返回了。
        return dfs(0, -1, hasApple) - 2;
    }
};

5407. 切披萨的方案数

设 dp(x, y, k) 表示pizza还剩 x 行,y列且还需要分成 k 块时的方案数。x, y, k 满足下述取值范围:

  • 0 <= x <= pizza.size()
  • 0 <= y <= pizza[0].size()
  • 1 <= k

当 k = 1 时有:

  • 如果 pizza 有苹果,则方案数为 1 。
  • 如果 pizza 没有苹果,则方案数为 0 。

当 k > 1 时,有 (x-1) + (y-1) 种(可能合法的)切法,则 dp(x,y,k) = sum(dp(i,y,k-1)) + sum(dp(x,j,k-1))。 其中 1 <= i < x, 1 <= j < y。

代码如下:

class Solution {
public:
    int64_t dp[51][51][11];
    //pre[i][j] 表示i行j列的左上角子矩阵的苹果数量
    int pre[51][51];
    int count(int x, int y, const vector<string> &pizza) {
        int r = pizza.size();
        int c = pizza[0].size();
        //根据容斥原理计算右下角 i 行 j 列的苹果数量。
        return pre[r][c] - pre[r-x][c] - pre[r][c-y] + pre[r-x][c-y];
    }
    void update(int64_t &x, int64_t y) {
        x += y;
        while(x >= 1000000007) {
            x -= 1000000007;
        }
    }
    int dfs(int x, int y, int k, const vector<string> &pizza) {
        if(dp[x][y][k] != -1) {
            return dp[x][y][k];
        }
        int cnt = count(x, y, pizza);
        if(cnt < k) {
            //苹果总数都小于 k 了,还分个毛线。
            return dp[x][y][k] = 0, 0;
        }
        if(k == 1) {
            // k == 1,不用分了,所以就一种方案。
            return dp[x][y][k] = 1, 1;
        }
        int64_t &val = dp[x][y][k];
        val = 0;
        //横着切,枚举剩下的pizza的行数
        for(int i = 1; i < x; i++) {
            //需要保证上半部分有苹果
            if(count(x-i, y, pizza) == cnt) {continue;}
            update(val, dfs(x-i, y, k-1, pizza));
        }
        //竖着切,枚举剩下的pizza的列数
        for(int i = 1; i < y; i++) {
            //需要保证左半部分有苹果
            if(count(x, y-i, pizza) == cnt) {continue;}
            update(val, dfs(x, y-i, k-1, pizza));
        }
        return val;
    }
    int ways(vector<string>& pizza, int k) {
        memset(dp, -1, sizeof(dp));
        memset(pre, 0, sizeof(pre));
        //根据容斥原理预处理子矩阵的苹果数量。
        for(int i = 0; i < pizza.size(); i++) {
            for(int j = 0; j < pizza[0].size(); j++) {
                pre[i+1][j+1] = pre[i+1][j] + pre[i][j+1] - pre[i][j];
                if(pizza[i][j] == 'A') {
                    pre[i+1][j+1] ++;
                }
            }
        }
        return dfs(pizza.size(), pizza[0].size(), k, pizza);
    }
};

如果感觉有点意思,可以扫码关注我 !