LeetCode 力扣周赛 271

219 阅读2分钟

又进前一百了,真带劲儿呀。主要是这次题目比较简单,都是些手速题,所以我才有机会~

5952. 环和杆

思路:模拟,哈希

时间复杂度O(n)\mathcal{O}(n)

空间复杂度O(n)\mathcal{O}(n)

遍历字符串,统计每个杆上出现的颜色即可。借助 std::unordered_mapstd::unordered_set,代码会很简便。

class Solution {
public:
    int countPoints(string rings) {
        // cnt 的 first 表示杆
        // cnt 的 second 记录杆上的出现的颜色
        unordered_map<char, unordered_set<char>> cnt;
        // anw 记录答案
        int anw = 0;
        // 开始遍历
        for (int i = 0; i < rings.size(); i += 2) {
            char c = rings[i];
            char p = rings[i+1];
            // 在杆p上插入颜色 c,如果插入成功且颜色数为 3,则说明新找到了一个集齐颜色的杆
            if(cnt[p].insert(c).second && cnt[p].size() == 3) {
                anw++;
            }
        }
        return anw;
    }
};

5953. 子数组范围和

方法一

思路:暴力枚举

时间复杂度O(n2)\mathcal{O}(n^2)

空间复杂度O(1)\mathcal{O}(1)

因为题目的数据范围较小,可以用两层循环枚举所有子数组。

class Solution {
public:
    long long subArrayRanges(vector<int>& nums) {
        // anw 保存答案
        int64_t anw = 0;
        int n = nums.size();
        // 两层循环
        for (int i = 0; i < n; i++) {
            // l 表示区间 [i,j] 内的最小值
            // r 表示区间 [i,j] 内的最大值
            int l = nums[i], r = nums[i];
            for (int j = i; j < n; j++) {
                // 更新 l, r
                l = min(l, nums[j]);
                r = max(r, nums[j]);
                // 更新答案
                anw += (r - l);
            }
        }
        return anw;
    }
};

方法二

思路:单调栈

时间复杂度O(n)\mathcal{O}(n)

空间复杂度O(n)\mathcal{O}(n)

假设我们知道每个元素 numinum_i 分别是 lowilow_i 个子数组的最小值,highihigh_i 个子数组的最大值,那么答案可表示为:

i=0n1numi(highilowi)\sum_{i=0}^{n-1}num_i*(high_i-low_i)

我们可使用单调栈求解 lowilow_ihighihigh_i。下面以 lowilow_i 为例讲解一下思路。

设有栈 stst。我们从前先后将元素的位置 ii 放入栈中,在放入 ii 之前, stst 需满足以下条件之一:

  • stst 为空。
  • numst.topnuminum_{st.top} \le num_{i}

否则,弹出栈顶元素直到满足要求。

在此过程中,可以得出「以 numinum_i 作为最小值的子数组的左边界 LiL_i」—— 在 stst 满足放入要求时:

  • stst 为空,则左边界 LiL_i 为 0。
  • stst 不为空,则左边界 LiL_ist.top+1st.top\mathrel{+}1

同样的,我们从后向前遍历,按照上述步骤可以得到「以 numinum_i 作为最小值的子数组的右边界 RiR_i」。需要注意的是,为了避免重复统计,限制条件应改为:

  • stst 为空。
  • numst.top<numinum_{st.top} \lt num_{i}(lele 改为 ltlt)

换言之,如果子数组包含多个最小值时,以下标较小的为最小值。

那么 lowilow_i 可表示为: lowi=(Rii+1)(iLi+1)low_i = (R_i-i+1)* (i-L_i+1)

同理,按照类似操作,可以得出 highihigh_i

class Solution {
public:
    void work(const vector<int> &nums, const function<bool(int, int)> &c0, const function<bool(int, int)> &c1, vector<int> &data) {
        int n = nums.size();
        stack<int> st;
        vector<int> L(n, 0), R(n, 0);
        // 求解 L
        for (int i = 0; i < n; i++) {
            while (false == st.empty() && !c0(nums[st.top()], nums[i])) {
                st.pop();
            }
            L[i] = (st.empty() ? 0 : st.top()+1);
            st.push(i);
        }
        st = stack<int>();
        // 求解 R
        for (int i = n-1; i >= 0; i--) {
            while (false == st.empty() && !c1(nums[st.top()], nums[i])) {
                st.pop();
            }
            R[i] = (st.empty() ? n-1 : st.top()-1);
            st.push(i);
        }
        // 求解 data,即传进来的 low 或 high
        for (int i = 0; i < n; i++) {
            data[i] = (R[i]-i+1)*(i-L[i]+1);
        }
    }
    long long subArrayRanges(vector<int>& nums) {
        int n = nums.size();
        vector<int> low(n, 0), high(n, 0);
        // 求解 low
        work(nums,
            [](int lhs, int rhs) { return lhs <= rhs;},
            [](int lhs, int rhs) { return lhs < rhs;},
            low);
        // 求解 high
        work(nums,
            [](int lhs, int rhs) { return lhs >= rhs;},
            [](int lhs, int rhs) { return lhs > rhs;},
            high);
        // 计算答案
        int64_t anw = 0;
        for (int i = 0; i < n; i++) {
            anw += 1L * nums[i] * (high[i] - low[i]);
        }
        return anw;
    }
};

