大家好,我是梁唐。
还是照惯例我们来看看昨天的LeetCode周赛,这一场是第316场。由华人运通高合汽车赞助,夺得第一的小伙伴可以获得车模一个。
这一次的比赛题意中藏了不少信息,因此赛后有些小伙伴调侃为阅读理解专场。
总体来说这次比赛的题目质量还是不错的,不过相对来说数学和思维题较多,因此也有人吐槽,说自己不爱数学……
闲言少叙,下面我们来看题。
判断两个事件是否存在冲突
给你两个字符串数组 event1
和 event2
,表示发生在同一天的两个闭区间时间段事件,其中:
event1 = [startTime1, endTime1]
且event2 = [startTime2, endTime2]
事件的时间为有效的 24 小时制且按 HH:MM
格式给出。
当两个事件存在某个非空的交集时(即,某些时刻是两个事件都包含的),则认为出现 冲突 。
如果两个事件之间存在冲突,返回 true
;否则,返回 false
。
题解
我们需要判断两个时间区间是否有重叠。如果我们把两个区间写成[s1, e1], [s2, e2]
的话,如果两个区间有重叠意味着: 或。
比较麻烦的是这题当中的时间都是以字符串给定的,需要我们手动分割一下。为了偷懒使用了Python,用C++也差不太多。
class Solution:
def haveConflict(self, event1: List[str], event2: List[str]) -> bool:
def greater(time1, time2):
h1, m1 = list(map(int, time1.split(':')))
h2, m2 = list(map(int, time2.split(':')))
return h1 > h2 or (h1 == h2 and m1 >= m2)
return greater(event1[1], event2[0]) and greater(event2[0], event1[0]) or greater(event1[0], event2[0]) and greater(event2[1], event1[0])
复制代码
最大公因数等于 K 的子数组数目
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 nums 的子数组中元素的最大公因数等于 k 的子数组数目。
子数组 是数组中一个连续的非空序列。
数组的最大公因数 是能整除数组中所有元素的最大整数。
题解
对于长度为n的区间包含的子数组的个数应该是,如果再要遍历每一个元素的话,时间复杂度会达到,对于当前题目的复杂度来说是无法接受的。
所以我们要进行优化,如果对最大公约数的定义熟悉的话,可以想到对于若干个数的最大公约数来说。随着元素的增加,最大公约数有变小的趋势。并且如果,那么
综合上面两点可以想到,我们可以想到,我们并不需要枚举所有的区间,再遍历求取每个区间的最大公约数。我们可以枚举区间的左侧端点,再从左往右依次添加元素,当最大公约数不再是的倍数时退出(最大公约数有变小的趋势),枚举下一个左侧端点。
class Solution {
public:
int subarrayGCD(vector<int>& nums, int k) {
int ret = 0;
int n = nums.size();
for (int i = 0; i < n; i++) {
// 当前最大公约数
int cur = nums[i];
for (int j = i; j < n; j++) {
cur = gcd(cur, nums[j]);
// 等于k时答案+1
if (cur == k) ret++;
// 不能整除k时退出
if (cur % k) break;
}
}
return ret;
}
};
复制代码
使数组相等的最小开销
给你两个下标从 0 开始的数组 nums 和 cost ,分别包含 n 个 正 整数。
你可以执行下面操作 任意 次:
- 将 nums 中 任意 元素增加或者减小 1 。
对第 i 个元素执行一次操作的开销是 cost[i] 。
请你返回使 nums 中所有元素 相等 的 最少 总开销。
题解
我们假设最后所有数都是,我们把nums
叫做,cost
叫做。我们代入之后写出最后总开销的方程:
这个式子当中存在绝对值看起来非常讨厌,但没有关系,我们可以分情况讨论把绝对值符号消除。假设,此时对于所有的式子去掉绝对值后的系数都是,所有式子合并之后的系数是。当时,此时每一项的系数都是,所有式子合并之后的系数为。
当在最大值和最小值中间时,一部分式子的系数为正,一部分为负。合并之后的系数可能是正也可能是负,很容易想到最后式子的系数和值的大小是正相关的。在某个临界点之前为负,到了某个临界点之后为正。既然如此,那么整个函数就是先减后增的。
对于先减后增的函数求最小值,我们可以使用三分法。如果对三分法不了解的同学可以点击下方链接跳转一下。没记错的话,这是我在公众号上更的第二篇算法文。
class Solution {
public:
long long minCost(vector<int>& nums, vector<int>& cost) {
long long ret = 0;
int n = nums.size();
int maxi = 0, mini = 0x3f3f3f3f;
for (int i = 0; i < n; i++) {
maxi = max(maxi, nums[i]);
mini = min(mini, nums[i]);
}
int l = mini-1, r = maxi+1;
auto get_cost = [&](int m) -> long long {
long long ret = 0;
for (int i = 0; i < n; i++) {
ret += cost[i] * (long long) abs(m - nums[i]);
}
return ret;
};
while (l+2 < r) {
int len = (r - l) / 3;
int m1 = l + len;
int m2 = m1 + len;
long long c1 = get_cost(m1), c2 = get_cost(m2);
if (c1 <= c2) {
r = m2;
ret = c1;
}else {
l = m1;
ret = c2;
}
}
return ret;
}
};
复制代码
所以本质上这题就是一个方程求最值的问题,希望大家不要被我的想法限制住,除了三分法之外还有很多可行的方法。
使数组相似的最少操作次数
给你两个正整数数组 nums 和 target ,两个数组长度相等。
在一次操作中,你可以选择两个 不同 的下标 i 和 j ,其中 0 <= i, j < nums.length ,并且:
- 令 nums[i] = nums[i] + 2 且
- 令 nums[j] = nums[j] - 2 。
如果两个数组中每个元素出现的频率相等,我们称两个数组是 相似 的。
请你返回将 nums 变得与 target 相似的最少操作次数。测试数据保证 nums 一定能变得与 target 相似。
题解
这题看起来很难,但实际上有一个trick:一个数每次操作它的奇偶性保持不变,即奇数仍然是奇数,不能变成偶数。进而可以想到我们可以把nums
和target
中的数字都按照奇偶性分成两组。
每一次操作会将一个数+2,而将另外一个数-2,也就是所有数的总和保持不变。题目保证最后一定有解,那么nums
和target
的和相同。我们将它们排序对齐之后,必然一部分数小于target
一部分大于target
。直观上我们很容易想到,按照贪心的思路,依次操作对齐。
每一次操作,一个数增加2,一个数减少2,带来的它们之间的差值变化是4。所以最后答案就是两个数组差值绝对值的和再除以4。
评论区中有大神进行了简单的证明:
假设nums
数组中有两个值ax, ay
,target
数组当中有两个值bx, by
。可以证明无论什么情况,直接对应都是最优的。
class Solution {
public:
long long makeSimilar(vector<int>& nums, vector<int>& target) {
vector<int> odd_n, even_n, odd_t, even_t;
for (auto x: nums) {
if (x % 2) odd_n.push_back(x);
else even_n.push_back(x);
}
for (auto x: target) {
if (x % 2) odd_t.push_back(x);
else even_t.push_back(x);
}
#define SORT(x) sort(x.begin(), x.end())
SORT(odd_n); SORT(even_n); SORT(odd_t); SORT(even_t);
int n = odd_n.size();
long long diff = 0;
for (int i = 0; i < n; i++) diff += abs(odd_n[i] - odd_t[i]);
n = even_n.size();
for (int i = 0; i < n; i++) diff += abs(even_n[i] - even_t[i]);
return diff / 4;
}
};
复制代码