华为OD机试 - 收集灵草 - 枚举(Java 新系统 200分)

华为OD机试 新系统 双机位C卷 真题题库目录|机考题库 + 算法考点详解 本文整理华为OD机试(机考) 新系统真题题库,提供高频考点精析与详细题目内容,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解 和 在线OJ做题练习。华为OD机试新系统真题题库。 阅读详情

在这里插入图片描述

华为OD机试 新系统 题库疯狂收录中,刷题点这里

专栏导读

本专栏收录于《华为OD机试(JAVA)真题》。

刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天优快云在线答疑。

一、题目描述

在一个远古修士的洞穴中,探宝者发现了一排共 N 棵灵草,每棵灵草有一个灵力值 A,灵力值可为正、负或零。
为避免灵草被探宝者一扫而空,远古修士设置了禁制,要求探宝者必须选择一段连续的灵草序列带回,且必须满足以下规则:
规则一:选中的连续序列中,灵力值为正的数量不少于 K 棵。
规则二:在满足规则一的前提下,探宝者还可以从选中序列中额外挑选最多 M 棵灵力值为负的灵草丢弃,以提升总收益。
求所有合法选择方案中,能够走灵草的灵力值总和的最大值。如果无法满足规则一,输出 -1。
数据范围:

  • 1 <= N <= 10^4
  • 1 <= K <= N
  • 0 <= M <= 5
  • -10^9 <= A <= 10^9

二、输入描述

输入格式:

N K M

a1 a2 a3,... aN

其中:

  • N 表示灵草数量
  • K 表示至少需要选择的正数灵草数量
  • M 表示最多可丢弃的负数灵草数量
  • a1 a2 ... aN 表示灵草灵力值数组

三、输出描述

输出一个整数,表示最大灵力值总和;若不存在满足条件的连续子序列,则输出 -1。

四、测试用例

测试用例1:

1、输入

8 3 2
3 -5 4 -1 2 -8 6 -2

2、输出

14

3、说明

可以选择区间 [1,7]:

3 -5 4 -1 2 -8 6

其中有 4 个正数,满足至少 3 个正数。删除 -5 和 -8 后:

3 + 4 - 1 + 2 + 6 = 14

测试用例2:

1、输入

5 4 1
1 -2 3 -4 5

2、输出

-1

3、说明

整个数组只有 1、3、5 共 3 个正数,小于 K=4,不存在合法方案。

五、解题思路

本题要求选择一个连续区间,区间内至少包含 K 个正数,并允许最多丢弃 M 个负数。由于 M <= 5,可以围绕“删除负数次数”设计小状态动态规划。

枚举区间右端点 r。假设从数组开头到 r 已经出现至少 K 个正数,令 p 为“从 r 向左数第 K 个正数”的位置。任何以 r 为右端点、满足至少 K 个正数的合法区间,其左端点一定不超过 p。因此一个合法区间可以拆成两部分:[l,p-1] 和固定区间 [p,r]。

对于左半部分,预处理 leftBest[i][d]:选择一个以 i 结尾的连续后缀,并最多丢弃 d 个负数时能够得到的最大收益,同时允许空后缀,其收益为 0。这个过程类似 Kadane 最大子段和,只是在遇到负数时多出“保留当前负数”和“消耗一次机会丢弃当前负数”两种选择,时间复杂度 O(NM)。

对于固定区间 [p,r],其普通区间和可以使用前缀和 O(1) 得到。若允许删除负数,显然应该优先删除绝对值最大的负数。因此建立线段树,每个节点只保存区间内绝对值最大的 M 个负数。一次区间查询即可得到 [p,r] 最值得删除的最多 M 个负数,查询复杂度 O(M log N)。

最后枚举 d=0…M,表示左半部分最多使用 d 次删除机会,则固定区间最多使用 M-d 次,两部分收益相加更新答案。若整个数组中的正数数量不足 K,则直接输出 -1。

