这周的双周赛总体简单,主要是最后一题有些难度。
第一题:模拟。
第二题:滑动窗口。
第三题:数学。
第四题:0 / 1 最短路(BFS)。
详细题解如下。
1. 将数字变成 0 的操作次数(Number of Steps to Reduce A Number to Zero)
AC代码(C++)
2. 大小为 K 且平均值大于等于阈值的子数组数目(Number of Sub Arrays of Size K And Average Greater Than or Equal to Threshold)
AC代码(C++)
3.时钟指针的夹角(Angle Between Hands of A Clock)
AC代码(C++)
4.跳跃游戏 IV(Jump Game IV)
AC代码(C++)
LeetCode第19场双周赛地址:
https://leetcode-cn.com/contest/biweekly-contest-19/
https://leetcode-cn.com/problems/number-of-steps-to-reduce-a-number-to-zero/
给你一个非负整数
num
,请你返回将它变成 0 所需要的步数。 如果当前数字是偶数,你需要把它除以 2 ;否则,减去 1 。示例 1:
输入:num = 14 输出:6 解释: 步骤 1) 14 是偶数,除以 2 得到 7 。 步骤 2) 7 是奇数,减 1 得到 6 。 步骤 3) 6 是偶数,除以 2 得到 3 。 步骤 4) 3 是奇数,减 1 得到 2 。 步骤 5) 2 是偶数,除以 2 得到 1 。 步骤 6) 1 是奇数,减 1 得到 0 。
提示:
- 0 <= num <= 10^6
题目意思很简单,其实在整数中除 2 相当于位运算的右移 1 位。对于 int 型,32位,所以最多循环 62 次(如果是奇数,相当于先 - 1,再 >> 1。如果是偶数,那就是 >> 1)
因此根据题目意思进行模拟即可,不会超时。
class Solution {
public:
int numberOfSteps (int num) {
int cnt = 0;
while(num)
{
if(num % 2 == 0)
num /= 2;
else
--num;
++cnt;
}
return cnt;
}
};
https://leetcode-cn.com/problems/number-of-sub-arrays-of-size-k-and-average-greater-than-or-equal-to-threshold/
给你一个整数数组
arr
和两个整数k
和threshold
。请你返回长度为
k
且平均值大于等于threshold
的子数组数目。示例 1:
输入:arr = [2,2,2,2,5,5,5,8], k = 3, threshold = 4 输出:3 解释:子数组 [2,5,5],[5,5,5] 和 [5,5,8] 的平均值分别为 4,5 和 6 。其他长度为 3 的子数组的平均值都小于 4 (threshold 的值)。
示例 3:
输入:arr = [11,13,17,23,29,31,7,5,2,3], k = 3, threshold = 5 输出:6 解释:前 6 个长度为 3 的子数组平均值都大于 5 。注意平均值不是整数。
提示:
1 <= arr.length <= 10^5
1 <= arr[i] <= 10^4
1 <= k <= arr.length
0 <= threshold <= 10^4
看到想到的先是,暴力求和每一段 k 的和,复杂度是 O(n ^ 2),这样子会超时。
经过分析,我们可以发现,当我们计算得到某一段的值 sum 时,对于下一段的值,可以不用重新计算那么多,其实就是 sumNow = sum + 后面新进来的那个值 - 前面出去的值。
这样子,我们只需要遍历一次 O(N) 的复杂度就是可以,所有 长度为 k
的子数组的和,然后再判断每一段的平均值是否大于 阈值(平均值 > 可以转化为 sum > k * 阈值,把 除法 转化为 乘法)。.
也就是使用了,用 k 的窗口去滑动计算值
class Solution {
public:
int numOfSubarrays(vector& arr, int k, int threshold) {
int n = arr.size();
if(n < k) return 0;
int cnt = 0;
int sum = 0;
// 第一段长度为 k 的子数组和
for(int i = 0;i < k;++i)
{
sum += arr[i];
}
if(sum >= k * threshold) ++cnt;
// 新的子数组和,加上当前值,减去最前面的那个值
for(int i = k;i < n;++i)
{
sum = sum - arr[i - k] + arr[i];
if(sum >= k * threshold) ++cnt;
}
return cnt;
}
};
https://leetcode-cn.com/problems/angle-between-hands-of-a-clock/
给你两个数
hour
和minutes
。请你返回在时钟上,由给定时间的时针和分针组成的较小角的角度(60 单位制)。示例 1:
(示例有图,具体看链接) 输入:hour = 12, minutes = 30 输出:165
提示:
1 <= hour <= 12
0 <= minutes <= 59
- 与标准答案误差在
10^-5
以内的结果都被视为正确结果。
一道数学分析题,我们可以以 12 点时候为 0 角度,分别计算 时针和分针的角度,然后两个角度作差后,取这个差值和 360 - 差值中的最小值(因为角度是 <= 180的)。
先分析 分针的角度,总共 60 m 分 360 °,所以是 6 ° / m,所以分针的角度就是 = 6 * 分针时间
接着分析 时针的角度,总共 12 h 分 360°,所以是 30 ° / h,但是在一小时内,时针走过的角度,还会与分针有关(可以看第一个示例图),所以还要再加上角度由于分针走的角度,那么在一个小时 30 °,会由于时针 60 m,所以也就是 0.5 °/ m。所以时针的角度总共是 = 30 * 时针 + 0.5 * 分针。
注意的地方,根据数据范围 h 是 1 - 12,但我们由于 12 点是 0 °,所以我们对时针要使得 12 变成 0,其他不影响,所以先时针对 12 进行取模。
这道题不难,主要就是数学分析后,再进行按最后结果计算即可。
class Solution {
public:
double angleClock(int hour, int minutes) {
hour %= 12;
double hAng = 0.5 * minutes + 30 * hour;
double mAng = 6 * 1.0 * minutes;
double res = fabs(hAng - mAng);
res = min(res, 360 - res);
return res;
}
};
https://leetcode-cn.com/problems/jump-game-iv/
给你一个整数数组 arr ,你一开始在数组的第一个元素处(下标为 0)。
每一步,你可以从下标 i 跳到下标:
- i + 1 满足:i + 1 < arr.length
- i - 1 满足:i - 1 >= 0
- j 满足:arr[i] == arr[j] 且 i != j
请你返回到达数组最后一个元素的下标处所需的 最少操作次数 。
注意:任何时候你都不能跳到数组外面。
示例 1:
输入:arr = [100,-23,-23,404,100,23,23,23,3,404] 输出:3 解释:那你需要跳跃 3 次,下标依次为 0 --> 4 --> 3 --> 9 。下标 9 为数组的最后一个元素的下标。
示例 2:
输入:arr = [11,22,7,7,7,7,7,7,7,22,13] 输出:3
提示:
1 <= arr.length <= 5 * 10^4
-10^8 <= arr[i] <= 10^8
又是跳跃游戏,某次周赛的跳跃游戏 III,是用动态规划解决,因此我们首先先考虑能不能用动态规划解决。根据数据范围来看,如果用动态规划做的,那只能 dp[ i ] 一维(二维会超时了),那么根据转移关系来看,也是可以找到的,但是问题来了,动态规划的顺序无法得知,因为有第三种跳跃方式,所以无法更新状态。
所以动态规划做不了
那么我们再从头看题目,它告诉了我们转移关系,是不是告诉我们,从一个点,可以到其他点的方式,然后问我们到达终点的最少操作次数(也就是距离),那么可以看成是问,最短路问题,也就是给的三种跳跃关系,是告诉了我们节点之间的连接关系(邻接表),然后问最短路,由于这个是 无权图(0 / 1) 的最短路问题,所以用 BFS解决。
那么知道了BFS,剩下的就是,建立邻接表由于相同数字的下标是可以到处走的,所以我们用 map
接着就是BFS模板即可。
但是直接BFS会发现超时,为什么呢?是因为,如果当所有数都是相同的,除了终点的数不同,那么根据BFS,我们每一次到了节点就要看这个点的所有相邻点,那么如果所有数都是相同的,对于每一个数,我们都要邻接表扫一次,所以时间复杂度就被卡成了 O(N ^ 2),那么就会超时。
所以重点在于,第三种跳跃方式,由于是最短路BFS,先入队的肯定是最短距离,因为 只要一个数的下标进行第三种跳跃方式了,那么下次相同这个数的下标就不要去遍历 邻接表了(因为前面已经入过队了),所以我们新开一个数组,用于记录某个点,是不是经历了第三种跳跃方式,如果经历了,那么下次再到这些点,我们就不用考虑第三种跳跃方式了。
所以这道题就是 BFS最短路 + 小处理(防止数据卡成 O(N ^ 2) )。
const int MAXN = 5e4 + 15;
class Solution {
public:
int vis[MAXN], same[MAXN], dist[MAXN];
map > g; // 相同数字的下标放在一块儿
queue q;
int minJumps(vector& arr) {
g.clear();
while(!q.empty()) q.pop();
int n = arr.size();
for(int i = 0;i < n;++i) g[arr[i]].push_back(i);
// BFS最短路模板
memset(vis, 0, sizeof(vis));
memset(same, 0, sizeof(same));
memset(dist, -1, sizeof(dist));
q.push(0);
vis[0] = 1;
dist[0] = 0;
while(!q.empty())
{
int cur = q.front();
q.pop();
if(cur == n - 1)
{
return dist[n - 1];
}
if(cur + 1 < n && vis[cur + 1]==0)
{
q.push(cur + 1);
dist[cur + 1] = dist[cur] + 1;
vis[cur + 1] = 1;
}
if(cur - 1 >= 0 && vis[cur - 1]==0)
{
q.push(cur - 1);
dist[cur - 1] = dist[cur] + 1;
vis[cur - 1] = 1;
}
// 重点,第三种跳跃方式,如果前面这个数相同的已经经历了,那我们就不用考虑了
if(same[cur] == 0)
{
for(int i = 0;i < g[arr[cur]].size();++i) // 找相同数的所有下标
{
int next = g[arr[cur]][i];
if(vis[next] == 1) continue;
q.push(next);
dist[next] = dist[cur] + 1;
vis[next] = 1;
same[next] = 1; // 这些相同数的下标都已经,经历了第三种跳跃方式了
}
}
}
return -1;
}
};