LeetCode周赛310,卷上天了,只有前5名才能直通面试

大家好,我是梁唐。

照惯例我们来看下昨天的LeetCode周赛。这一场是LeetCode第310场,由趋势科技赞助举办。

其中1-5名可以直通面试……

我们来看看欢乐的评论区……

出现最频繁的偶数元素

给你一个整数数组 nums ,返回出现最频繁的偶数元素。

如果存在多个满足条件的元素,只需要返回 最小 的一个。如果不存在这样的元素,返回 -1

题解

水题,统计一下所有偶数出现的次数,找到其中出现次数最多并且最小的即可。

class Solution {
public:
    int mostFrequentEven(vector<int>& nums) {
        int ret = -1, maxi = 0;
        map<int, int> mp;
        for (auto &x : nums) {
            if (x % 2) continue;
            mp[x]++;
            if (mp[x] > maxi || (mp[x] == maxi && x < ret)) {
                ret = x;
                maxi = mp[x];
            } 
        }
        return ret;
    }
};
复制代码

子字符串的最优划分

给你一个字符串 s ,请你将该字符串划分成一个或多个 子字符串 ,并满足每个子字符串中的字符都是 唯一 的。也就是说,在单个子字符串中,字母的出现次数都不超过 一次

满足题目要求的情况下,返回 最少 需要划分多少个子字符串*。*

注意,划分后,原字符串中的每个字符都应该恰好属于一个子字符串。

题解

注意,题目当中说的是子字符串,而非子序列,子字符串需要保证连续。

既然要保证连续,那么当然是在划分的时候尽可能多地包含尽量多的字符。所以这是一道明显的贪心题。

我们可以使用set来存储当前分割的子字符串,当遇到重复字符时意味着需要新分割一个子串,我们用清空set的操作来代替新创建子串。维护一下创建新子串的次数即为答案。

class Solution {
public:
    int partitionString(string s) {
        int n = s.length();
        int ret = 0;
        
        set<char> st;
        for (int i = 0; i < n; i++) {
            if (st.count(s[i])) {
                ret++;
                st.clear();
            }
            st.insert(s[i]);
        }
        return ret+1;
    }
};
复制代码

将区间分为最少组数

给你一个二维整数数组 intervals ,其中 intervals[i] = [lefti, righti] 表示 区间 [lefti, righti]

你需要将 intervals 划分为一个或者多个区间 ,每个区间 属于一个组,且同一个组中任意两个区间 不相交

请你返回 最少 需要划分成多少个组。

如果两个区间覆盖的范围有重叠(即至少有一个公共数字),那么我们称这两个区间是 相交 的。比方说区间 [1, 5][5, 8] 相交。

题解

这是一道区间覆盖问题,我们当然可以用线段树来区间操作,当然仔细想想其实也能发现更简单的解法。

我们分析题意会发现,同一个分组内的区间不能互相重叠。也就意味着如果存在多个区间互相之间有重叠,它们需要被划分进不同的分组当中。为了方便发现区间重叠的情况,我们可以对区间进行排序。维护每一个分组的结束位置,如果下一个区间的开始位置在它之前,那么说明它们会发生重叠,我们需要新开辟一个分组。

那么,我们怎么知道有没有一个分组可以容纳当前这个区间呢?答案也很简单,我们选择结束位置最早的分组。因为所有区间都是按照从小到大顺序排序的,如果连结束得最早的分组都无法兼容,那么其他分组也一样无法兼容。

我们可以使用一个优先队列来维护每一个分组结束的时间,队列头部的元素,就是最早结束的分组下标。

class Solution {
public:
    int minGroups(vector<vector<int>>& inte) {
        sort(inte.begin(), inte.end());
        priority_queue<int, vector<int>, greater<int>> pq;
        
        for (auto &vt: inte) {
            int s = vt[0], e = vt[1];
          	// 如果队列非空,并且最早结束的分组能够兼容
            if (!pq.empty() && pq.top() < s) {
              	// 弹出,即更新该分组的结束位置
                pq.pop();
            }
            pq.push(e);
        }
      	// 队列中的元素数量即为答案
        return pq.size();
    }
};
复制代码

最长递增子序列 II

给你一个整数数组 nums 和一个整数 k

找到 nums 中满足以下要求的最长子序列:

  • 子序列 严格递增
  • 子序列中相邻元素的差值 不超过 k

请你返回满足上述要求的 最长子序列 的长度。

子序列 是从一个数组中删除部分元素后,剩余元素不改变顺序得到的数组。

题解

曾经有一道经典的动态规划例题,叫做最长不下降子序列。其实这两题的解法一致,我们也需要维护数组当中的每一个元素,以它为结尾的最长子序列的长度。

比如元素nums[i],如果我们求出了它之前所有位置的最长子序列的长度。那么我们只需要遍历0到i为止的所有元素,找到满足nums[i]-k<=nums[j]<nums[i]的长度最大的j,就可以知道当前nums[i]对应的最长子序列的长度。

这样的解法是OK的,但是有一个比较大的问题:这样做时间复杂度太大,对于每一个位置我们都要遍历它之前所有的元素。时间复杂度是O(n2)O(n^2)。本题的数据量级是1e5,在这样的复杂度下是无法通过的。

我们可以进一步思考,对于nums[i]来说,我们其实只关心在[nums[i]-k, nums[i])这个半闭半开区间内元素对应的子序列长度的最大值。这是一个经典的区间求最值的问题,是经典的单点更新,区间查找的线段树的使用场景。

熟悉线段树的同学相信很容易即可写出代码,不熟悉的同学可能需要去学习一下线段树这个数据结构。

class Solution {
public:
  	// 数组存储线段树中所有节点
    int arr[300500] {0};
    const int N = 100050;
    
  	// k是线段树节点下标,它存储[l, r)区间内最大值,查询区间为[ll, rr)
    int query(int k, int l, int r, int ll, int rr) {
        if (ll <= l && rr >= r) {
            return arr[k];
        }
        if (rr <= l || ll >= r) {
            return 0;
        }
        int m = (l + r) >> 1;
        if (rr <= m) return query(k << 1, l, m, ll, rr);
        if (ll >= m) return query(k << 1 | 1, m, r, ll, rr);
        return max(query(k << 1, l, m, ll, rr), query(k << 1 | 1, m, r, ll, rr));
    }
    
  	// 将节点p更新成v
    void update(int k, int l, int r, int p, int v) {
        arr[k] = max(arr[k], v);
        if (l+1 >= r) return ;
        int m = (l + r) >> 1;
        if (p < m) update(k << 1, l, m, p, v);
        if (p >= m) update(k << 1 | 1, m, r, p, v);
    }
    
    int lengthOfLIS(vector<int>& nums, int k) {
        int n = nums.size();
        int ret = 0;
        for (int i = 0; i < n; i++) {
            int v = query(1, 0, N, nums[i] - k, nums[i]) + 1;
            update(1, 0, N, nums[i], v);
            ret = max(ret, v);
        }
        return ret;
    }
};
复制代码