LeetCode 力扣双周赛 65

2,235 阅读3分钟

偷懒了,水了两套题滚去睡觉了。。

周赛传送门

5910. 检查两个字符串是否几乎相等

思路:计数

时间复杂度O(n)O(n)

思路比较直观啦。先统计 word1word1 中每种字符的出现次数,再统计 word2word2 的,然后检验两者之差即可。

class Solution {
public:
    bool checkAlmostEquivalent(string word1, string word2) {
        // 计数用的数组
        int cnt[26] = {0};
        // 遍历 word1,做加法
        for (auto c : word1) {
            cnt[c-'a']++;
        }
        // 遍历 word2,做减法
        for (auto c : word2) {
            cnt[c-'a']--;
        }
        // 检验差值
        for (auto c : cnt) {
            if (c < -3 || c > 3) {
                return false;
            }
        }
        return true;
    }
};

5911. 模拟行走机器人 II

思路:模拟

时间复杂度O(lenop)O(len*op)opop 为操作次数。

中规中矩的模拟题啦。不过需要简单优化一下。

考虑到每次移动的上限为 1e51e5,操作次数的上限为 1e41e4,直接模拟必然会带来超时。

观察上图,不难发现机器人「只会走最外圈」。因此,机器人每走 lenlen 步就回到了其实位置,lenlen 为周长。

于是,每次移动前先将 numnumlenlen 取余。这保证了每次移动步数不会超过 lenlen。因此移动操作的时间复杂度降为 O(len)O(len)

需要注意一点,numnumlenlen 整除的情形。此时机器人的位置不会改变,但朝向有可能发生变化。

class Robot {
public:
    // w 宽;h 高;len 周长
    int w, h, len;
    // (x,y) 机器人的位置; 
    // dir 机器人的朝向:
    // 0 - "East"
    // 1 - "North"
    // 2 - "West"
    // 3 - "South"
    int x = 0, y = 0, dir = 0;
    // dx[dir],dy[dir] 分别表示朝 dir 方向移动一步时,x 和 y 的变化量
    int dx[4] = { 1, 0, -1,  0};
    int dy[4] = { 0, 1,  0, -1};
    Robot(int width, int height) : w(width), h(height), len(w*2+h*2-4) {}
    
    void move(int num) {
        // 取余,避免绕圈
        num %= len;
        // 如果 num 为 0,就让机器人跑一圈,避免朝向的问题
        if (num == 0) {
            num = len;
        }
        while (num > 0) {
            int nx = x + dx[dir], ny = y + dy[dir];
            // 尝试朝 dir 方向走一步
            if (0 <= nx && nx < w && 0 <= ny && ny < h) {
                // 走完未出圈,成功走了一步。
                x = nx, y = ny;
                num--;
            } else {
                // 走完出圈了,则不能走,只能转向了。
                dir = (dir+1)&3;
            }
        }
    }
    
    vector<int> getPos() {
        return vector<int>{x, y};
    }
    
    string getDir() {
        const vector<string> name{"East", "North", "West", "South"};
        return name[dir];
    }
};

/**
 * Your Robot object will be instantiated and called as such:
 * Robot* obj = new Robot(width, height);
 * obj->move(num);
 * vector<int> param_2 = obj->getPos();
 * string param_3 = obj->getDir();
 */

5912. 每一个查询的最大美丽值

思路:排序,二分

时间复杂度O(nlgn)O(n*\lg n)

首先,将 itemsitems 按价格升序排序。那么对于每个 queryquery,可以二分的找出最大的 ii,满足前 iiitemitem 的价格都不超过 queryquery。这 iiitemitem 中的最大美丽值即为本次答案。

通过时间复杂度为 O(n)O(n) 的预处理,可将 itemiitem_i 的美丽值修改为前 iiitemitem 中的最大值。这使得每次询问的时间复杂度降为 O(lgn)O(\lg n)