总时间复杂度为 O(NMlogN),空间复杂度为 O(N*M)。由于 M 最大只有 5,在 N=10^4 的数据范围内效率很高。

六、Java算法源码

public class OdTest {
    static int N;
    static int M;
    static long[] a;
    static long[] prefixSum;
    static long[][] tree;

    /**
     * 合并两个节点中“丢弃负数能够增加的收益”。
     * 每个数组按从大到小保存最多 M 个收益。
     * 不足 M 个负数时使用 0 补位。
     */
    static long[] mergeTop(long[] left, long[] right) {
        long[] result = new long[M];
        int i = 0;
        int j = 0;

        for (int k = 0; k < M; k++) {
            if (i < M && (j >= M || left[i] >= right[j])) {
                result[k] = left[i++];
            } else if (j < M) {
                result[k] = right[j++];
            }
        }

        return result;
    }

    /**
     * 构建线段树。
     * 每个节点保存当前区间内绝对值最大的 M 个负数。
     *
     * 例如负数 -8,丢弃它以后区间总和会增加 8,
     * 因此在线段树中保存的是收益 8。
     */
    static void build(int node, int l, int r) {
        if (l == r) {
            if (M > 0 && a[l] < 0) {
                tree[node][0] = -a[l];
            }
            return;
        }

        int mid = (l + r) >>> 1;

        build(node << 1, l, mid);
        build(node << 1 | 1, mid + 1, r);

        tree[node] = mergeTop(
                tree[node << 1],
                tree[node << 1 | 1]
        );
    }

