【备战秋招】每日一题:2023.7.7-oppo秋招-第三题-最小K0序列

为了更好的阅读体检,可以查看我的算法学习网
本题在线评测链接:P1391

题目描述

K0序列是指,序列中元素的乘积转换为2进制后末尾至少有 k k k个0。

给定一个长度为 n n n的序列 a a a k k k值,求其中为K0序列的连续子序列的最小长度。

输入描述

输入第一行两个正整数 n , k n,k nk。( 3 ≤ n ≤ 1 0 5 , 1 ≤ k ≤ 1 0 5 3 \leq n \leq 10^5,1 \leq k \leq 10^5 3n1051k105

输入第二行 n n n个正整数,第 i i i个为 a i a_i ai。( 1 ≤ a i ≤ 1 0 9 1 \leq a_i \leq 10^9 1ai109

输出描述

输出一个正整数 ,表示连续子序列的二进制不小于 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);
});
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值