突然想之后每次参加的小竞赛都写一份题解,这样可能以后查看啥的也方便,并进一步优化比赛时代码。那么话不多说我们开始!
这题如果能够记录奇数和偶数对应的位置情况,再对奇数和偶数分别进行降序排列并填入相应的位置,可以得到最大的整数(由于最大数位的位置只可能变大或不变,而不可能变小,因此结果一定合法)。
具体而言 先记住奇偶数分别的位置,即 位置为奇数, 位置为偶数;再对对应数进行排序,奇数排序结果为 ,偶数排序结果为 。因此很容易依次在对应位置填数可以得到结果为 。
具体处理,Python 转为字符串中每一项的判断将比较容易,并且位置的记录也不容易出错。
当然,此题如果要变为交换后的最小整数,则需要考虑结果的合法性,即不能以 作为一整数的开头,需在排序后进行一定的判断,可能能小小提高难度。
class Solution:
def largestInteger(self, num: int) -> int:
evens = [i for i in str(num) if int(i) % 2 == 0]
odds = [i for i in str(num) if int(i) % 2 == 1]
# 记录不同奇偶性数码分别有哪些数
evens.sort(reverse=True)
odds.sort(reverse=True)
pos = {x for x, val in enumerate(str(num)) if int(val) % 2}
# 查找对应位置是奇数的位置
result = ''
pos_e = 0
pos_o = 0
# 分别记录此时两数码分别取到了何位置的数
for i in range(len(str(num))):
if i in pos:
result += odds[pos_o]
pos_o += 1
else:
result += evens[pos_e]
pos_e += 1
return int(result)这题由于 Python 有可以直接计算表达式值的 eval 函数,大大简化了程序计算的难度(但是还是傻傻的以为要输出最小结果而非最小表达式被罚时一次,实在是太亏了),整体而言只有括号在最开头和最结尾的时候会不需要在两侧添加 符号,因此程序主要注意的也是这一点,最后也需要将添加的 符号删去;另外加括号处不能为前一数的末尾以及后一数的开头,也需要在循环时加以判断。
整体思路在于生成所有可能表达式,并计算值,若比目前最小情况更小,则更新对应最小情况。这题用 Python 还是非常轻松愉快的。至于复杂度,完全取决于前后两数长度的乘积,以及 eval 表达式的计算。
class Solution:
def minimizeResult(self, expression: str) -> str:
num1, num2 = expression.split('+')
result = eval(expression)
ans = f'({expression})'
len1, len2 = len(num1), len(num2)
for i in range(len1):
for j in range(1, len2+1):
expr = num1[:i] + '*' *(num1[:i]!='') + '(' + num1[i:] + '+' + num2[:j] + ')' + '*'*(num2[j:] != '') + num2[j:]
if eval(expr) < result:
result = eval(expr)
ans = expr.replace('*','')
return ans仿佛刚刚进入一个比较有意思的题,关键在于每次操作的数为最小的数。(这一点的说明好像比较复杂,可以理解为和一定的若干个数越接近乘积越大)因此不少解答利用了堆进行每一次相加的模拟,但这样做最终复杂度会与 的大小有关,因而存在优化空间。
我们这里采取二分的方式解决对应的问题,即我们考虑,可以通过 次操作把前面多少个数变成相同的数。为了达到这样的目的,我们先对数字进行排序;对每个点如果能通过 次操作把前面 个数变为相同,则需要满足条件,最大值的 倍小于前 个数的和加上 。得到这样的二分条件后,我们可只对前面这 个数进行操作,使得其极差不超过 (可通过部分数取均值,其他取均值加一得到);剩余数直接乘积。注意:每次乘积都要取模,避免数值过大计算复杂度过大。
class Solution:
def maximumProduct(self, nums: List[int], k: int) -> int:
nums.sort()
acc_sum = [0]
acc = 0
for num in nums:
acc += num
acc_sum.append(acc)
l, r = 0, len(nums)-1
while l <= r:
m = (l+r) // 2
# 这里是前面提到的判断
if -acc_sum[m+1] + nums[m]*(m+1) <= k: l = m+1
else: r=m-1
ans = 1
# 后面的数可以直接不操作
for i in range(l, len(nums)):
ans *= nums[i]
ans %= 10**9+7
acc = acc_sum[r+1]+k
# 通过剩余数的和进行类似平均分配,bigger_num表示均值+1的数的个数
bigger_num = acc % (r+1)
tmp = acc //(r+1)
for i in range(bigger_num):
ans *= tmp+1
ans %= 10**9+7
for i in range(r+1-num1):
ans *= tmp
ans %= 10**9+7
return ans这题跟前一题极其相关!因为都要控制一部分量满足最小值尽可能大的特性(这也是我认为这次周赛出题可能不够好的地方)。我们先顺着一个容易犯错的方法进行考虑(这样这题才显得更困难)。
我们考虑先将花园的数组排序(这么做的理由类似于上一题,可以直接截取到一点看前面的未 full 的花最多可以每一堆最少到多少朵),因此只要遍历对应的 full 的堆数,确定其前的最大的最小值是多少即可。
接下来是需要修正的一些细节:
最小值不能超过 full 的数量,因此要取其和 full-1 的最小值;
最重要的一点——最小值一定能取到 full-1 吗?
不一定!因为前面可能已经取到了full!!
所以开始不需要进行花朵添加的情况下,可以尽量往前作为遍历的起点;
这里可以不用二分查找进行处理,因为随着 full 组数量增加,前面最大的最小值保持单调变换,可以直接往前走进行对比,后续整体时间复杂度可以减小到 (但这里暂未做对应优化)。虽然整体的复杂度由于开始的排序仍然为 。
class Solution:
def maximumBeauty(self, flowers: List[int], newFlowers: int, target: int, full: int, partial: int) -> int:
flowers.sort()
# 分别计算从任一点往前的总花数和往后要全部填满的总花数
acc_sum = []
acc = 0
for flower in flowers:
acc += flower
acc_sum.append(acc)
lack = []
acc = 0
for flower in flowers[::-1]:
acc+=max(0,target-flower)
lack.append(acc)
lack = lack[::-1]
lack.append(0)
# 进行得分的计算
ans = 0
for i in range(len(flowers), -1, -1):
# 如果新的一项不需要任何花数,则持续向前,解决了前面的重点问题
if i and lack[i] == lack[i-1]: continue
# 对剩余的花进行分配,若没有剩余花,则结束讨论!
res = newFlowers - lack[i]
if res < 0: break
l, r = 0, i-1
while l <= r:
m = (l+r)//2
if flowers[m] *(m+1)-acc_sum[m] > res: r = m-1
else:l=m+1
partial_flower = min((acc_sum[r] + res)//max(1,r+1), target-1)
# i!=0 的作用?不存在未满的情况下
ans = max(ans, full*(len(flowers)-i)+partial_flower*partial * (i!=0))
return ans