LeetCode 力扣双周赛 62

151 阅读1分钟

周赛传送门

2022. 将一维数组转变成二维数组

思路:计算下标

时间复杂度O(nm)O(n*m)

首先判断能否转变:如果一维数组和二维数组包含元素个数相同,则可转换。

若能转换,则不难计算出下标的映射关系,设有一维数组的下标 kk,二维数组的下标 (i,j)(i,j),三者均从 0 开始,三者必然满足:

in+j=ki*n + j = k
class Solution {
public:
    vector<vector<int>> construct2DArray(vector<int>& original, int m, int n) {
        if (m*n != original.size()) {
            return vector<vector<int>>();
        }
        vector<vector<int>> anw(m, vector<int>(n,0));
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                anw[i][j] = original[i*n+j];
            }
        }
        return anw;
    }
};

2023. 连接后等于目标字符串的字符串对

思路:暴力枚举

时间复杂度O(n2s)O(n^2*s)nn 为字符串个数,ss 为字符串的平均长度。

比较粗暴的做法,直接暴力下标 (i,j)(i,j),然后进行拼接校验即可。

class Solution {
public:
    int numOfPairs(vector<string>& nums, string target) {
        int anw = 0;
        for (int i = 0; i < nums.size(); i++) {
            for (int j = 0; j < nums.size(); j++) {
                if (i != j && nums[i] + nums[j] == target) {
                    anw++;
                }
            }
        }
        return anw;
    }
};

思路:哈希

时间复杂度O(ns+len2)O(n*s + len^2)lenlen 为目标串的长度。

借助哈希表记录每种字符串出现的次数,然后枚举 targettarget 的前缀和后缀,从哈希表中查找出两者的出现次数直接计算即可。

class Solution {
public:
    int numOfPairs(vector<string>& nums, string target) {
        unordered_map<string, int> cnt;
        for (const auto &n : nums) {
            cnt[n]++;
        }
        int anw = 0;
        for (int i = 1; i < target.size(); i++) {
            auto pre = target.substr(0, i);
            auto suf = target.substr(i);
            if (pre != suf) {
                anw += cnt[pre] * cnt[suf];
            } else {
                anw += cnt[pre] * (cnt[pre]-1);
            }
        }
        return anw;
    }
};

2024. 考试的最大困扰度

思路:双指针

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

该问题可以转化为:找出一个最长的子数组,其包含的 letter 不超过 k 个。letter 可以是 T 也可是 F

一个比较直观的思路是,枚举 i[0,n)i\in[0,n) 作为子数组的左边界,枚举 j[i,n)j\in[i,n)作为子数组的有边界。

找出其中符合要求的最长的子数组[i,j][i,j]ji+1j-i+1 即为答案。暴力求解所有子数组 [i,j][i,j] 的时间复杂度为 O(n2)O(n^2)

    int bf(const string &anw, int k, char letter) {
        int len = 0;
        for (int i = 0; i < anw.size(); i++) {
            for (int j = i, cnt = 0; j < anw.size(); j++) {
                // 在枚举右边界 j 的过程中,统计区间[i,j]内 letter 的数量。 
                if (anw[j] == letter) {
                    cnt++;
                }
                
                if (cnt == k+1) {
                // letter 数量超过了限制,尝试更新一次 len。
                    len = max(len, j-i);
                    break;
                } else if (j == anw.size()-1) {
                // 枚举到了最后一个字符了,尝试更新一次 len。
                    len = max(len, j-i+1);
                }
            }
        }
        return len;
    }

考虑一个性质:随着 ii 的增大,其对应的最大的 jj 只会单调递增。因此每次 ii 自增后,无需从 ii 开始枚举 jj,而从之前 jj 继续枚举即可。

    int work(const string &anw, int k, char letter) {
        int len = 0;
        // i 从 0 开始,j 从 -1 开始,表示初始时为空区间。
        for (int i = 0, j = -1, cnt = 0; i < anw.size(); i++) {
            // 尝试将 anw[j+1] 加入到子数组中
            while (j+1 < anw.size()) {
                if (anw[j+1] != letter) {
                // 不是letter,直接加入即可
                    j++;
                } else if (cnt < k) {
                // 是letter,但添加后仍然不超过 k,因此可加入。
                    cnt++;
                    j++;
                } else {
                // 是 letter,但添加后超过 k 了,因此不能添加,需break。
                    break;
                }
            }
            // 本次迭代结束,更新一次答案。
            len = max(len, j-i+1);

            // 从子数组中移除 anw[i],为下一轮迭代做好准备。
            cnt -= (anw[i] == letter);
        }
        return len;
    }

