LeetCode 力扣周赛 259

106 阅读3分钟

周赛传送门

来公司打黑工了。忙里偷闲打场周赛,水了三道题,还挺满意哈哈哈。

2011. 执行操作后的变量值

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

算了个简单题了。根据每次操作的中间符号计算累加和即可。

class Solution {
public:
    int finalValueAfterOperations(vector<string>& operations) {
        int anw = 0; 
        for (const auto &s : operations) {
            anw += (s[1] == '+' ? 1 : -1);
        }
        return anw;
    }
};

2012. 数组美丽值求和

思路:预处理

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

设有一维数组 sufsufsufisuf_i 表示以 numsinums_i 为起点的后缀中的最小值。

可以从大到小枚举 iiO(n)O(n) 的预处理所有的 sufisuf_isufi=min(numsi,sufi+1)suf_i = min(nums_i, suf_{i+1})

同样的,设有一维数组 preprepreipre_i 表示以 numsinums_i 为终点的前缀中的最大值。从小到大枚举 iiO(n)O(n) 的预处理所有的 preipre_iprei=max(numsi,prei1)pre_i = max(nums_i, pre_{i-1})

至此,每个位置的美丽值可以O(1)O(1)的得到了:

  • 如果 prei1<numsi<sufi+1pre_{i-1} \lt nums_i \lt suf_{i+1},则美丽值为 2。
  • 如果不满足上述条件,且 numsi1numsi<numsi+1nums_{i-1} \le nums_i \lt nums_{i+1},则美丽值为 1。
  • 其他情况为 0。
class Solution {
public:
    int sumOfBeauties(vector<int>& nums) {
        vector<int> suf(nums.size(), nums.back());
        for (int i = nums.size()-2; i >= 0; i--) {
            suf[i] = min(suf[i+1], nums[i]);
        }
        vector<int> pre(nums.size(), nums[0]);
        for (int i = 1; i < nums.size(); i++) {
            pre[i] = max(pre[i-1], nums[i]);
        }
        int sum = 0;
        for (int i = 1; i <= nums.size()-2; i++) {
            if (pre[i-1] < nums[i] && nums[i] < suf[i+1]) {
                sum += 2;
            } else if (nums[i-1] < nums[i] && nums[i] < nums[i+1]) {
                sum += 1;
            }
        }
        return sum;
    }
};

2013. 检测正方形

思路:哈希,对角线

时间复杂度O(addcount)O(add*count)

设有一个容器 markmark,用于记录坐标为 (x,y)(x,y) 的点出现的次数。

对于每个 add(x,y)add(x,y) 操作,仅需执行 mark(x,y)++mark(x,y)\mathrel{+}\mathrel{+}

对于每个 count(x,y)count(x,y) 操作,可以枚举所有出现过的坐标 (x0,y0)(x_0,y_0),判断两者可否构成正方形的对角线,既满足下述两个需求:

  • abs(xx0)=abs(yy0)abs(x-x_0) = abs(y-y_0)
  • xx0x \ne x_0yy0y \ne y_0

如果满足上述需求,可以计算出正方形的另外两点的坐标:

  • x1=x,y1=y0x_1 = x, y_1 = y_0
  • x2=x0,y2=yx_2 = x_0, y_2 = y

拿到四个坐标后,从 markmark 中查询出(x0,y0)(x_0,y_0)(x1,y1)(x_1,y_1)(x2,y2)(x_2,y_2) 三个坐标上点的数量,记为 c0,c1,c2c_0,c_1,c_2

累加 c0c1c2c_0*c_1*c_2 即为本次 count(x,y)count(x,y) 操作的答案。

class DetectSquares {
public:
    // 定义容器 mark,key 为 坐标,value 为出现次数。
    // key 的前32比特存储 x,后32比特存储y。这样就不用定义 hash 以及 operator== 啦
    unordered_map<uint64_t, int> mark;

    DetectSquares() {
    }
    
    void add(vector<int> point) {
        // 更新点出现的次数
        mark[uint64_t(point[0])<<32|point[1]]++;
    }
    