class Solution {
public:
    vector<int> maximumBeauty(vector<vector<int>>& items, vector<int>& queries) {
        // 排序,将 item 按价格升序排序
        sort(items.begin(), items.end(), [](const auto &lhs, const auto &rhs) {
            return lhs[0] < rhs[0];
        });
        // 预处理,将 items[i] 的美丽值值为 items[0..i] 中的最大值。
        for (int i = 1; i < items.size(); i++) {
            items[i][1] = max(items[i-1][1], items[i][1]);
        }
        // anw 即为答案。
        vector<int> anw;
        // 开始按序处理所有 query
        for (auto q : queries) {
            // 借助 upper_boud,找出第一个价格大于 q 的 items,记为  it。
            auto it = upper_bound(items.begin(), items.end(), vector<int>{q, q}, [](const auto &lhs, const auto &rhs) {
                return lhs[0] < rhs[0];
            });
            
            if (it == items.begin()) {
                // 当 it 指向第一个元素时,显然不存在价格小于或等于 q 的商品
                anw.push_back(0);
            } else {
                // 反之,it 的前一个商品的美丽值即为答案。
                anw.push_back((*--it).at(1));
            }
        }
        return anw;
    }
};

5913. 你可以安排的最多任务数目

思路:二分,贪心

时间复杂度O(nlgnlgn)O(n*\lg n*\lg n)

试想,如果存放完成 mm 个任务的方案,则必有一种方案为选中了力量值最大的 mm 个工人。同时不难得出,也必然存在完成 m1m-1 个任务的方案。

反之,如果不存在完成 mm 个任务的方案,也必然不存在完成 m+1m+1 个任务的方案。

因此,可以尝试二分寻找 mm 的最大值。

接下来,问题变为「给出一个 m,如何判定方案是否存在呢」?

首选,选取力量值最大的 mm 个工人。按力量值从低到高依次选取任务:

  • 如果存在可以完成的任务,任选一个即可。因为后续工人的力量值不会更低
  • 如果不存在,则当前工人必须要吃药了。如果无药可吃,或吃药后仍无可选任务,则说明方案不存放。因为每个工人都必须完成一个任务

如果每个工人均有任务可完成,则方案存放,反之则不存放。

注释写的很详细啦~

class Solution {
public:
    bool check(const vector<int> &t, const vector<int> &w, int p, int s, int l) {
        // 初始化一个 multiset,方便个人选取任务
        multiset<int> c;
        for (auto v : t) { c.insert(v); }
        
        // w 已经排序了,选取最大的 l 个工人。
        for (int i = w.size()-l ; i < w.size(); i++) {
            // 找到第一个大于 w[i] 的 task,记为 it,则 c[begin,it) 内的任务可任意选取啦。
            auto it = c.upper_bound(w[i]);
            if (it != c.begin()) {
                // 就选个最小的吧,代码写起来方便。
                c.erase(c.begin());
                continue;
            }
            // 没的可选,那就吃药吧。
            if (p <= 0) {
                // 无药可吃,说明方案不存在。
                return false;
            }
            p--;
            // 吃完药了,寻找第一个大于 w[i]+s 的task,记为 it。则 c[begin,it) 内的任务可任意选取啦。
            it = c.upper_bound(w[i]+s);
            if (it != c.begin()) {
                // !!!注意,他吃药了,要选尽可能大的。因为后续工人的力量值可能小于他。
                c.erase(--it);
                continue;
            }
            // 吃完药仍没得选,说明方案不存在。
            return false;
        }
        return true;
    }
    int maxTaskAssign(vector<int>& tasks, vector<int>& workers, int pills, int strength) {
        // 按力量值升序排序,方便后续使用
        sort(workers.begin(), workers.end());
        int l = 0, r = workers.size();
        // 开始二分
        while (l <= r) {
            int mid = (l+r)>>1;
            // 开始检查
            if (check(tasks, workers, pills, strength, mid)) {
                // 完成 mid 个任务的方案存放,说明答案在 [mid, r] 中
                l = mid+1;
            } else {
                // 完成 mid 个任务的方案不存放,说明答案在 [l, mid-1] 中
                r = mid-1;
            }
        }
        // l-1 即为答案啦,因为只有找到存放方案的 mid,才会将 l 更新为 mid+1。
        return l-1;
    }
};