    /**
     * 查询区间 [ql, qr] 中绝对值最大的 M 个负数,
     * 返回对应的删除收益,并按照从大到小排列。
     */
    static long[] query(int node, int l, int r, int ql, int qr) {
        if (ql <= l && r <= qr) {
            return tree[node];
        }

        int mid = (l + r) >>> 1;

        if (qr <= mid) {
            return query(node << 1, l, mid, ql, qr);
        }

        if (ql > mid) {
            return query(node << 1 | 1, mid + 1, r, ql, qr);
        }

        return mergeTop(
                query(node << 1, l, mid, ql, qr),
                query(node << 1 | 1, mid + 1, r, ql, qr)
        );
    }

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);

        // 同时兼容空格、换行以及英文逗号分隔。
        scanner.useDelimiter("[\\s,]+");

        N = scanner.nextInt();
        int K = scanner.nextInt();
        M = scanner.nextInt();

        a = new long[N + 1];
        prefixSum = new long[N + 1];

        // 保存所有正数所在的位置。
        int[] positivePositions = new int[N];
        int positiveCount = 0;

        for (int i = 1; i <= N; i++) {
            a[i] = scanner.nextLong();

            // 前缀和用于快速得到任意固定区间 [l,r] 的原始总和。
            prefixSum[i] = prefixSum[i - 1] + a[i];

            if (a[i] > 0) {
                positivePositions[positiveCount++] = i;
            }
        }

        /*
         * 如果整个数组的正数总数量都不足 K,
         * 那么任何连续区间都不可能满足规则一。
         */
        if (positiveCount < K) {
            System.out.println(-1);
            return;
        }

        /*
         * leftBest[i][d]:
         *
         * 在前 i 个位置中,选择一个“以 i 作为右边界的连续后缀”,
         * 并允许最多丢弃 d 个负数时能够取得的最大收益。
         *
         * 同时允许选择空后缀,收益记为 0。
         *
         * 这个状态类似于 Kadane 最大子段和,
         * 但遇到负数时可以选择消耗一次机会把它丢掉。
         */
        long[][] leftBest = new long[N + 1][M + 1];

        for (int i = 1; i <= N; i++) {
            for (int d = 0; d <= M; d++) {

                if (a[i] >= 0) {
                    /*
                     * 当前数非负。
                     * 保留当前数一定不会让答案变差,
                     * 直接连接在上一个最优连续后缀后面。
                     */
                    leftBest[i][d] =
                            leftBest[i - 1][d] + a[i];

                } else {
                    /*
                     * 方案1:保留当前负数。
                     */
                    long keep =
                            leftBest[i - 1][d] + a[i];

                    /*
                     * 方案2:如果还有删除额度,
                     * 丢弃当前负数。
                     *
                     * 丢弃以后当前负数对总和的贡献为 0。
                     */
                    long discard = d > 0
                            ? leftBest[i - 1][d - 1]
                            : Long.MIN_VALUE / 4;

                    /*
                     * 方案3:之前的区间收益太差,
                     * 直接重置为空区间 0,
                     * 之后重新开始选择连续区间。
                     */
                    leftBest[i][d] =
                            Math.max(0L, Math.max(keep, discard));
                }
            }
        }

        /*
         * 线段树用于快速查询任意区间中
         * 最值得丢弃的最多 M 个负数。
         */
        tree = new long[4 * N + 5][M];
        build(1, 1, N);

        long answer = Long.MIN_VALUE;

        int seenPositive = 0;

        /*
         * 枚举最终连续区间的右端点 r。
         */
        for (int r = 1; r <= N; r++) {

            if (a[r] > 0) {
                seenPositive++;
            }

            // 正数数量不足 K,此时还无法构成合法区间。
            if (seenPositive < K) {
                continue;
            }

            /*
             * p 表示截至位置 r,
             * 从右向左数第 K 个正数的位置。
             *
             * 对于任何以 r 为右端点且至少有 K 个正数的区间,
             * 左端点一定 <= p。
             *
             * 因此区间可以拆成:
             *
             * [l, p-1] + [p, r]
             */
            int p = positivePositions[seenPositive - K];

            /*
             * 固定区间 [p,r] 尚未删除负数时的原始总和。
             */
            long baseSum =
                    prefixSum[r] - prefixSum[p - 1];

            /*
             * 查询 [p,r] 中最值得丢弃的 M 个负数。
             */
            long[] topNegativeGains =
                    query(1, 1, N, p, r);

            /*
             * removeGain[e]:
             * 在固定区间 [p,r] 中,
             * 最多丢弃 e 个负数能够额外增加多少收益。
             */
            long[] removeGain = new long[M + 1];

            for (int e = 1; e <= M; e++) {
                removeGain[e] =
                        removeGain[e - 1]
                                + topNegativeGains[e - 1];
            }

            /*
             * 将最多 M 次删除机会分配给左右两部分。
             *
             * 左侧最多使用 d 次,
             * 那么固定区间 [p,r] 最多使用 M-d 次。
             */
            for (int d = 0; d <= M; d++) {

                long candidate =
                        leftBest[p - 1][d]
                                + baseSum
                                + removeGain[M - d];

                answer = Math.max(answer, candidate);
            }
        }

        System.out.println(answer);
    }
}

七、效果展示

1、输入

3 2 0
1 -100 2

2、输出

-97

3、说明

需要至少两个正数,只能选择整个区间:

1 -100 2

又因为 M=0 不能删除负数,所以:

1 - 100 + 2 = -97

这个测试非常重要,因为不能简单地把答案初始化为 -1。

在这里插入图片描述


🏆下一篇:华为OD机试 - 简易内存池 - 逻辑分析(Java 新系统 200分)

🏆本专栏收录于《华为OD机试(JAVA)真题》。

刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新,全天优快云在线答疑。

在这里插入图片描述

2026华为OD机考新系统真题 + 双机位C卷 + 往年真题 - 题库目录分值说明 + 考点说明+在线OJ 2026年4月1日起,华为OD机试正式切换至新系统模式!采用力扣模式,无需自己写输入! 难度大幅度降低。正在备考华为OD机试的同学,优先刷新系统机试真题,可大幅提升通过率。 新系统考题考点与旧版一致,无需重新适应。本专栏所有算法题均配有详细思路解析,并提供C语言、JS、C++、Python、Go、Java多语言源码实现。订阅后私信即可开通专属OJ刷题权限,高效备考一次上岸! 阅读详情

