今天的题目大概是竞赛史上最简单的了吧,也可能是出题水平最保守的了吧。
像我这种平时三题选手,竟然也能顺利完赛,还是一小时以内。刷新了个人最好成绩,当然,高手们也是刷新了最好成绩。
有顺着做的,也有倒着做的,有57秒完成第一题的,也有90秒完成第四题的。说实话这么短的时间,题目我都还没看明白呢。
一、数组能形成多少数对
给你一个下标从 0 开始的整数数组 nums 。在一步操作中,你可以执行以下步骤:
- 从
nums选出 两个 相等的 整数 - 从
nums中移除这两个整数,形成一个 数对
请你在 nums 上多次执行此操作直到无法继续执行。
返回一个下标从 0 开始、长度为 2 的整数数组 answer 作为答案,其中 **answer[0] **是形成的数对数目,answer[1] 是对 nums 尽可能执行上述操作后剩下的整数数目。
示例 1:
输入: nums = [1,3,2,1,3,2,2]
输出: [3,1]
解释:
nums[0] 和 nums[3] 形成一个数对,并从 nums 中移除,nums = [3,2,3,2,2] 。
nums[0] 和 nums[2] 形成一个数对,并从 nums 中移除,nums = [2,2,2] 。
nums[0] 和 nums[1] 形成一个数对,并从 nums 中移除,nums = [2] 。
无法形成更多数对。总共形成 3 个数对,nums 中剩下 1 个数字。
示例 2:
输入: nums = [1,1]
输出: [1,0]
解释: nums[0] 和 nums[1] 形成一个数对,并从 nums 中移除,nums = [] 。
无法形成更多数对。总共形成 1 个数对,nums 中剩下 0 个数字。
示例 3:
输入: nums = [0]
输出: [0,1]
解释: 无法形成数对,nums 中剩下 1 个数字。
提示:
1 <= nums.length <= 1000 <= nums[i] <= 100
解析
如果想形成数对,则至少nums得有两个以上数字,因此只有一个数字的时候必然是无法形成数对的,应当返回[0, 1]
其余的情况下,需要数一数有多少数字相同,也就是可以记录一下每个数字出现了多少次,如果一个数字出现了偶数次,则必然会被消除掉,如果出现了奇数次,则会剩余一个,因此,可以知道最后剩下的是什么数字。因为题目问的是一共需要多少步操作,每当清除一对,就是一次操作,出现偶数次的时候直接除以2即可,出现奇数次,向下取整即可。
代码
from collections import defaultdict
class Solution:
def numberOfPairs(self, nums: List[int]) -> List[int]:
if len(nums) == 1:
return [0, 1]
count_dict = defaultdict(int)
for n in nums:
count_dict[n] += 1
steps = 0
left = 0
for n, c in count_dict.items():
if c % 2 == 0:
steps += c / 2
else:
steps += c//2
left += 1
print([int(steps), left])
return [int(steps), left]
二、数位和相等数对的最大和
给你一个下标从 0 开始的数组 nums ,数组中的元素都是 正 整数。请你选出两个下标 i 和 j(i != j),且 nums[i] 的数位和 与 nums[j] 的数位和相等。
请你找出所有满足条件的下标 i 和 j ,找出并返回 **nums[i] + nums[j] **可以得到的 最大值 。
示例 1:
输入: nums = [18,43,36,13,7]
输出: 54
解释: 满足条件的数对 (i, j) 为:
- (0, 2) ,两个数字的数位和都是 9 ,相加得到 18 + 36 = 54 。
- (1, 4) ,两个数字的数位和都是 7 ,相加得到 43 + 7 = 50 。
所以可以获得的最大和是 54 。
示例 2:
输入: nums = [10,12,19,14]
输出: -1
解释: 不存在满足条件的数对,返回 -1 。
提示:
1 <= nums.length <= 10^51 <= nums[i] <= 10^9
解析
这道题就按照题目要求模拟整个流程即可,首先先将每个数字进行位数和计算,在寻找到位数和相等的,按照从大到小的顺序排列,取出前两个最大的求和,再看所有能求和的结果里,哪个最大即可,没有什么实质性难度。
代码
from collections import defaultdict
class Solution:
def calcSum(self, num):
s = str(num)
r = 0
for c in s:
r += int(c)
return r
def maximumSum(self, nums: List[int]) -> int:
d = defaultdict(list)
for num in nums:
s = self.calcSum(num)
d[s].append(num)
m = -1
for _, l in d.items():
if len(l) < 2:
continue
x = sorted(l)[::-1]
m = max(m, x[0] + x[1])
return m
小结
前两道题,熟练使用defaultdict就可以顺利做出来,感觉就是在考察基础知识。
三、裁剪数字后查询第 K 小的数字
给你一个下标从 0 开始的字符串数组 nums ,其中每个字符串 长度相等 且只包含数字。
再给你一个下标从 0 开始的二维整数数组 queries ,其中 queries[i] = [ki, trimi] 。对于每个 queries[i] ,你需要:
- 将
nums中每个数字 裁剪 到剩下 最右边trimi个数位。 - 在裁剪过后的数字中,找到
nums中第ki小数字对应的 下标 。如果两个裁剪后数字一样大,那么下标 更小 的数字视为更小的数字。 - 将
nums中每个数字恢复到原本字符串。
请你返回一个长度与 queries 相等的数组 **answer,其中 **answer[i]是第 **i **次查询的结果。
提示:
- 裁剪到剩下
x个数位的意思是不断删除最左边的数位,直到剩下x个数位。 nums中的字符串可能会有前导 0 。
示例 1:
输入: nums = ["102","473","251","814"], queries = [[1,1],[2,3],[4,2],[1,2]]
输出: [2,2,1,0]
解释:
1. 裁剪到只剩 1 个数位后,nums = ["2","3","1","4"] 。最小的数字是 1 ,下标为 2 。
2. 裁剪到剩 3 个数位后,nums 没有变化。第 2 小的数字是 251 ,下标为 2 。
3. 裁剪到剩 2 个数位后,nums = ["02","73","51","14"] 。第 4 小的数字是 73 ,下标为 1 。
4. 裁剪到剩 2 个数位后,最小数字是 2 ,下标为 0 。
注意,裁剪后数字 "02" 值为 2 。
示例 2:
输入: nums = ["24","37","96","04"], queries = [[2,1],[2,2]]
输出: [3,0]
解释:
1. 裁剪到剩 1 个数位,nums = ["4","7","6","4"] 。第 2 小的数字是 4 ,下标为 3 。
有两个 4 ,下标为 0 的 4 视为小于下标为 3 的 4 。
2. 裁剪到剩 2 个数位,nums 不变。第二小的数字是 24 ,下标为 0 。
提示:
1 <= nums.length <= 1001 <= nums[i].length <= 100nums[i]只包含数字。- 所有
nums[i].length的长度 相同 。 1 <= queries.length <= 100queries[i].length == 21 <= ki <= nums.length1 <= trimi <= nums[0].length
解析
第三题看着很长,很唬人,其实也是一个简单到中等难度的题目。这里面有一个难点,就是当出现数字相同的时候,下标小的认为小,下标大的认为是大,因为有相同的数字,就无法使用 list.index()函数了。需要自己手动去数一数,到底是相同数字的第几个。下述代码使用c来记录要找的这个数字如果出现相同数字,它是第几个,排序后,看看左边一共还有几个跟它相同的数字。
代码
class Solution:
def smallestTrimmedNumbers(self, nums: List[str], queries: List[List[int]]) -> List[int]:
res = []
for k, trim in queries:
tmp = []
for num in nums:
tmp.append(int(num[-1 * trim:]))
x = sorted(tmp)
v = x[k-1]
left = True
c = 1
while left:
l = k -(c+1)
if l >= 0 and x[l] == v:
c += 1
else:
left = False
for index, n in enumerate(tmp):
if n == v:
c -= 1
if c == 0:
res.append(index)
return res
四、使数组可以被整除的最少删除次数
给你两个正整数数组 nums 和 numsDivide 。你可以从 nums 中删除任意数目的元素。
请你返回使 nums 中 最小 元素可以整除 numsDivide 中所有元素的 最少 删除次数。如果无法得到这样的元素,返回 -1 。
如果 y % x == 0 ,那么我们说整数 x 整除 y 。
示例 1:
输入: nums = [2,3,2,4,3], numsDivide = [9,6,9,3,15]
输出: 2
解释:
[2,3,2,4,3] 中最小元素是 2 ,它无法整除 numsDivide 中所有元素。
我们从 nums 中删除 2 个大小为 2 的元素,得到 nums = [3,4,3] 。
[3,4,3] 中最小元素为 3 ,它可以整除 numsDivide 中所有元素。
可以证明 2 是最少删除次数。
示例 2:
输入: nums = [4,3,6], numsDivide = [8,2,6,10]
输出: -1
解释:
我们想 nums 中的最小元素可以整除 numsDivide 中的所有元素。
没有任何办法可以达到这一目的。
提示:
1 <= nums.length, numsDivide.length <= 10^51 <= nums[i], numsDivide[i] <= 10^9
解析
这是一道小学数学题,之所以说是小学而不是初中,是因为小学三四年级的时候就已经学习了最大公约数了。最大公约数,好像近期有个什么事情跟他有关,感兴趣的可以去搜搜看B站的事故报告。
题目要求的是希望能让nums的最小的数可以整除所有numsDivide,那就势必要知道,numsDivide的最大公约数是多少,nums的最小数必须要能够整除这个最大公约数,不然就没法达到效果。在这个过程中,还有一个更显而易见的反例就是,如果numsDivide的最小值比nums的最小值要大,那肯定无法完成了,直接返回-1即可。
问题的难度就来到了如何计算numsDivide的最大公约数,使用python自带的math库可以计算出两个数字的最大公约数,现在需要计算出一个数组的最大公约数,其实就是两两计算即可。在周赛的时候没有来得及优化,其实是可以更快速的算出来的,首先将numsDivide进行排序,从小到大排序之后,依次计算相邻的最大公约数,如果出现了最大公约数是1,则可以停止计算了,因为已经互质。
虽然周赛时候没有这个优化,但是也不影响最终的结果。
代码
import math
class Solution:
def gcd_list(self, s):
g = 0
for i in range(len(s)):
if i == 0:
g = s[i]
else:
g = math.gcd(g, s[i])
return g
def minOperations(self, nums: List[int], numsDivide: List[int]) -> int:
minDivide = min(numsDivide)
if min(nums) > minDivide:
return -1
g = self.gcd_list(numsDivide)
nums = sorted(nums)
res = -1
for index, num in enumerate(nums):
if g % num == 0:
res= index
break
return res
总结
总的来说,今天周赛的这四道题目都是处理一维数组,按照题目要求进行模拟运算就可以的,四道题目没有明显的难度区分,甚至一道动态规划都没有,供大家刷分是用的。
我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。