为了更好的阅读体检,可以查看我的算法学习网
本题在线评测链接:P1391
题目描述
K0序列是指,序列中元素的乘积转换为2进制后末尾至少有 k k k个0。
给定一个长度为 n n n的序列 a a a和 k k k值,求其中为K0序列的连续子序列的最小长度。
输入描述
输入第一行两个正整数 n , k n,k n,k。( 3 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ 1 0 5 3 \leq n \leq 10^5,1 \leq k \leq 10^5 3≤n≤105,1≤k≤105)
输入第二行 n n n个正整数,第 i i i个为 a i a_i ai。( 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1≤ai≤109)
输出描述
输出一个正整数 ,表示连续子序列的二进制不小于 k k k的长度。
样例
样例输入
7 3
1 2 3 4 5 6 7
样例输出
3
思路:双指针
一个数的二进制末尾的 0 的个数 j j j,等价于这个数是 2 j 2^j 2j的倍数。
所以可以将每个数转换为对应的 j j j。
所以问题转换为,找到一个长度最短的连续子数组,使得子数组的和大于等于 k k k。
那么这就是一个双指针问题了。
枚举右端点,不断移动向右移动左端点以减少子数组长度后,但是需要满足移动后的子数组的和依旧大于等于 k k k。
时间复杂度: O ( n log n ) O(n\log n) O(nlogn) 。因为 a i a_i ai和 n n n同阶,最坏情况下需要 log a i \log a_i logai次才能计算出一个数中 2 2 2的个数。
思考
1.拓展问题:十进制下的0个数,如何做?
2.终极版本:阿里巴巴 2023.4.3-研发岗-第三题-又一个数论题
代码
C++
#include <bits/stdc++.h>
using namespace std;
int main()
{
int n, k;
cin >> n >> k;
vector<int> a(n);
int ans = n;
for (int i = 0, l = 0, cnt = 0; i < n; ++i) {
int x; cin >> x;
// 计算 2 的个数
// 统计完毕后,ai 就是的 i 个数中 2 的数量了
while (x % 2 == 0) {
a[i] += 1;
x >>= 1;
}
// 右移右端点一位
cnt += a[i];
// 不断尝试右移左端点,但是需要满足右移左端点后的子数组和仍然满足 >= k
while (l <= i && cnt - a[l] >= k) {
cnt -= a[l];
l += 1;
}
// 更新当 cnt >= k 时的子数组的最短长度
if (cnt >= k) ans = min(ans, i - l + 1);
}
printf("%d\n", ans);
return 0;
}
python
# 读入 n 和 k
n, k = map(int, input().split())
# 初始化一个长度为 n 的数组 a,用于存储每个数中包含的 2 的个数
a = list(map(int, input().split()))
# 初始化最短子数组长度为 n
ans = n
# 初始化左端点 l 和当前子数组中包含的 2 的个数 cnt
l, cnt = 0, 0
# 遍历 n 个数
for i in range(n):
# 计算a[i] 中 2 的个数
c = 0
while a[i] % 2 == 0:
c += 1
a[i] //= 2
# 更新当前子数组中包含的 2 的个数 cnt
cnt += c
a[i] = c
# 右移左端点 l,直到当前子数组不满足包含 k 个以上的 2
while l <= i and cnt - a[l] >= k:
cnt -= a[l]
l += 1
# 如果当前子数组中包含 k 个以上的 2,更新最短子数组长度
if cnt >= k:
ans = min(ans, i - l + 1)
# 输出最短子数组长度
print(ans)
Java
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读入 n 和 k
int n = sc.nextInt();
int k = sc.nextInt();
// 初始化一个长度为 n 的数组 a,用于存储每个数中包含的 2 的个数
int[] a = new int[n];
// 初始化最短子数组长度为 n
int ans = n;
// 初始化左端点 l 和当前子数组中包含的 2 的个数 cnt
int cnt = 0;
int l = 0;
// 遍历 n 个数
for (int i = 0; i < n; ++i) {
// 读入当前数 x,计算其中 2 的个数
int x = sc.nextInt();
while (x % 2 == 0) {
a[i] += 1;
x /= 2;
}
// 更新当前子数组中包含的 2 的个数 cnt
cnt += a[i];
// 右移左端点 l,直到当前子数组不满足包含 k 个以上的 2
while (l <= i && cnt - a[l] >= k) {
cnt -= a[l];
l += 1;
}
// 如果当前子数组中包含 k 个以上的 2,更新最短子数组长度
if (cnt >= k) {
ans = Math.min(ans, i - l + 1);
}
}
// 输出最短子数组长度
System.out.println(ans);
}
}
Go
package main
import "fmt"
func main() {
var n, k int
fmt.Scan(&n, &k)
// 初始化一个长度为 n 的数组 a,用于存储每个数中包含的 2 的个数
a := make([]int, n)
// 初始化最短子数组长度为 n
ans := n
// 初始化左端点 l 和当前子数组中包含的 2 的个数 cnt
cnt, l := 0, 0
// 遍历 n 个数
for i := 0; i < n; i++ {
var x int
fmt.Scan(&x)
// 计算 2 的个数
for x%2 == 0 {
a[i] += 1
x /= 2
}
// 更新当前子数组中包含的 2 的个数 cnt
cnt += a[i]
// 右移左端点 l,直到当前子数组不满足包含 k 个以上的 2
for l <= i && cnt-a[l] >= k {
cnt -= a[l]
l += 1
}
// 如果当前子数组中包含 k 个以上的 2,更新最短子数组长度
if cnt >= k {
ans = min(ans, i-l+1)
}
}
// 输出最短子数组长度
fmt.Println(ans)
}
// 返回 a 和 b 中的最小值
func min(a, b int) int {
if a < b {
return a
}
return b
}
Js
process.stdin.resume();
process.stdin.setEncoding('utf-8');
let input = '';
process.stdin.on('data', (data) => {
input += data;
return;
});
process.stdin.on('end', () => {
const lines = input.trim().split('\n');
// 读入 n 和 k
const [n, k] = lines[0].trim().split(' ').map(Number);
// 初始化一个长度为 n 的数组 a,用于存储每个数中包含的 2 的个数
const a = lines[1].trim().split(' ').map(Number);
// 初始化最短子数组长度为 n
let ans = n;
// 初始化左端点 l 和当前子数组中包含的 2 的个数 cnt
let cnt = 0, l = 0;
// 遍历 n 个数
for (let i = 0; i < n; i++) {
// 读入当前数 x,计算其中 2 的个数
let x = a[i];
let c = 0;
while (x % 2 === 0) {
c += 1;
x /= 2;
}
// 更新当前子数组中包含的 2 的个数 cnt
cnt += c;
a[i] = c;
// 右移左端点 l,直到当前子数组不满足包含 k 个以上的 2
while (l <= i && cnt - a[l] >= k) {
cnt -= a[l];
l += 1;
}
// 如果当前子数组中包含 k 个以上的 2,更新最短子数组长度
if (cnt >= k) {
ans = Math.min(ans, i - l + 1);
}
}
// 输出最短子数组长度
console.log(ans);
});