完整代码如下:

class Solution {
public:
    int work(const string &anw, int k, char letter) {
        int len = 0;
        // i 从 0 开始,j 从 -1 开始,表示初始时为空区间。
        for (int i = 0, j = -1, cnt = 0; i < anw.size(); i++) {
            // 尝试将 anw[j+1] 加入到子数组中
            while (j+1 < anw.size()) {
                if (anw[j+1] != letter) {
                // 不是letter,直接加入即可
                    j++;
                } else if (cnt < k) {
                // 是letter,但添加后仍然不超过 k,因此可加入。
                    cnt++;
                    j++;
                } else {
                // 是 letter,但添加后超过 k 了,因此不能添加,需break。
                    break;
                }
            }
            // 本次迭代结束,更新一次答案。
            len = max(len, j-i+1);

            // 从子数组中移除 anw[i],为下一轮迭代做好准备。
            cnt -= (anw[i] == letter);
        }
        return len;
    }
    int maxConsecutiveAnswers(string answerKey, int k) {
        return max(work(answerKey, k , 'T'), work(answerKey, k , 'F'));
    }
};

2025. 分割数组的最多方案数

思路:哈希表,前缀和

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

设有一维数组 prepre 表示前缀和:

prei=j=0inumsjpre_i = \sum_{j=0}^i nums_j

另设 nn 个元素的累加和为 totaltotal

当不做修改时,满足要求的 pivotpivot 必有:

prepivot=totalprepivotprepivot=total2\begin{align*}%不会产生编号 pre_{pivot} &= total - pre_{pivot}\\ pre_{pivot} &= \frac{total}{2}\\ \end{align*}

当修改了元素 numspnums_pp[0,n)p \in[0,n),令 d=knumspd = k - nums_p,此时有

  • 对于 i[0,p)i\in[0,p)preipre_i 没有发生变化,因此满足要求的 pivot[0,p)pivot\in[0,p) 必有
prepivot=d+totalprepivotprepivot=d+total2\begin{align*}%不会产生编号 pre_{pivot} &= d + total - pre_{pivot}\\ pre_{pivot} &= \frac{d+total}{2}\\ \end{align*}
  • 对于 i[p,n)i\in[p,n),前缀和发生了变化,因此满足要求的 pivot[p,n)pivot\in[p,n) 必有
prepivot+d=totalprepivotprepivot=totald2\begin{align*}%不会产生编号 pre_{pivot}+d &= total - pre_{pivot}\\ pre_{pivot} &= \frac{total-d}{2}\\ \end{align*}

因此,我们可以在枚举 numspnums_p 的过程中,通过哈希表记录两个区间 [0,p)[0,p) 以及 [p,n)[p, n)prepre 的值出现的次数,从而可以 O(1)O(1) 的得出修改 numspnums_{p} 时的分割方案数。

class Solution {
public:
    int waysToPartition(vector<int>& nums, int k) {
        // pre 用于存储前缀和
        vector<int64_t> pre(nums.size(), nums[0]);

        // sumL 被修改元素之前的前缀和
        // sumR 被修改元素及之后的前缀和
        unordered_map<int64_t, int> sumL, sumR;

        // 统计前缀和,并更新 sumL。
        for (int i = 1; i < nums.size(); i++) {
            pre[i] = pre[i-1] + nums[i];
            sumL[pre[i-1]]++;
        }

        int64_t total = pre.back();
        int anw = (total%2) ? 0 : sumL[total/2];// 不做修改时的分割方案数

        // 枚举被修改的元素 nums[p]
        // 初始时,sumL 包含了长度从 1 到 n-1 的前缀的和。
        // 初始时,sumR 为空。
        for (int p = nums.size()-1; p >= 0; --p) {
            // 修改 nums[p] 带来的变化
            int diff = k - nums[p];

            // 因为分割之后不能有空数组,所以长度为 n 的前缀的和不能加入 sumR。
            if (p != nums.size()-1) {
                sumR[pre[p]]++;
            }

            // 尝试更新答案
            if ((diff+total)%2 == 0) {
                anw = max(anw, sumL[(diff+total)/2] + sumR[(total-diff)/2]);
            }
            // 从 sumL 移除 pre[p-1],为下一轮迭代做准备。
            if (p != 0) {
                sumL[pre[p-1]]--;
            }
        }

        return anw;
    }
};