大家好,我是梁唐。
今天是周一,照惯例我们来聊聊昨天的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
,那么可以肯定k
和reverse(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 。
返回定界子数组的数目。
子数组是数组中的一个连续部分。
题解
这题有一点棘手,有很多隐藏的坑点。首先,明确一下题意。我们要寻找的是区间的个数,要使得区间内的最大值和最小值分别是maxK
和minK
。可以想到,区间内必不能包含小于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
可能出现的位置是连续的。l
离r
越近,区间内包含的元素越少,越难符合题意,很容易想到,必然存在一个位置k
,使得当l
在它左侧时区间符合题意,当l
出现在它右侧时,不符合题意。通过遍历,我们可以很容易求出k
。
同时,我们又能发现,如果固定l
,r
越大,区间内的元素也越多。越大的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;
}
};
复制代码