相关推荐

2026华为OD机试(Python)真题【E卷+A卷+B卷+C卷+D卷】目录+在线OJ

华为OD机试真题题库【E卷+A卷+B卷+C卷+D卷】目录,Python实现。

MISAYAONE的博客 28万+

华为OD机试 - 收集灵草 - 枚举(Python/JS/C/C++/Go 新系统 200分)

本专栏收录于《华为OD机试真题(Java/Python/JS/C/C++/Go)》。刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,每一题都有详细的答题思路、详细的代码注释、3个测试用例、为什么这道题采用XX算法、XX算法的适用场景,发现新题目,随时更新。在一个远古修士的洞穴中,探宝者发现了一排共 N 棵灵草,每棵灵草有一个灵力值 A,灵力值可为正、负或零。 为避免灵草被探宝者一扫而空,远古修士设置了禁制,要求探宝者必须选择一段连续的灵草序列带回,且必须满足以下规则: 规则一:选中

学Java,找哪吒 214

2026华为OD机试 新系统真题题库目录|机考题库 + 算法考点详解

华为OD机试 新系统真题题库目录|机考题库 + 算法考点详解华为od机试,华为od机试真题,华为od机试E卷,华为odE卷,华为od机试真题E卷,华为OD,华为OD机试,华为OD机试真题,华为od机考,华为od机试D卷,华为od机试真题D卷,华为od手撕代码,华为od,华为od机试C卷,华为od e卷,华为2026e卷,od机试,2026E卷,华为OD机试E卷,什么是华为od,华为od介绍,全面了解华为od,2026华为od,如何备战华为od,华为od算法,华为od手撕代码,华为OD机试2024年最新题库,

KJ.JK 4674

2026最新华为OD机试新系统卷 + 双机位C卷 真题题库目录|全覆盖题库 + 逐点算法考点详解

并提供C语言、JS、C++、Python、Go、Java多语言源码实现。订阅后私信即可开通专属OJ刷题权限,高效备考一次上岸!采用力扣模式,无需自己写输入!正在备考华为OD机试的同学,优先刷。新系统考题考点与旧版一致,无需重新适应。本专栏所有算法题均配有。2026年4月1日起,华为OD机试正式切换至。,可大幅提升通过率。

算法大师! 2万+

2026华为OD机试(JAVA&Go)真题【E卷+A卷+B卷+C卷+D卷】目录+在线OJ

华为OD机试真题题库【E卷+A卷+B卷+C卷+D卷】目录,Java实现。

MISAYAONE的博客 34万+

2026华为OD机试(C语言)真题【E卷+A卷+B卷+C卷+D卷】目录+在线OJ

华为OD机试真题题库【E卷+A卷+B卷+C卷+D卷】目录,C语言实现。

MISAYAONE的博客 11万+

2026最新华为OD新系统机试解析 + 最新真题题库 + 备考策略

2026最新华为OD新系统机试改动变化讲解,分享最新华为OD新系统机试备考策略以及最新华为OD新系统机试题库,华为OD新系统机试题库包含的所有算法题均有详细思路解析,并提供C语言、JS、C++、Python、Go、Java多语言源码实现。订阅后私信即可开通专属OJ刷题权限,实现高效备考。

qq_45776114的博客 7857

华为OD机试 - 最旺店铺 - 枚举(Java 2025 B卷 200分)