    int count(const vector<int> &point) {
        int x = point[0], y = point[1];
        int anw = 0; // anw 用于存储本次查询的答案

        // 枚举所有出现过的点
        for (const auto &p : mark) {
            int x0 = (p.first>>32), y0 = p.first&0xffff;
            // 判断(x,y) 和 (x0,y0) 能够构成正方形的对角线。
            if (x == x0 || y == y0) continue;
            if (abs(x-x0) != abs(y-y0)) continue;

            int x1 = x, y1 = y0;
            int x2 = x0, y2 = y;
            auto it1 = mark.find(uint64_t(x1)<<32|y1);
            auto it2 = mark.find(uint64_t(x2)<<32|y2);
            // 判断另外两点是否存在。
            if (it1 != mark.end() && it2 != mark.end()) {
                // 累加c0*c1*c2
                anw += it1->second * it2->second * p.second;
            }
        }
        return anw;
    }
};

/**
 * Your DetectSquares object will be instantiated and called as such:
 * DetectSquares* obj = new DetectSquares();
 * obj->add(point);
 * int param_2 = obj->count(point);
 */

2014. 重复 K 次的最长子序列

思路:全排列,枚举子集,剪枝

时间复杂度O(nr!2r)O(n*r!*2^r)nn 为输入字符串的长度,rr为答案的长度上限。

首先我们将出现次数超过 kk 的字符找出来,记为集合 remainremain

同一字符每出现 kk 次就加入 remainremain 一次,这样方便处理序列中有重复字符的情况。

考虑到输入字符串的长度 n[2,k8)n\in[2,k*8)。因此,remainremain 中的元素必然不超过 7 个。

暴力枚举 remainremain 的所有子集的所有排列 pp,大约有 7!277!*2^7 种。对于每种 pp 检查其在 ss 中的出现次数。这样整体的时间复杂度为 O(nr!2r)O(n*r!*2^r)。约为 1e91e9,必然是要超时的。下面介绍一种剪枝策略。

考虑排列 ppss 中出现次数小于 kk,则所有以 pp 为前缀的排列都不可能是候选答案,那就没必要枚举这些排列了。

考虑出现 kk 次的,长度为 r[1,7]r\in[1,7] 的子序列有 C7r2rC_7^r*2^r 种,约为 3r3^r 种。因此加入剪枝之后,只会处理 3r3^r 种有效的排列,其他的排列会因为很短的前缀未出现而被过滤。因此时间复杂度的下限降至 O(3rn)O(3^r*n),至于上限没想到该如何估算🙁。。。

class Solution {
public:
    int k_ = 0;
    bool check(const std::string &input, const std::string &tmp) {
        int i = 0, j = 0, k =0;
        for (; j < input.size(); j++) {
            if (tmp[i] == input[j]) {
                i++;
                if (i == tmp.size()) {
                    i = 0;
                    k++;
                }
            }
        }
        return k >= k_;
    }
    /*
    * part 递归过程中构造的字符串
    * pos 要处理 remain[pos] 了
    * remain 预处理出来的字符集
    * input 输入的字符串 s
    * anw 用于存储答案
    */
    void cst(std::string part, int pos, const std::string &remain, const std::string &input, std::string &anw) {
        // 递归的边界
        if (pos == remain.size()) { return; }

        // 如果将剩余字符都加入 part,其长度仍小于 anw,那么就没必要处理了。
        if (part.size() + remain.size()-pos < anw.size()) { return; }

        // 将当前字符将入 part.
        {
            auto tmp = part;
            tmp += remain[pos];
            do {
                // 检查 tmp 出现的字符是否超过 k
                if (check(input, tmp)) {
                    // 尝试更新答案
                    if (tmp.size() > anw.size() || (tmp.size() == anw.size() && tmp > anw)) {
                        anw = tmp;
                    }
                    // tmp 出现了 k 次,尝试添加其他字符。
                    cst(tmp, pos+1, remain, input, anw);
                }
            } while(std::next_permutation(tmp.begin(), tmp.end()));
        }

        // 当前字符未加入 part 的情况。
        {
            cst(part, pos+1, remain, input, anw);
        }
    }

    string longestSubsequenceRepeatedK(string s, int k) {
        k_ = k; // 将k拷贝至成员函数,方便处理
        std::string remain; // 至少出现 k 次的字符集合
        unordered_map<char, int> count; // 用于统计每种字符的出现次数。
        
        // 统计次数
        for (auto c : s) {
            count[c]++;
        }

        // 初始化 remain。字符每出现 k 次就加入 remain 一次。
        for (auto &p : count) { 
            while (p.second >= k) {
                p.second -= k;
                remain += p.first;
            }
        }
        // 排序是为了使用 std::next_permutation
        sort(remain.begin(), remain.end());

        std::string anw; // 用于存储答案
        cst("", 0, remain, s, anw); // 枚举所有排列并寻找答案

        return anw;
    }
};