第268场周赛
上午打完疫苗回来,已经十一点多了,十点半开始的周赛过了一半。不过还是写一写吧,毕竟上周也是推到这周,这周不能再拖延了,总得起个头。一共写出来2道题,因为晚入场了四十分钟左右,排名太拉跨了,才3050名。第一次参加,也不知道自己这个垃圾多少名才是合理的。
可以看出来,我第二题和第一题之间只隔了8分钟,还是很不错的。不过第三题对C语言用户太不友好了,第四题时间复杂度也很不友好,哼,气人。
下面看题吧。
第一题
街上有 n 栋房子整齐地排成一列,每栋房子都粉刷上了漂亮的颜色。给你一个下标从 0 开始且长度为 n 的整数数组 colors ,其中
colors[i]
表示第 i 栋房子的颜色。返回两栋颜色不同房子之间的最大距离。
第 i 栋房子和第 j 栋房子之间的距离是
abs(i - j)
,其中abs(x)
是 x 的绝对值。
分析
分析一下,这两栋房子一定有一栋在边上。如果不是在边上,比如是 xxxxx5xxxxx4xxxxx
,是不是意味着 4 后面的值都是 5 ,不然就会和 5 匹配了; 5 前面的数字一定是 4 ,不然也会和 4 匹配,那么就是 444445xxxxx455555
,就和假设矛盾了。所以一定是至少有一个在边上。
思路一
没有想到一定有个在边上的,用的递归。判断左右两边是否相等,不等的话,就返回结果。相等的话,最终结果一定出在 s + 1 到 t 内或者 s 到 t - 1 内。超时了,因为很多没必要的计算。
int my_max(int *nums, int s, int t)
{
if (s == t) return 0;
if (nums[s] != nums[t])
return t - s;
else
return fmax(my_max(nums, s + 1, t), my_max(nums, s, t - 1));
}
int maxDistance(int* colors, int colorsSize){
return my_max(colors, 0, colorsSize - 1);
}
思路二
双指针,指向首尾。假设一定有第一个元素,那么就只用挪动尾指针,直到不等,求得一个最大距离;一定有最后一个元素,那么只用挪动首指针,直到不等,求得一个最大距离。最终结果就是两个中的最大值。
int my_max(int *nums, int left, int right)
{
int s = left, t = right;
int max1 = 0, max2 = 0;
while (s < t) {
if (nums[s] != nums[t]) {
max1 = t - s;
break;
}
while(s < t && nums[s] == nums[++s]);
}
s = left; t = right;
while (s < t) {
if (nums[s] != nums[t]) {
max2 = t - s;
break;
}
while(t > 0 && nums[t] == nums[--t]);
}
return fmax(max1, max2);
}
int maxDistance(int* colors, int colorsSize){
return my_max(colors, 0, colorsSize - 1);
}
小结
现在回想起来当时还是太紧张了,因为超时就全盘否定了第一次的做法,其实并没有必要,小改一下就行,保证一定有个边界就好了。
int my_max(int *nums, int s, int t, int length)
{
if (s == t) return 0;
if (nums[s] != nums[t])
return t - s;
else {
int max1 = 0, max2 = 0;
if (t == length)
max1 = my_max(nums, s + 1, t, length);
if (s == 0)
max2 = my_max(nums, s, t - 1, length);
return fmax(max1, max2);
}
}
int maxDistance(int* colors, int colorsSize){
return my_max(colors, 0, colorsSize - 1, colorsSize - 1);
}
第二题
你打算用一个水罐给花园里的 n 株植物浇水。植物排成一行,从左到右进行标记,编号从 0 到 n - 1 。其中,第 i 株植物的位置是 x = i 。x = -1 处有一条河,你可以在那里重新灌满你的水罐。
每一株植物都需要浇特定量的水。你将会按下面描述的方式完成浇水:
按从左到右的顺序给植物浇水。
在给当前植物浇完水之后,如果你没有足够的水完全浇灌下一株植物,那么你就需要返回河边重新装满水罐。
你不能提前重新灌满水罐。
最初,你在河边(也就是,x = -1),在 x 轴上每移动一个单位都需要 一步 。给你一个下标从 0 开始的整数数组 plants ,数组由 n 个整数组成。其中,plants[i] 为第 i 株植物需要的水量。另有一个整数 capacity 表示水罐的容量,返回浇灌所有植物需要的步数。
分析
模拟。模拟整个过程,用 sum 表示走过的步数,water 表示当前剩余的水量,循环数组模拟浇水。
int wateringPlants(int* plants, int plantsSize, int capacity){
int sum = 0;
int water = capacity;
for (int i = 0; i < plantsSize; i++) {
// 水够就走一步,然后浇水
if (water >= plants[i]) {
sum += 1;
water -= plants[i];
}
// 不够就回去装水然后再来
else {
sum += 2 * i;
water = capacity;
// for循环默认都会走一步,但是这个水没浇,怎么能走呢
// 既然默认走一步,那我就先退一步,用while会好一些
i--;
}
}
return sum;
}
第三题
请你设计一个数据结构,它能求出给定子数组内一个给定值的频率 。
子数组中一个值的频率指的是这个子数组中这个值的出现次数。
请你实现
RangeFreqQuery
类:
RangeFreqQuery(int[] arr)
用下标从 0 开始的整数数组arr
构造一个类的实例。int query(int left, int right, int value)
返回子数组arr[left...right]
中value
的 频率 。一个子数 指的是数组中一段连续的元素。
arr[left...right]
指的是nums
中包含下标left
和right
在内的中间一段连续元素。提示:
- 1 <=
arr.length
<= 105- 1 <=
arr[i]
,value
<= 104- 0 <=
left
<=right
<arr.length
- 调用
query
不超过 10^5 次。
输入:
["RangeFreqQuery", "query", "query"]
[[[12, 33, 4, 56, 22, 2, 34, 33, 22, 12, 34, 56]], [1, 2, 4], [0, 11, 33]]
输出:
[null, 1, 2]
解释:
RangeFreqQuery rangeFreqQuery = new RangeFreqQuery([12, 33, 4, 56, 22, 2, 34, 33, 22, 12, 34, 56]);
rangeFreqQuery.query(1, 2, 4); // 返回 1 。4 在子数组 [33, 4] 中出现 1 次。
rangeFreqQuery.query(0, 11, 33); // 返回 2 。33 在整个子数组中出现 2 次。
分析
沉下心来,好好看懂题目。示例的第一行没什么用,告诉我们第一个是数组,后面都是查询(格式是 left , right , value)。意思就是题目给我们一个数组,我们可以自定义一个数据结构来存储,以便最有利于后面的查找。然后查找 left 和 right 之间的 value 的个数,会查找很多次。
思路
简简单单用个结构体数组,把下标和 value 存入结构体。然后对 value 排序,手写归并,这样是稳定的排序,那么下标也是有序的。然后每次查询的时候,二分找到第一个等于 value 的值和最后一个等于 value 的值,在这两个值之间二分查找第一个下标大于等于 left 的值和最后一个下标小于等于 right 的值。相减就是最终结果。
别人十行能解决,我要100行,属实是很废物了。
typedef struct {