LeetCode 力扣双周赛 64

148 阅读2分钟

进第一页啦,开心。今天得知上半财年的绩效拿了 A,更开心啦~

周赛传送门

2053. 数组中第 K 个独一无二的字符串

思路:哈希表统计次数

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

总共需要遍历 arrarr 两趟:

  • 第一趟:借助哈希表统计出每种字符串的出现次数
  • 第二趟:借助次数的统计数据,找到第 kk 个独一无二的字符串。
class Solution {
public:
    string kthDistinct(vector<string>& arr, int k) {
        // 先统计一下每种字符串出现的次数
        unordered_map<string, int> cnt;
        for (const auto &s : arr) {
            cnt[s] ++;
        }
        // 再遍历一遍 arr,找到答案
        for (const auto &s : arr) {
            if (cnt[s] == 1) {
                if (--k == 0) {
                    return s;
                }
            }
        }
        return "";
    }
};

2054. 两个最好的不重叠活动

思路:双指针

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

首先将输入的 eventsevents 复制两份,分别记为 ssee

  • ss 按照开始时间升序排序。
  • ee 按照结束时间升序排序。

则答案可表示为:

max0i<n(si.score+maxej.end<si.begin(ej.score))\max_{0\le i \lt n}(s_i.score + \max_{e_j.end \lt s_i.begin}(e_j.score))

比较直观的方法是嵌套两层循环:

  • 外层循环枚举 sis_i
  • 内存循环找出所有满足 ej.end<si.starte_j.end \lt s_i.start 的元素,并记录最优解。

上述思路的时间复杂度为 O(n2)O(n^2)

因为已将 ssee 分别按开始时间以及结束时间排序,因此随着 ii 的递增,符合要求的 jj 是单调递增的。

因此可用双指针的思路,在 O(n)O(n) 的时间复杂度内找出最优解。详见注释~

class Solution {
public:
    int maxTwoEvents(vector<vector<int>>& e) {
        // 将 e 按结束时间升序排序
        sort(e.begin(), e.end(), [](const auto &lhs, const auto &rhs) {
            return lhs[1] < rhs[1];
        });
        // 复制一份 s
        auto s = e;
        // 将 s 按开始时间升序排序
        sort(s.begin(), s.end(), [](const auto &lhs, const auto &rhs) {
            return lhs[0] < rhs[0];
        });
        // anw 为最终答案
        // opt 为满足 e[j].end < s[i].start 中的 max(e[j].score)
        int anw = 0, opt = 0;
        for (int i = 0, j = 0; i < s.size(); i++) {
            // i 增加了,尝试从之前的基础上继续更新 j 和 opt
            while (j < e.size() && e[j][1] < s[i][0]) {
                // j 可以增加,更新一下 opt
                opt = max(opt, e[j][2]);
                j++;
            }
            // 尝试用 opt 和 s[i].score 更新答案
            anw = max(anw, opt + s[i][2]);
        }
        return anw;
    }
};

2055. 蜡烛之间的盘子

思路:预处理

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

对于每次询问 <l,r>\lt l,r\gt,只需知道三个信息就能得出答案:

  • ll 及之后的,坐标最小的蜡烛的位置,记为 ll'
  • rr 及之前的,坐标最大的蜡烛的位置,记为 rr'
  • 在区间 [l,r][l',r'] 中盘子的数量,即为本次询问的答案。需要注意 ll' 可能大于 rr'

以上三种信息,可在 O(n)O(n) 时间复杂度内得到。这样对于每次查询,可在 O(1)O(1) 的时间复杂度内得出答案啦。详见注释~

class Solution {
public:
    vector<int> platesBetweenCandles(string s, vector<vector<int>>& queries) {
        // left[i] 即为 l',即在 i 及其之后的,坐标最小的蜡烛的位置
        vector<int> left(s.size()+1, s.size()+1);
        for (int i = s.size()-1; i >= 0; i--) {
            // 如果当前位置是蜡烛,则 left[i] = i。
            // 否则 left[i] = left[i+1]
            (s[i] == '|') ? left[i] = i : left[i] = left[i+1];
        }
        // right[i] 即为 r',即在 i 及其之前的,坐标最大的蜡烛的位置
        vector<int> right;
        for (int i = 0; i < s.size(); i++) {
            // 如果当前位置是蜡烛,则 right[i] = i。
            // 反之 right[i] = right[i-1]
            if (s[i] == '|') {
                right.push_back(i);
            } else {
                right.push_back(right.empty() ? -1 : right.back());
            }
        }
        // pre[i] 记录了区间[0,i]中的盘子数量。就是一个前缀和啦
        vector<int> pre;
        for (int i = 0; i < s.size(); i++) {
            pre.push_back((s[i] == '|' ? 1 : 0) + (pre.empty() ? 0 : pre.back()));
        }
        
        vector<int> anw;
        for (const auto &q : queries) {
            int l = left[q[0]];
            int r = right[q[1]];
            // 当前仅当区间[l,r]不为空时,才有可能有盘子
            if (l < r) {
                anw.push_back(r - l - pre[r] + pre[l]);
            } else {
                anw.push_back(0);
            }
        }
        return anw;
    }
};

