LeetCode 力扣周赛 263

111 阅读3分钟

感冒了,周末一直发烧。各位老铁记得记得多喝热水,谨防感冒🤧。

周赛传送门

2042. 检查句子中的数字是否递增

思路:字符串分割

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

可以按空格分割,然后转化为数字依次比较即可。

class Solution {
public:
    bool areNumbersAscending(string s) {
        // pre 记录前一个数字
        // now 记录刚刚转化得到的数字
        int pre = -1, now = -1;
        // 默认放入一个空格,这样使得每个 word 后都有一个空格,方便处理。
        s.push_back(' ');
        for (int i = 0; i < s.size(); i++) {
            if (s[i] >= '0' && s[i] <= '9') {
                now = (now == -1) ? (s[i]-'0') : (now*10 + s[i]-'0');
            } else {
                if (now != -1) {
                    if (now <= pre) {
                        return false;
                    }   
                    pre = now;
                    now = -1;
                }
            }
        }
        return true;
    }
};

2043. 简易银行系统

思路:模拟

时间复杂度O(n+m)O(n+m)nn 为操作次数,mm 为用户数量。

比较简单的模拟题啦,直接把题目翻译成代码就好啦。

class Bank {
public:
    vector<long long> bal;
    Bank(vector<long long>& b) {
        bal.reserve(b.size() + 1);
        bal.emplace_back(0); // 添加一份冗余数据,对齐账户编号和下标。
        bal.insert(bal.end(), b.begin(), b.end());
    }
    
    bool transfer(int a1, int a2, long long money) {
        if (a1 < 1 || bal.size() < a1 || a2 < 1 || bal.size() < a2) {
            return false;
        }
        if (bal[a1] < money) {
            return false;
        }
        bal[a1] -= money;
        bal[a2] += money;
        return true;
    }
    
    bool deposit(int account, long long money) {
        if (account < 1 || bal.size() < account) {
            return false;
        }
        bal[account] += money;
        return true;
    }
    
    bool withdraw(int account, long long money) {
        if (account < 1 || bal.size() < account) {
            return false;
        }
        if (bal[account] < money) {
            return false;
        }
        bal[account] -= money;
        return true;
    }
};

/**
 * Your Bank object will be instantiated and called as such:
 * Bank* obj = new Bank(balance);
 * bool param_1 = obj->transfer(account1,account2,money);
 * bool param_2 = obj->deposit(account,money);
 * bool param_3 = obj->withdraw(account,money);
 */

2044. 统计按位或能得到最大值的子集数目

思路:位运算

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

空间复杂度O(1)O(1)

枚举所有子集,然后计算每个子集的值即可。

class Solution {
public:
    int countMaxOrSubsets(vector<int>& nums) {
        int mask = (1<< nums.size());

        // val 用于存储最大值
        // cnt 存储次数
        int val = -1, cnt = 0;

        // 枚举状态 i
        for (int i = 0; i < mask; i++) {
            int v = 0;
            // 计算状态 i 的值
            for (int j = 0, b = 1; j < nums.size(); j++, b<<=1) {
                if (i&b) {
                    v |= nums[j];
                }
            }
            // 尝试更新最大值以及次数。
            if (v > val) {
                val = v, cnt = 1;
            } else  if (v == val) {
                cnt++;
            }
        }
        return cnt;
    }
};

思路:动态规划

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

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

除空集外,每个子集都可由另一个子集添加一个数字得到。因此不难想到:

dpi=dpjdpkdp_i = dp_j | dp_k

其中,

  • j & k=0j\ \&\ k = 0
  • j  k=ij\ |\ k = i
