算法:二分plus前缀和思想的运用

本文介绍了一个有趣的问题:如何找到一个子数组,使得红色数的乘积末尾0的数量加上蓝色数的乘积末尾0的数量总和不小于k,同时找到满足条件的最短子数组长度。通过使用前缀和与二分查找的方法,可以在较短时间内解决问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在前缀和上进行二分

题目描述:

有一个数组,数组中的数字可以是红色或者蓝色。

找到一个子数组,子数组内,红色的数乘积为x,蓝色的乘积为 y,并且 x 末尾 0 的数量和 y 末尾 0 的数量总和不小于 k,找到满足这样要求的子数组长度的最小值。

输入示例:

1 4 15 8 5
BRRBB

输出示例:

4

思路:

  1. 看到 末尾 0 的个数,就要想到 质因子 2/5 的对数,因为 0 是由某个数乘 10 产生的。

  2. 这个题又要求每个子数组内乘积末尾 0 的个数,为了加快查找速度,可以使用前缀数组进行记录,这样就能在 O ( 1 ) O(1) O(1) 的时间内,得到区间内质因子 2/5 的对数了。

  3. 暴力的话,还是需要 O ( n 2 ) O(n^2) O(n2) 的时间来得到每个子数组,有什么方法可以加速查找呢?二分,所要求的最长满足题意的长度为 n,最短的为0,当一个长度 mid 满足要求的时候,可以暂时先将这个长度记为答案,然后缩小区间,为什么?因为长度大于 mid 也一定是满足要求的,就没必要再求了,所以也相当于一次排除了一半的选项。

整个代码如下,其中包含了对数器进行测试。

public class ZeroMax {
    public static void main(String[] args) {
        int[] nums;
        String color;
        int testTime = 1000000;
        int maxVal = (int) 1e9;
        int maxLen = 100;
        System.out.println("测试开始");
        for (int i = 0; i < testTime; i++) {
            GeneInfo info = geneArr(maxLen, maxVal);
            nums = info.nums;
            color = info.color;
            int k = (int) (Math.random() * 20) + 1;
            genePerfix(nums, color);
            int res1 = median(nums.length, k);
            int res2 = baoli(nums.length, k);
            if (res1 != res2) {
                System.out.println("出错了");
            }
        }
        System.out.println("测试结束");

    }

    public static void print2DArray(int[][] arr) {
        for (int i = 0; i < arr.length; i++) {
            System.out.print("row " + i + ": ");
            for (int j = 0; j < arr[0].length; j++) {
                System.out.print(arr[i][j] + " ");
            }
            System.out.println();
        }
        System.out.println();
    }
//    第 0 行代表 2 的前缀数组
//    第 1 行代表 5 的前缀数组
    static int[][] B25;
    static int[][] R25;
    //生成前缀数组
    public static void genePerfix(int[] nums, String color ) {
        B25 = new int[2][nums.length + 1];
        R25 = new int[2][nums.length + 1];
        for (int i = 0; i < nums.length; i++) {
//            如果是蓝色的话,放进蓝色的前缀数组里面

//            得到当前的数有多少个 25
            int[] cnts = get25(nums[i]);
            if (color.charAt(i) == 'B') {
//                记录 2 的前缀和
                B25[0][i+1] = B25[0][i] + cnts[2];
                B25[1][i+1] = B25[1][i] + cnts[5];

                R25[0][i+1] = R25[0][i];
                R25[1][i+1] = R25[1][i];
            } else {
                R25[0][i+1] = R25[0][i] + cnts[2];
                R25[1][i+1] = R25[1][i] + cnts[5];

                B25[0][i+1] = B25[0][i];
                B25[1][i+1] = B25[1][i];
            }
        }

    }
//    对一个数,求它 2 和 5 质因子的数目
    public static int[] get25 (int num) {
        int begin = 2;
        int[] res = new int[6];
        while (num > 1) {
            while ((num % begin) == 0) {
                res[begin]++;
                num /= begin;
            }
            begin++;
            if (begin > 5) return res;
        }
        return res;
    }

//  二分的查找方法,二分的是长度
//  根据题意要求,要求最短的,当某个长度满足要求的情况下,那么就要缩小区间
    public static int median(int n, int k) {
//        最短长度可以是 0
        int l = 0;
        int r = n;
        int res = Integer.MAX_VALUE;
        while (l <= r) {
            int mid = l + ((r - l) >> 1);
            if (isValid(mid, n, k)) {
//                当前这个长度是满足要求的,存下结果,那么试一下更小的长度呢
                res = Math.min(res, mid);
                r = mid - 1;
            } else {
                l = mid + 1;
            }
        }
        return res;
    }
    public static boolean isValid(int len, int maxLen, int k) {
//        判断长度为 len 的是否满足题意的要求
        for (int i = len; i <= maxLen; i++) {
//            该区间范围内, 质因子 2 的个数
            int Bnum2 = B25[0][i] - B25[0][i-len];
//            该区间范围内, 质因子 5 的个数
            int Bnum5 = B25[1][i] - B25[1][i-len];

            int Rnum2 = R25[0][i] - R25[0][i-len];
            int Rnum5 = R25[1][i] - R25[1][i-len];
            int B0 = Math.min(Bnum2, Bnum5);
            int R0 = Math.min(Rnum2, Rnum5);
            if ((B0 + R0) >= k) return true;
        }
        return false;
    }
//  暴力解法,对每一个子数组,看其是否满足要求,如果满足要求,更新结果,得到最小区间长度
    public static int baoli (int n, int k) {
        int res = Integer.MAX_VALUE;
        for (int i = 0; i < n; i++) {
            for (int j = i; j < n; j++) {
//                对每一个子数组做判断
//                左边界 i, 右边界 j
                int Bnum2 = B25[0][j+1] - B25[0][i];
                int Bnum5 = B25[1][j+1] - B25[1][i];

                int Rnum2 = R25[0][j+1] - R25[0][i];
                int Rnum5 = R25[1][j+1] - R25[1][i];
//              Blue 结尾 0 的个数
                int B0 = Math.min(Bnum2, Bnum5);
//              Red 结尾 0 的个数
                int R0 = Math.min(Rnum2, Rnum5);
//                当前范围内的数字
                if ((B0 + R0) >= k) {
                    res = Math.min(res, j-i + 1);
                }
            }
        }
        return res;
    }

    static class GeneInfo {
        String color;
        int[] nums;
        public GeneInfo(String color, int[] nums) {
            this.color = color;
            this.nums = nums;
        }
    }
//    生成随机数组,进行测试
    public static GeneInfo geneArr(int maxLen, int maxVal) {
        int len = (int)(Math.random() * maxLen + 1);
        StringBuilder color = new StringBuilder();
        int[] nums = new int[len];
        for (int i = 0; i < len; i++) {
            nums[i] = (int)(Math.random() * maxVal + 1);
            color.append(Math.random() < 0.5 ? 'B': 'R');
        }
        return new GeneInfo(color.toString(), nums);
    }
}

其中有一个感觉可以改进的地方是,得到一个数的 2/5 质因子的个数,还请懂得大佬不吝赐教。

还有一个类似的题目:
前缀和与二分查找的应用 | 编程之禅

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值