二进制尾零子数组问题 | 豆包MarsCode AI 刷题
问题描述
小U拿到了一个由 个正整数组成的数组,她需要从中选择一个连续子数组,使得该子数组中所有元素的乘积在二进制表示中末尾至少有 个连续的 。也就是说,子数组的乘积必须是 的倍数。你的任务是找到满足条件的最短连续子数组的长度。如果不存在这样的子数组,输出 。
例如,给定数组 [1, 2, 3, 4, 5, 6] 和 ,你需要找到一个连续子数组,它的乘积在二进制中尾部至少有3个 。取子数组 [2, 3, 4] 时,乘积 ,其二进制表示为 11000,尾部有3个 '0'。
思路
最简单的思路,直接暴力求解,长度从1到n遍历连续子数组,然后检测每个子数组的成绩是否是的倍数,如果是直接返回。如果找不到返回-1
def solution(n: int, k: int, a: list) -> int:
target = 2 ** k
for length in range(1, n + 1):
for start in range(n - length + 1):
product = 1
for i in range(start, start + length):
product *= a[i]
if product % target == 0:
return length
return -1
if __name__ == '__main__':
#test cases...
再分析一下,如果一个数字能被 整除,则其尾部有至少 个连续的二进制 。若子数组的乘积满足此条件,意味着该子数组中 的因子总数至少为 。
那对于数组中的每个数,我们可以先计算出该数能分解出多少个 。这样,我们可以问题转化成“求 的因子数的和等于 的最小连续子数组的长度”
比如,对于样例
输入:
n = 6,k = 3,a = [1, 2, 3, 4, 5, 6]
输出:3
先求每个数的 的因子个数,可以得到数组b = [0, 1, 0, 2, 0, 1]。然后可以很容易看出子数组[1, 0, 2]或者[2, 0, 1]满足我们的要求。
对于因子数的和的计算,我们可以使用滑动窗口或者双指针来找。
代码:
def solution(n: int, k: int, a: list) -> int:
def count_twos(x):
count = 0
while x % 2 == 0:
count += 1
x //= 2
return count
# 计算每个元素的因子数量
twos = [count_twos(x) for x in a]
# 滑动窗口寻找最短满足条件的子数组
left = 0
current_sum = 0
min_length = float('inf')
for right in range(n):
current_sum += twos[right]
# 如果当前窗口内因子和大于等于 k,尝试收缩窗口
while current_sum >= k:
min_length = min(min_length, right - left + 1)
current_sum -= twos[left]
left += 1
# 如果未找到满足条件的子数组,返回 -1
return min_length if min_length != float('inf') else -1
if __name__ == '__main__':
#test cases...
更短的代码,加上了位运算优化:
def solution(n: int, k: int, a: list) -> int:
s = l = 0
r = float('inf')
for i, x in enumerate(a):
s += (x & -x).bit_length() - 1
while s >= k: r, s, l = min(r, i - l + 1), s - ((a[l] & -a[l]).bit_length() - 1), l + 1
return r if r < float('inf') else -1
if __name__ == '__main__':
#test cases...
复杂度分析
-
时间复杂度:
- 预处理因子数量:,其中 是数组 中最大值。
- 滑动窗口:。
- 总时间复杂度:。
-
空间复杂度:
- 额外的数组 ,空间复杂度为 。