2105. 给植物浇水 II

思路:模拟

时间复杂度O(n)\mathcal{O}(n)

空间复杂度O(1)\mathcal{O}(1)

比较直白的模拟题啦。Alice 浇前 n2\lfloor\frac{n}{2}\rfloor 棵植物,Bob 浇后 n2\lfloor\frac{n}{2}\rfloor 棵植物。如果 nn 是奇数,那么根据 Alice 和 Bob 的剩余水量决定是谁来浇。

在模拟过程中,记录灌水的次数即可。详见注释。

class Solution {
public:
    int minimumRefill(vector<int>& plants, int capacityA, int capacityB) {
        int n = plants.size();
        // anw 用来保存答案。
        int anw = 0;
        // remainA 记录 Alice 浇完 plants[i] 之后的剩余量
        int remainA = capacityA;
        for (int i = 0; i < n/2; i++) {
            if (remainA >= plants[i]) {
                // 如果水足够,那么直接浇
                remainA -= plants[i];
            } else {
                // 否则先灌满水,再浇。
                remainA = capacityA - plants[i];
                // 记录一次灌水次数
                anw++;
            }
        }
        // remainB 类似
        int remainB = capacityB;
        for (int i = 0; i < n/2; i++) {
            if (remainB >= plants[n-1-i]) {
                remainB -= plants[n-1-i];
            } else {
                remainB = capacityB - plants[n-1-i];
                anw++;
            }
        }
        // 如果是奇数,根据剩余水量决定由谁来浇。
        if (n&1) {
            int r = remainA;
            if (remainA < remainB) {
                r = remainB;
            }
            if (r < plants[n/2]) {
                anw++;
            }
        }
        return anw;
    }
};

2106. 摘水果

思路:模拟,前缀和

时间复杂度O(n+k)\mathcal{O}(n+k)

空间复杂度O(n)\mathcal{O}(n)

有四种策略来分配 kk

  • 向左走 kk 步,覆盖区间 [sk,s][s-k,s]
  • 向右走 kk 步,覆盖区间 [s,s+k][s,s+k]
  • 先向左走 xx 步,再向右走 kxk-x 步,覆盖区间 [sx,s+k2x][s-x,s+k-2*x]
  • 先想右走 xx 步,再向左走 kxk-x 步,覆盖区间 [sk+2x,s+x][s-k+2*x,s+x]

其中,x[0,k2]x\in[0,\lfloor\frac{k}{2}\rfloor]

那么问题变为了,如何求解给定区间内的水果水量,显然可以借助前缀和咯。

具体编程实现时,需要注意边界溢出的情形。详见注释。

class Solution {
public:
    int maxTotalFruits(vector<vector<int>>& fruits, int startPos, int k) {
        // 为了便于编写代码,我们将所有的位置都 + 1。
        const int MAXN = 200001;
        int64_t pos[MAXN+1] = {0};
        // 先统计每个位置上的水果数量。
        for (const auto &f : fruits) {
            pos[f[0]+1] = f[1];
        }
        // 预处理前缀和。
        for (int i = 1; i <= MAXN; i++) {
            pos[i] += pos[i-1];
        }
        int s = startPos + 1;
        // 仅向右走
        int sol1 = pos[min(s+k, MAXN)] - pos[s-1];
        // 仅向左走
        int sol2 = pos[s] - pos[max(s-k-1, 0)];
        // 先左后右
        int sol3 = 0;
        // 枚举先向左走多少步
        for (int i = 1; i <= k/2; i++) {
            int tmp = pos[min(MAXN,s+(k-2*i))] - pos[max(s-i-1, 0)];
            sol3 = max(tmp, sol3);
        }
        // 先右后左
        int sol4 = 0;
        // 枚举先向右走多少步
        for (int i = 1; i <= k/2; i++) {
            int tmp = pos[min(MAXN, s + i)] - pos[max(0, s - (k-2*i) -1)];
            sol4 = max(tmp, sol4);
        }
        return max(max(sol1, sol2), max(sol3, sol4));
    }
};