LeetCode 第 218 场周赛 题解 C++

第 218 场周赛

给作者三连!!!

题目1:5617. 设计 Goal 解析器

思路:模拟

代码:

class Solution {
public:
    string interpret(string s) {
        int n = s.size();
        int i = 0;
        string ret = "";
        while(i < n) {
            if(s[i] == '(' && s[i + 1] == ')') {
                i += 2;
                ret += "o";
            } else if(s[i] == '(') {
                i += 4;
                ret += "al";
            } else {
                i++;
                ret += "G";
            }
        }
        return ret;
    }
};

复杂度分析:

时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( 1 ) O(1) O(1)



题目2:5618. K 和数对的最大数目

思路:哈希表

遍历数组,每遍历到一个元素 x x x,先进入哈希表检查之前是否出现过 k − x k - x kx 这个元素,若出现过则默认将这两个元素一起移出数组,结果值 r e t ret ret 加一;若未出现过则将当前元素存入哈希表。

代码:

class Solution {
public:
    int maxOperations(vector<int>& nums, int k) {
        unordered_map<int, int> mp;
        int ret = 0;
        for(int x : nums) {
            if(mp[k - x]) {
                mp[k - x]--;
                ret++;
            } else {
                mp[x]++;
            }
        }
        return ret;
    }
};

复杂度分析:

时间复杂度为 O ( n ) O(n) O(n),空间复杂度为 O ( n ) O(n) O(n)



题目3:5620. 连接连续二进制数字

思路:模拟

我们观察 n = 3 n = 3 n=3 的情况,拼接得到的二进制串为 1   10   11 1\ 10\ 11 1 10 11,转为十进制,相当于 1 ∗ 2 4 + 2 ∗ 2 2 + 3 1 * 2^4 + 2 * 2^2 + 3 124+222+3,其中 2 4 2^4 24 表示 2 、 3 2、3 23 对应的二进制串长度为 4 4 4 2 2 2^2 22 表示 3 3 3 对应的二进制串长度为 2 2 2 ,所以我们遍历 1 − n 1-n 1n,求出当前元素二进制串长度 p p p,其对应的权值 2 p 2^p 2p 就可以作为上一个元素得到结果的偏移量。

由于相邻的数字 k , k + 1 k, k + 1 k,k+1 对应的二进制串长度基本相同或相差 1 1 1,所以不用每次从头开始计算,维护一个变量随着数组遍历过程不断变化即可。

代码:

class Solution {
public:
    const int M = 1e9 + 7;
    int concatenatedBinary(int n) {
        int ret = 0;
        int p = 1;
        for (int i = 1; i <= n; ++i) {
            while (p <= i) {	// 相当于求log(i)
                p *= 2;
            }
            ret = ((long long)ret * p + i) % M;
        }
        return ret;
    }
};

复杂度分析:

时间复杂度为 O ( n l g n ) O(nlgn) O(nlgn),空间复杂度为 O ( 1 ) O(1) O(1)



题目4:5619. 最小不兼容性

思路:状压DP

看到 N N N 的范围,立即想到进行状态压缩。

我们从初始状态(所有数字都在一起)开始,尝试所有可能的将当前数字分成两堆的方案,然后递归求解左堆和右堆。为了避免重复计算,我们需要使用记忆化。

在枚举分组方案时,如果我们朴素地遍历 [ 1... s t a t e ] [1...state] [1...state] s t a t e state state 为当前堆中的数字),总时间复杂度将达到 O ( 4 N ) O(4^N) O(4N),对于 N = 16 N=16 N=16,这是我们不能接受的。因此,我们需要使用子集枚举的优化(注释中优化二),从而将总时间复杂度降低到 O ( 3 N ) O(3^N) O(3N)

特别地,在固定 N N N n u m s nums nums 的情况下, K K K 越大意味着分组越小,从而有效分组方案越多,处理时间越长。但是 K = N K = N K=N 时,所有组的大小都为 1 1 1 ,不兼容性总和一定为 0 0 0,所以我们可以进行特判,从而极大的降低运行时间(注释中优化一)。

有了这两个优化,实际上已经可以通过题目的时间限制。不过,我们还可以通过引入优化三优化四,来进一步减少代码的运行时间。

代码:

const int INF = 0x3f3f3f3f;
int memo[65536], v[16];

class Solution {
    int n, k, sz;
    vector<int> nums;
    int solve(int state) {
        if (memo[state] != -1)
            return memo[state];
        
        // 边界条件:当前集合刚好包含n/k个元素,不需要继续划分
        if (__builtin_popcount(state) == sz) {
            int idx = 0;
            for (int i = 0; i < n; ++i) {
                if (state & (1 << i)) {
                    // 将包含的元素存入分组
                   v[idx++] = i;
                }
        	}
            for (int i = 0; i + 1 < sz; ++i) {
                // 一个分组有两数相同,则说明是不满足要求的分组
                if (nums[v[i]] == nums[v[i + 1]]) {
                    return memo[state] = INF;
                }
            }
            return memo[state] = nums[v[n / k - 1]] - nums[v[0]];
        }
        int ans = INF;
        
        // 优化二:子集枚举优化
        for (int sub = state - 1; sub; sub = (sub - 1) & state) {
            if (__builtin_popcount(sub) % sz != 0)
                continue;
            // left 和 right 分别表示将当前集合再分为两个子集
            int left = solve(sub);
            
            // 优化三:如果左边的最优值已经达到了当前总和的最优值,则不需要继续计算右边。
            if (left >= ans)
                continue;
            int right = solve(state ^ sub);
            ans = min(ans, left + right);
        }
        return memo[state] = ans;
    }

public:
    int minimumIncompatibility(vector<int>& nums, int k) {
        n = nums.size();
        sz = n / k;
        
        // 优化一:每个子集的大小为1,不兼容性显然为0,总和也是0。
        // 这种情况的筛除非常重要,因为它需要最多的枚举次数。
        if (sz == 1)
            return 0;
        
        sort(nums.begin(), nums.end());
        vector<int> cnt(n + 1);
        for (int num : nums) {
            // 记录每个数字出现的次数,若大于组数则说明不能分组
            cnt[num]++;
            if (cnt[num] > k)
                return -1;
        }
        this->k = k;	// 更新全局变量
        this->nums = nums;
        for (int i = 0; i < (1 << n); ++i)
            memo[i] = -1; 
        return solve((1 << n) - 1);
    }
};

复杂度分析:

时间复杂度为 O ( 3 n ) O(3^n) O(3n),空间复杂度为 O ( 2 n ) O(2^n) O(2n)

猜你喜欢

转载自blog.csdn.net/weixin_42396397/article/details/110733495
今日推荐