题目描述与示例
题目描述
小欧拿到了一个数组,她准备选择一个连续子数组,满足该连续子数组的所有元素乘积的2
进制末尾至少有k
个0
。小红想知道,这个连续子数组的最短长度是多少?
输入描述
第一行输入两个正整数n
和k
。第二行输入n
个正整数ai
。
输出描述
一个整数,代表连续子数组的最短长度。如果不存在这样的子数组,输出-1
。
示例一
输入
6 3
1 2 3 4 5 6
输出
3
说明
取[2,3,4]
即可,2*3*4=24
,其二进制为11000
。
示例二
输入
6 4
2 2 2 1 4 8
输出
2
示例三
输入
5 1
1 1 1 1 1
输出
-1
解题思路
2进制末尾的0代表什么
实际上,如果一个数的二进制末尾存在k
个0
,说明这个数是2
的k
次方即2^k
的倍数。
比如数字24
,其二进制为11000
,末尾存在3
个0
,说明24
是2^3 = 8
的倍数。
故题目可以简化为,找到最短的连续子数组,这个子数组的乘积是2
的k
次方即2^k
的倍数。
子数组乘积与2的k次方的关系
由于对连续子数组的操作是相乘,只有包含质因数2
的元素,才会对整个子数组的相乘结果造成影响。因此我们可以对数组中的每一个元素进行质因数分解,只考虑每个元素的质因数中2
的个数,得到相应的数组nums_contain_2
。
以原数组 nums = [1, 2, 3, 4, 5, 6]
为例,我们可以得到每个元素的质因数中2
的个数的数组nums_contain_2 = [0, 1, 0, 2, 0, 1]
。
故问题可以进一步简化为,找到nums_contain_2
的最短的连续子数组,该数组的和至少为k
。这就是一个非常典型的滑动窗口问题了。
因此直接考虑滑动窗口三问三答。
滑窗三问
Q1:对于每一个右指针right
所指的元素num
,做什么操作?
Q2:什么时候要令左指针left
右移?left
对应的元素做什么操作?while
中的循环不变量是什么?
Q3:什么时候进行ans
的更新?
滑窗三答
A1:将num
计入窗口中质因数2
的数量的变量windows_contain_2
中。
A2:windows_contain_2 >= k
,windows_contain_2
减去left_num
,left
右移,直到windows_contain_2 < k
成立。
A3:在left
最后一次右移之前,区间nums_contain_2[left:right+1]
是满足题意的连续子数组。故在left
右移之后,可以更新答案ans = min(ans, right-left+2)
代码
Python
# 题目:【不定滑窗】OPPO2023秋招提前批-小欧的区间取数
# 作者:闭着眼睛学数理化
# 算法:不定滑窗
# 代码有看不懂的地方请直接在群上提问
from math import inf
# 计算数字num包含多少个质因数2的函数
def get_num_contain_2(num):
# 初始化个数为2
ans = 0
# 如果num可以整除2,持续循环
# 整除2,同时ans递增1
while(num % 2 == 0):
num //= 2
ans += 1
return ans
n, k = map(int, input().split())
nums = list(map(int, input().split()))
# 获得nums数组中每一个元素num,包含质因数2的个数
nums_contain_2 = [get_num_contain_2(num) for num in nums]
# 初始化答案、滑窗中质因数2的个数、左指针
ans = inf
windows_contain_2 = 0
left = 0
for right, num in enumerate(nums_contain_2):
# A1
windows_contain_2 += num
# A2
if windows_contain_2 >= k:
while windows_contain_2 >= k:
windows_contain_2 -= nums_contain_2[left]
left += 1
# A3
ans = min(ans, right-left+2)
print(ans if ans != inf else -1)
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
int n = scanner.nextInt();
int k = scanner.nextInt();
int[] nums = new int[n];
for (int i = 0; i < n; i++) {
nums[i] = scanner.nextInt();
}
int[] numsContain2 = new int[n];
for (int i = 0; i < n; i++) {
numsContain2[i] = getNumContain2(nums[i]);
}
int ans = Integer.MAX_VALUE;
int windowsContain2 = 0;
int left = 0;
for (int right = 0; right < n; right++) {
windowsContain2 += numsContain2[right];
if (windowsContain2 >= k) {
while (windowsContain2 >= k) {
windowsContain2 -= numsContain2[left];
left++;
}
ans = Math.min(ans, right - left + 2);
}
}
System.out.println(ans != Integer.MAX_VALUE ? ans : -1);
}
private static int getNumContain2(int num) {
int ans = 0;
while (num % 2 == 0) {
num /= 2;
ans++;
}
return ans;
}
}
C++
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits>
using namespace std;
int getNumContain2(int num) {
int ans = 0;
while (num % 2 == 0) {
num /= 2;
ans++;
}
return ans;
}
int main() {
int n, k;
cin >> n >> k;
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
vector<int> numsContain2(n);
for (int i = 0; i < n; i++) {
numsContain2[i] = getNumContain2(nums[i]);
}
int ans = INT_MAX;
int windowsContain2 = 0;
int left = 0;
for (int right = 0; right < n; right++) {
windowsContain2 += numsContain2[right];
if (windowsContain2 >= k) {
while (windowsContain2 >= k) {
windowsContain2 -= numsContain2[left];
left++;
}
ans = min(ans, right - left + 2);
}
}
cout << (ans != INT_MAX ? ans : -1) << endl;
return 0;
}
时空复杂度
- 时间复杂度:
O(NlogM)
。对于数字M
,单次计算其包含质因数2个数的函数get_num_contain_2(M)
的时间复杂度为O(logM)
,故N
次计算的总时间复杂度为O(NlogM)
。滑窗过程的时间复杂度为O(N)
。 - 空间复杂度:
O(N)
。辅助数组nums_contain_2
所占空间。 -
华为OD算法/大厂面试高频题算法练习冲刺训练
华为OD算法/大厂面试高频题算法练习冲刺训练
-
华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!
-
课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化
-
每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!
-
60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁
-
可上全网独家的欧弟OJ系统练习华子OD、大厂真题
-
可查看链接 大厂真题汇总 & OD真题汇总(持续更新)
-
绿色聊天软件戳
od1336
了解更多