接下来的n行,每行输出两个整数p和q,以逗号隔开,其中p表示该市民初步想要投票给p号商店,q表示为了让该市民改投1号商店需要支付的补贴金额。如果选择发放10+20+30,抢2、3、4号店铺手中的票,总共需要发放60元补贴(5号店铺2票、1号店铺3票,1号店铺胜出)选择发放10+20+30,抢2、3、4号店铺手中的票,总共需要发放60元补贴(5号店铺2票、1号店铺3票,1号店铺胜出)如果选择发放10+40,抢2、5号店铺手中的票,总共需要发放50元补贴(5号店铺1票、1号店铺2票,1号店铺胜出)

学Java,找哪吒 1046

2026最新华为OD机试新系统 机考真题题库 + 真题考点分类 + 备考策略

华为OD机试新系统最新机考重点考点详解以及详细备考策略详解。收录华为OD机试真题中所有题目全部提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及提供在线OJ模拟做题。

qq_45776114的博客 2012

华为OD机试 新系统真题-收集灵草(Java/Py/C++/Go/C/Js)

华为OD机试 新系统真题【收集灵草】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD机试真题【收集灵草】多语言题解。

qq_45776114的博客 543

2026华为OD机试(JavaScript)真题【E卷+A卷+B卷+C卷+D卷】目录+在线OJ

华为OD机试真题题库【E卷+A卷+B卷+C卷+D卷】目录,JavaScript实现。

MISAYAONE的博客 8万+

2026最新华为OD机试新系统卷 全覆盖真题题库 + 真题考点分类 + 完整备考策略详解

华为OD机试新系统最新机考全覆盖题库,提供华为OD真题重点题型分类以及详细备考策略详解。华为OD近期真题所有题目全部提供C语言、Java、C++、Python、Go、JavaScript多语言源码实现,并提供完整思路讲解,以及提供在线OJ模拟做题。助力高效提升准备华为OD新系统机试效率。

qq_45776114的博客 898

7.1华为OD机试真题 新系统 - 收集灵草 (Java/Py/C/C++/Js/Go)

7.1华为OD机试真题 新系统 - 收集灵草 (Java/Py/C/C++/Js/Go)

算法大师! 427

7.1华为OD机试真题 新系统 - 收集灵草 (JavaPyCC++JsGo)

4. 当正数个数 >= K 时,计算 cur_sum + neg_sum(丢弃负数后的实际收益)求所有合法选择方案中,能够走灵草的灵力值总和的最大值。2026 华为OD机试真题 7月1日华为OD上机新系统考试真题200 分题型。2026 华为OD机试真题 7月1日华为OD上机新系统考试真题200 分题型。规则二:在满足规则一的前提下,探宝者还可以从选中序列中额外挑选最多。规则一:选中的连续序列中,灵力值为正的数量不少于。在一个远古修士的洞穴中,探宝者发现了一排共。棵灵力值为负的灵草丢弃,以提升总收益。

2601_95165999的博客 86

2026华为OD机试(C++)真题【E卷+A卷+B卷+C卷+D卷】目录+在线OJ

华为OD机试真题题库【E卷+A卷+B卷+C卷+D卷】目录,C++实现。

MISAYAONE的博客 20万+

量化回测系统实战指南:从VectorBT到自研框架的深度解析

本文深入解析量化回测系统实战,从VectorBT和Backtesting.py的对比到自研框架miniQuant的设计与优化。探讨如何平衡速度与灵活性,解决实盘适配性问题,并提供性能调优技巧和可视化分析工具,帮助开发者构建高效可靠的量化交易系统。

weixin_28308325的博客 275
上一篇: 华为OD机试 - 仓库盘点(Python/JS/C/C++ 新系统 100分)
下一篇: 华为OD机试 - 收集灵草 - 枚举(Python/JS/C/C++/Go 新系统 200分)
哪 吒
哪 吒 2022年度博客之星TOP 1 2022年度博客之星TOP 1 领域专家: 后端开发技术领域 领域专家: 后端开发技术领域
博客等级 码龄9年 59万+粉丝 2933原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

哪 吒

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

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

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

打赏作者

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

抵扣说明:

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

余额充值