class Solution {
public:
    int countMaxOrSubsets(vector<int>& nums) {
        int mask = (1<< nums.size());

        // val 用于存储最大值
        // cnt 存储次数
        int val = 0, cnt = 1;
        vector<int> dp(mask, 0);

        // 初始化仅含单个数字的集合
        for (int i = 0; i < nums.size(); i++) {
            dp[1<<i] = nums[i];
        }

        // 枚举状态 i
        for (int i = 1; i < mask; i++) {
            int j = i&(i-1);
            int k = i^j;
            dp[i] = dp[j] | dp[k];
            // 尝试更新最大值以及次数。
            if (dp[i] > val) {
                val = dp[i], cnt = 1;
            } else  if (dp[i] == val) {
                cnt++;
            }
        }
        return cnt;
    }
};

2045. 到达目的地的第二短时间

思路:BFS

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

先考虑没有红绿灯的情况,该题就是一个标准的 BFS 题目。

先回忆一下BFS求最短路的过程,设有数组 d0d_0d0,id_{0,i} 表示节点 00ii 的最短距离。每次从队首取出节点 uu,设 vvuu 相邻节点,当且仅当 d0,u+w<d0,vd_{0,u} + w \lt d_{0,v} 成立时 vv 进入队列,其中 ww 为边权。

为求次短路,需引入数组 d1,id_{1,i},表示节点 00ii 的严格次短距离。为了更新两个距离数组,每个节点需进入队列两次。为了便于区分,队列需记录节点编号及距离。

从队首取出节点及距离(u,d)(u,d),设 vvuu 的相邻节点。当且仅当满足下述条件之一时,vv 加入队列。

  • d+w<d0,vd + w \lt d_{0,v}(v,d+w)(v,d+w) 加入队列
  • d+w=d0,vd + w = d_{0,v}d+w<d1,vd+w \lt d_{1,v}(v,d+w)(v,d+w) 加入队列

接下来考虑,引入红绿灯后 BFS 的正确性。

因此所有节点的红绿灯切换是一致的,且每条边的权值相同,所以走过两条长度相同的路径,其等待红灯的时间必然相同,因此总时间也相同。

而走过较长的路径,其等待红灯的时间必然不小于较短的路径。因此引入红绿灯后,较短路径的总时间仍小于较长的路径。

甚至,由于红灯的可预见性,我们可以在找出次短路之后,再计算引入红绿灯之后的总时长。

class Solution {
public:
    int secondMinimum(int n, vector<vector<int>>& edges, int time, int change) {
        // 建立边表
        vector<int> edge[10001];
        for (const auto &e : edges) {
            edge[e[0]].push_back(e[1]);
            edge[e[1]].push_back(e[0]);
        }

        // 初始化距离数组
        vector<vector<int>> dis(n+1, vector<int>(2, INT_MAX));
        dis[1][0] = 0;
        
        // 将源点加入队列
        queue<pair<int, int>> q;
        q.push(make_pair(1, 0));

        // 开始BFS
        while(!q.empty()) {
            // 取出队首的元素(u,d)
            auto f = q.front();
            q.pop();
            int u = f.first;
            int d = f.second;
            // 遍历相邻节点 v
            for (auto v : edge[u]) {
                int cost = d + 1; // 到达相邻节点的距离为 d + 1
                // d+1 < d0[v]
                if (cost < dis[v][0]) {
                    dis[v][1] = dis[v][0];
                    dis[v][0] = cost;
                    q.push(make_pair(v, cost));
                }
                // d0[v] < d+1 && d+1 < d1[v]
                else if (dis[v][0] < cost && cost < dis[v][1]) {
                    dis[v][1] = cost;
                    q.push(make_pair(v, cost));
                }
            }
        }
        // len 是次短路的长度,即次短路包含的边数
        int len = dis[n][1];
        // cost 为走过次短路的总时长
        int cost = 0; 
        for (int i = 0; i < len; i++) {
            // 到达路径上第 i 个点的时间点为 cost
            // 如果 (cost / change) 是个奇数,说明此时是红灯,需等待change - cost%change
            if ((cost / change)&1) {
                cost += change - cost%change;
            }
            // 再加上 i -> i+1 所需的时间,即走过一条边的时间。
            cost += time;
        }
        return cost;
    }
};