LeetCode周赛315,打完之后想去应聘LeetCode,因为我上我也行……

大家好,我是梁唐。

今天是周一,照惯例我们来聊聊昨天的LeetCode周赛。

昨天的是LeetCode周赛第315场,由中国银联赞助。PS:银联校招据说一直给得很慷慨,当年校招的时候,听说同年级钱最多的offer就是银联发的。感兴趣的小伙伴可以考虑一下。

评论区对于本场比赛的评价出奇得一致:本场次的赛题质量太差了。甚至有同学吐槽,我觉得我上我也行……

与对应负数同时存在的最大正整数

给你一个 不包含 任何零的整数数组 nums ,找出自身与对应的负数都在数组中存在的最大正整数 k 。

返回正整数 k ,如果不存在这样的整数,返回 -1 。

题解

水题,使用set维护出现的元素,然后按照提议维护答案即可。

class Solution {
public:
    int findMaxK(vector<int>& nums) {
        int ret = -1;
        set<int> st;
        for (auto x: nums) {
            if (st.count(-x)) {
                ret = max(ret, abs(x));
            }
            st.insert(x);
        }
        return ret;
    }
};
复制代码

反转之后不同整数的数目

给你一个由 整数组成的数组 nums 。

你必须取出数组中的每个整数,反转其中每个数位,并将反转后得到的数字添加到数组的末尾。这一操作只针对 nums 中原有的整数执行。

返回结果数组中 不同 整数的数目。

题解

水题,直接按照题意操作即可。

建议使用Python,Python对于字符串的相关操作会非常简单。

class Solution:
    def countDistinctIntegers(self, nums: List[int]) -> int:
        st = set(nums)
        
        for x in nums:
            s = int(str(x)[::-1])
            st.add(s)
            
        return len(st)
复制代码

反转之后的数字和

给你一个 非负 整数 num 。如果存在某个 非负 整数 k 满足 k + reverse(k) = num ,则返回 true ;否则,返回 false 。

reverse(k) 表示 k 反转每个数位后得到的数字。

题解

假设存在k,满足k + reverse(k) = num,那么可以肯定kreverse(k)之间必有一个大于等于n / 2

所以我们可以直接从n/2开始枚举。

同样使用Python完成数字翻转的操作。

class Solution:
    def sumOfNumberAndReverse(self, num: int) -> bool:
        
        for i in range(num//2, num+1):
            x = int(str(i)[::-1])
            if x + i == num:
                return True
        
        return False
复制代码

统计定界子数组的数目

给你一个整数数组 nums 和两个整数 minK 以及 maxK 。

nums 的定界子数组是满足下述条件的一个子数组:

  • 子数组中的 最小值 等于 minK 。
  • 子数组中的 最大值 等于 maxK 。

返回定界子数组的数目。

子数组是数组中的一个连续部分。

题解

这题有一点棘手,有很多隐藏的坑点。首先,明确一下题意。我们要寻找的是区间的个数,要使得区间内的最大值和最小值分别是maxKminK。可以想到,区间内必不能包含小于minK或大于maxK的元素。我们可以先找出nums中小于minK和大于maxK的位置,合法的区间只能出现在这些位置中间,而不能包含这些位置。

我们使用一个vector来记录所有不能包含的位置,这些位置会将完整的数组分割成若干段。我们只需要求出其中每一段的结果相加就是答案。

int n = nums.size();
// 为了防止越界,最左侧加入-1
block.push_back(-1);
for (int i = 0; i < n; ++i) {
    if (nums[i] < minK || nums[i] > maxK) {
        block.push_back(i);
    }
}
// 最右侧加入n
block.push_back(n);
复制代码

接着我们思考一下对于一个不包含越界元素的区间,我们怎么找到合法的区间数量呢?显然,我们直接枚举区间左右两个端点是不行的,时间复杂度过大。既然同时枚举两个端点不行,那固定一个端点枚举另外一个可不可行呢?

假设我们确定了右端点r,对于r而言,能够构成合法区间的所有左侧端点l可能出现的位置是连续的。lr越近,区间内包含的元素越少,越难符合题意,很容易想到,必然存在一个位置k,使得当l在它左侧时区间符合题意,当l出现在它右侧时,不符合题意。通过遍历,我们可以很容易求出k

同时,我们又能发现,如果固定lr越大,区间内的元素也越多。越大的r也对应越大的k。也就是说当我们遍历r的时候,寻找k时不需要每次都从头开始,只需要接着之前的结果继续遍历就行。

如果顺利地推导出上面这些内容,会发现这其实是经典的two pointers算法。我们每次枚举区间的一侧,通过区间内逻辑上的某种连续性和递增性来在O(n)的复杂度内枚举另外一侧。

class Solution {
public:
    long long countSubarrays(vector<int>& nums, int minK, int maxK) {
        vector<int> block;
        int n = nums.size();
        block.push_back(-1);
        for (int i = 0; i < n; ++i) {
            if (nums[i] < minK || nums[i] > maxK) {
                block.push_back(i);
            }
        }
        block.push_back(n);

        long long ans = 0;
        auto get_value = [&](int l, int r) {
            // 区间[lef, rig)内最小值和最大值出现的次数
            int minx = 0, maxx = 0;
            for (int rig = l, lef = l; rig < r; ++rig) {
                if (nums[rig] == minK) minx++;
                if (nums[rig] == maxK) maxx++;
                // 如果最小值和最大值都出现过,则移动左侧端点
                while (lef <= rig && minx > 0 && maxx > 0) {
                    if (nums[lef] == minK) minx--;
                    if (nums[lef] == maxK) maxx--;
                    lef++;
                }
                // 对于当前r,合法的区间左侧的数量是lef - l
                ans += lef - l;
            }
        };

        for (int i = 0; i < block.size()-1; i++) {
            get_value(block[i]+1, block[i+1]);
        }
        return ans;
    }
};
复制代码