2056. 棋盘上有效移动组合的数目

思路:模拟,枚举

时间复杂度O(64nn2)O(64^n*n^2)

每个棋子的移动方案包含两个维度:

  • 方向:皇后有八个选择,车和相各有四个。
  • 距离:八种选择,0 到 7。

因此,单子棋子最多有 88=648*8 = 64 种移动方案。nn 个棋子组合起来最多有 64n64^n 种方案。又因为题目限制了皇后的数量,因此最多有 6432n164*32^{n-1} 种方案。

介于 n[1,4]n\in [1,4],因此直接暴力枚举所有移动方案,然后逐步模拟移动过程,判断是否合法即可。详见注释~

class Solution {
public:
    bool check(vector<vector<int>> pos, const vector<vector<int>> &combo) {
        vector<vector<int>> goal;
        // 计算每个棋子的目标位置
        for (int i = 0; i < pos.size(); i++) {
            int x = pos[i][0] + combo[i][0]*combo[i][2];
            int y = pos[i][1] + combo[i][1]*combo[i][2];
            goal.push_back({x, y});
        }

        // 开始模拟移动过程
        while (true) {
            bool moving = false;
            // 所有未到达目标位置的棋子,朝着预设方向移动一步
            for (int i = 0; i < pos.size(); i++) {
                if (pos[i][0] != goal[i][0] || pos[i][1] != goal[i][1]) {
                    pos[i][0] += combo[i][0];
                    pos[i][1] += combo[i][1];
                    moving = true;
                }
            }
            if (moving == false) {
                // 没有棋子移动了,说明均已到达目标位置,移动过程结束
                break;
            }
            // 检查是否发生了碰撞
            for (int i = 0; i < pos.size(); i++) {
                for (int j = i+1; j < pos.size(); j++) {
                    if (pos[i][0] == pos[j][0] && pos[i][1] == pos[j][1]) {
                        return false;
                    }
                }
            }
        }
        return true;
    }
    void find(vector<string>& p, vector<vector<int>>& pos, vector<vector<int>> &combo, int &anw) {
        if (combo.size() == p.size()) {
            // n 个棋子的方向和距离都确定了,开始检查
            anw += check(pos, combo);
            return ;
        }

        int i = combo.size();
        // 第 i 个棋子是车
        if (p[i] == "rook") {
            int dx[] = {-1, 0, 1, 0};
            int dy[] = {0, -1, 0, 1};
            // 枚举方向
            for (int j = 0; j < 4; j++) {
                // 枚举距离
                for (int s = 1; s <= 7; s++) {
                    int x = pos[i][0] + dx[j]*s;
                    int y = pos[i][1] + dy[j]*s;
                    if (1 <= x && x <= 8 && 1 <= y && y <= 8) {
                        // 保存棋子 i 移动方案
                        combo.push_back({dx[j], dy[j], s});
                        // 开始构造下一个棋子的
                        find(p, pos, combo, anw);
                        // 销毁棋子 i 的移动方案,为枚举下一个方案做准备
                        combo.pop_back();
                    }
                }    
            }
            // 单独处理原地不动的情况。
            combo.push_back({0, 0, 0});
            find(p, pos, combo, anw);
            combo.pop_back();
        } else if (p[i] == "bishop"){
            int dx[] = {-1,  1, -1, 1};
            int dy[] = {-1, -1,  1, 1};
            for (int j = 0; j < 4; j++) {
                for (int s = 1; s <= 7; s++) {
                    int x = pos[i][0] + dx[j]*s;
                    int y = pos[i][1] + dy[j]*s;
                    if (1 <= x && x <= 8 && 1 <= y && y <= 8) {
                        combo.push_back({dx[j], dy[j], s});
                        find(p, pos, combo, anw);
                        combo.pop_back();
                    }
                }    
            }
            combo.push_back({0, 0, 0});
            find(p, pos, combo, anw);
            combo.pop_back();
        } else {
            int dx[] = {-1,  1, -1, 1, -1, 0, 1, 0};
            int dy[] = {-1, -1,  1, 1, 0, -1, 0, 1};
            for (int j = 0; j < 8; j++) {
                for (int s = 1; s <= 7; s++) {
                    int x = pos[i][0] + dx[j]*s;
                    int y = pos[i][1] + dy[j]*s;
                    if (1 <= x && x <= 8 && 1 <= y && y <= 8) {
                        combo.push_back({dx[j], dy[j], s});
                        find(p, pos, combo, anw);
                        combo.pop_back();
                    }
                }    
            }
            combo.push_back({0, 0, 0});
            find(p, pos, combo, anw);
            combo.pop_back();
        }
    }
    int countCombinations(vector<string>& p, vector<vector<int>>& pos) {
        // combo 保存了一种移动方案。
        // combo[i] 包含三个元素,x,y,d。(x,y)表示第 i 个棋子的移动方案,d 表示移动距离
        vector<vector<int>> combo;
        // anw 存储答案
        int anw = 0;

        // find 是一个递归函数,用于枚举所有的移动方案。
        // find 的第 i 层调用用于构造 combo[i]
        find(p, pos, combo, anw);
        return anw;
    }
};