Weekly Contest 407 周赛题目解析
Posted on Thu 08 August 2024 in Leetcode
题目列表
- 3226. Number of Bit Changes to Make Two Integers Equal 使两个整数相等的位更改次数
- 3227. Vowels Game in a String 字符串元音游戏
- 3228. Maximum Number of Operations to Move Ones to the End 将 1 移动到末尾的最大操作次数
- 3229. Minimum Operations to Make Array Equal to Target 使数组等于目标数组所需的最少操作次数
3226. Number of Bit Changes to Make Two Integers Equal 使两个整数相等的位更改次数
给定两个整数n
和k
,你可以把n
的任意一位从1
变到0
,问将n
变为k
的最小变换次数是多少。如果无法变换,则返回-1
。
比较基础的位运算题目,先看区间为10^6
,直接用位移判断是否相等或者可以变换。
class Solution:
def minChanges(self, n: int, k: int) -> int:
def bitof(v, i):
return ((v >> i) & 1)
result = 0
for i in range(32):
nb = bitof(n, i)
kb = bitof(k, i)
if nb == kb:
continue
elif nb == 1 and kb == 0:
result += 1
else:
return -1
return result
3227. Vowels Game in a String 字符串元音游戏
爱丽丝和鲍勃正在玩一个游戏,给定一个小写字母字符串s
,爱丽丝每回合可以从s
中移除奇数个原音,鲍勃则可以移除偶数个元音。两人轮流进行回合,直到元音字符皆被移除,此时最后一个移除的人获得胜利。若两人都以最优的方式进行游戏,要求返回一个布尔值表明爱丽丝是否会赢。爱丽丝永远先进行游戏。
这里我们可以看出,只要s
中至少有一个元音,爱丽丝永远可以进行移除最大的奇数个数,此时剩余元音个数要么为0
要么为1
,鲍勃无法进行移除,因此算输。直接数元音个数,然后进行简单判断即可。
class Solution:
def doesAliceWin(self, s: str) -> bool:
VOWELS = list('aeiou')
vowel_count = sum(int(c in VOWELS) for c in s)
print(vowel_count)
if vowel_count == 0:
return False
return True
3228. Maximum Number of Operations to Move Ones to the End 将 1 移动到末尾的最大操作次数
给定二进制字符串s
,你可以将s[i] = 1
与右边的s[i + 1] = 0
进行交换。你可以进行这个操作任意多次,问至多可以进行多少次操作。
这题的关键点要求最大次数。先来思考一下,如果我们要移动最少次数,那么我们肯定会选择优先移动靠右的元素,这样子一次操作的话,左边的元素会跳过更多的0
直接来到右边。因此,反向思考的话,我们可以得出,一点一点的从左边开始将元素往右边移动,是求最大次数的关键。
当然,我们肯定不能一个一个的去看,这样时间复杂度较慢。我们倾向于一次处理一排1
。这一题的设计刚好如此。假设s[0:i]
之间有k
个元素,且位置i
之前所有的1
都已经移动到了最佳位置,当s[i + 1] == 0
时,我们最大可以移动k
次。基于动态规划的思想,我们可以令dp[i]
为索引 s[0:i]
包含的1
移动到最佳位置的最大次数,则我们可以基于前面的结果,以及当前1
的数量,来快速计算最大需要多少次的操作。
class Solution:
def maxOperations(self, s: str) -> int:
ipos = [idx for idx in range(len(s)) if s[idx] == '1']
if len(ipos) == 0:
return 0
if ipos[-1] != len(s) - 1:
ipos.append(len(s))
memo = [0] * len(ipos)
for i in range(1, len(ipos)):
if ipos[i] - 1 == ipos[i - 1]:
memo[i] = memo[i - 1]
else:
memo[i] = memo[i - 1] + i
# print(i, memo[i])
return memo[-1]
3229. Minimum Operations to Make Array Equal to Target 使数组等于目标数组所需的最少操作次数
给两个定数组nums
和target
,在单次操作中,你可以将任意nums
中的连续子数组的所有数字增加或者减少1
。你可以操作任意多次,使得nums
恰好为target
,问最少需要需要多少次。
当然要从两个数组之间的区别开始。令数组diff
为target
和nums
中元素两两相减之差,我们就能找到可以连续操作的最长的子数组,并且批量对他们进行操作。但这样真的解决了问题吗?当你遇到diff = [-1, 2, -2]
的时候应该怎么抉择呢?程序可不会做这样的抉择。
实际上,这道题的性质与总变差(total variation)有关。先来看一个简单的例子。我们有nums = [1, 3, 3, 2, 3, 3]
和target = [4, 4, 4, 4, 4, 4]
,则两者的差值为diff = [3, 1, 1, 2, 1, 1]
。为了方便进行计算,我们在两端加上0
,则有diff_new = [0, 3, 1, 1, 2, 1, 1, 0]
。将diff_new
中的前后元素进行差分,我们有var = [3, -2, 0, 1, -1, 0, -1]
。其中的正值相加,则是这道题的答案4
。
当我们计算出一个序列的差分数组后,再进一步计算相邻元素之间的差异,其中正差值代表了每次转换时所需的增加量。将这些正差值相加,实际上就是计算了从原始数组转换为目标数组时,最少需要多少次操作。这个和反映了在转换过程中所需要的最小“工作量”。
class Solution:
def minimumOperations(self, nums: List[int], target: List[int]) -> int:
n = len(nums)
incr, decr, ops = 0, 0, 0
for i in range(n):
diff = target[i] - nums[i]
if diff > 0:
if incr < diff:
ops += diff - incr
# print(f"{i}: diff is {diff}, incr is {incr}, additional {diff - incr} ops needed.")
incr = diff
decr = 0
elif diff < 0:
if diff < decr:
ops += decr - diff
# print(f"{i}: diff is {diff}, decr is {decr}, additional {diff - decr} ops needed.")
decr = diff
incr = 0
else:
incr = decr = 0
return ops
另一种方式是求全变差的绝对值总和之后除以2
。
class Solution:
def minimumOperations(self, nums: List[int], target: List[int]) -> int:
diffs = [0] + list(map(sub, target, nums)) + [0]
return sum(abs(x-y) for x, y in pairwise(diffs))//2