【华为OD】贪吃的猴子

在这里插入图片描述

文章目录

  • 【华为OD】贪吃的猴子
    • 题目描述
    • 输入描述
    • 输出描述
    • 示例
      • 示例一
      • 示例二
    • 解题思路
    • 解法一:前缀和枚举法
      • Java实现
      • Python实现
      • C++实现
    • 解法二:滑动窗口法
      • Java实现
      • Python实现
      • C++实现
    • 解法三:优化的动态规划法
      • Java实现
      • Python实现
      • C++实现
    • 算法复杂度分析
      • 解法一:前缀和枚举法
      • 解法二:滑动窗口法
      • 解法三:优化的动态规划法
    • 算法原理详解
      • 核心思想
      • 三种解法的特点
    • 示例分析
      • 示例一详细分析
      • 图示表示
    • 优化技巧
      • 1. 边界条件优化
      • 2. 提前终止优化
      • 3. 空间优化
    • 扩展应用
      • 1. 股票买卖问题
      • 2. 游戏策略问题
      • 3. 资源分配问题
    • 常见错误
      • 1. 边界处理错误
      • 2. 索引计算错误
      • 3. 滑动窗口更新错误
    • 总结

【华为OD】贪吃的猴子

题目描述

一只贪吃的猴子,来到一个果园,发现许多串香蕉排成一行,每串香蕉上有若干根香蕉,每串香蕉的根数由数组 numbers 给出,猴子获取香蕉每次都只能从行的开头或者末尾获取,并且只能获取 N 次,求猴子最多能获取多少根香蕉。

输入描述

第一行为数组 numbers 的长度
第二行为数组 numbers 的值,每个数字通过空格分开
第三行输入为 N,表示获取的次数

约束条件:

  • 1 <= numbers.length <= 100000
  • 1 <= numbers[i] <= 100
  • 1 <= N <= numbers.length

输出描述

按照题目要求能获取的最大数值

示例

示例一

输入:

7
1 2 2 7 3 6 1
3

输出:

10

说明:
第一次获取香蕉,无论是从行的开头或者末尾获取,得到的香蕉根数目为1。但是,从行末尾获取能获取到最优的策略,后面可以直接得到香蕉根数目 6和3。因此最终根数为 1+6+3=10

示例二

输入:

7
2 2 2 7 3 6 1
3

输出:

10

解题思路

这是一个典型的滑动窗口问题。猴子只能从数组的两端取元素,取N次,要求最大和。

核心思想:

  1. 猴子取N个元素,这N个元素必定是数组两端的连续子数组的组合
  2. 可以枚举从左端取i个,从右端取(N-i)个的所有情况
  3. 使用前缀和优化计算效率

关键概念:

  • 前缀和:预计算数组前缀和,快速计算任意区间和
  • 枚举策略:枚举从左端取的个数,剩余从右端取
  • 边界处理:注意取0个和取N个的边界情况

我将提供两种解法:前缀和枚举法滑动窗口法

解法一:前缀和枚举法

通过预计算前缀和,枚举所有可能的取法组合,找到最大值。

Java实现

import java.util.*;public class Solution1 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int len = sc.nextInt();int[] numbers = new int[len];for (int i = 0; i < len; i++) {numbers[i] = sc.nextInt();}int n = sc.nextInt();int result = maxBananas(numbers, n);System.out.println(result);sc.close();}private static int maxBananas(int[] numbers, int n) {int len = numbers.length;// 计算前缀和int[] prefixSum = new int[len + 1];for (int i = 0; i < len; i++) {prefixSum[i + 1] = prefixSum[i] + numbers[i];}int maxSum = 0;// 枚举从左端取i个,从右端取(n-i)个for (int i = 0; i <= n; i++) {int leftSum = 0;int rightSum = 0;// 从左端取i个if (i > 0) {leftSum = prefixSum[i];}// 从右端取(n-i)个int rightCount = n - i;if (rightCount > 0) {rightSum = prefixSum[len] - prefixSum[len - rightCount];}maxSum = Math.max(maxSum, leftSum + rightSum);}return maxSum;}
}

Python实现

def max_bananas(numbers, n):length = len(numbers)# 计算前缀和prefix_sum = [0] * (length + 1)for i in range(length):prefix_sum[i + 1] = prefix_sum[i] + numbers[i]max_sum = 0# 枚举从左端取i个,从右端取(n-i)个for i in range(n + 1):left_sum = 0right_sum = 0# 从左端取i个if i > 0:left_sum = prefix_sum[i]# 从右端取(n-i)个right_count = n - iif right_count > 0:right_sum = prefix_sum[length] - prefix_sum[length - right_count]max_sum = max(max_sum, left_sum + right_sum)return max_sumdef solve_prefix_sum():length = int(input())numbers = list(map(int, input().split()))n = int(input())result = max_bananas(numbers, n)print(result)solve_prefix_sum()

C++实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;int maxBananas(vector<int>& numbers, int n) {int len = numbers.size();// 计算前缀和vector<int> prefixSum(len + 1, 0);for (int i = 0; i < len; i++) {prefixSum[i + 1] = prefixSum[i] + numbers[i];}int maxSum = 0;// 枚举从左端取i个,从右端取(n-i)个for (int i = 0; i <= n; i++) {int leftSum = 0;int rightSum = 0;// 从左端取i个if (i > 0) {leftSum = prefixSum[i];}// 从右端取(n-i)个int rightCount = n - i;if (rightCount > 0) {rightSum = prefixSum[len] - prefixSum[len - rightCount];}maxSum = max(maxSum, leftSum + rightSum);}return maxSum;
}int main() {int len;cin >> len;vector<int> numbers(len);for (int i = 0; i < len; i++) {cin >> numbers[i];}int n;cin >> n;int result = maxBananas(numbers, n);cout << result << endl;return 0;
}

解法二:滑动窗口法

使用滑动窗口的思想,先取前N个元素,然后逐步调整窗口位置。

Java实现

import java.util.*;public class Solution2 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int len = sc.nextInt();int[] numbers = new int[len];for (int i = 0; i < len; i++) {numbers[i] = sc.nextInt();}int n = sc.nextInt();int result = maxBananasSliding(numbers, n);System.out.println(result);sc.close();}private static int maxBananasSliding(int[] numbers, int n) {int len = numbers.length;// 先计算取前n个元素的和int currentSum = 0;for (int i = 0; i < n; i++) {currentSum += numbers[i];}int maxSum = currentSum;// 滑动窗口:逐步将左端元素替换为右端元素for (int i = 0; i < n; i++) {// 移除左端第(n-1-i)个元素,添加右端第(len-1-i)个元素currentSum = currentSum - numbers[n - 1 - i] + numbers[len - 1 - i];maxSum = Math.max(maxSum, currentSum);}return maxSum;}
}

Python实现

def max_bananas_sliding(numbers, n):length = len(numbers)# 先计算取前n个元素的和current_sum = sum(numbers[:n])max_sum = current_sum# 滑动窗口:逐步将左端元素替换为右端元素for i in range(n):# 移除左端第(n-1-i)个元素,添加右端第(length-1-i)个元素current_sum = current_sum - numbers[n - 1 - i] + numbers[length - 1 - i]max_sum = max(max_sum, current_sum)return max_sumdef solve_sliding_window():length = int(input())numbers = list(map(int, input().split()))n = int(input())result = max_bananas_sliding(numbers, n)print(result)solve_sliding_window()

C++实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;int maxBananasSliding(vector<int>& numbers, int n) {int len = numbers.size();// 先计算取前n个元素的和int currentSum = 0;for (int i = 0; i < n; i++) {currentSum += numbers[i];}int maxSum = currentSum;// 滑动窗口:逐步将左端元素替换为右端元素for (int i = 0; i < n; i++) {// 移除左端第(n-1-i)个元素,添加右端第(len-1-i)个元素currentSum = currentSum - numbers[n - 1 - i] + numbers[len - 1 - i];maxSum = max(maxSum, currentSum);}return maxSum;
}int main() {int len;cin >> len;vector<int> numbers(len);for (int i = 0; i < len; i++) {cin >> numbers[i];}int n;cin >> n;int result = maxBananasSliding(numbers, n);cout << result << endl;return 0;
}

解法三:优化的动态规划法

使用动态规划的思想,记录每种取法的最优解。

Java实现

import java.util.*;public class Solution3 {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int len = sc.nextInt();int[] numbers = new int[len];for (int i = 0; i < len; i++) {numbers[i] = sc.nextInt();}int n = sc.nextInt();int result = maxBananasDP(numbers, n);System.out.println(result);sc.close();}private static int maxBananasDP(int[] numbers, int n) {int len = numbers.length;// leftSum[i] 表示从左端取i个元素的和int[] leftSum = new int[n + 1];for (int i = 1; i <= n; i++) {leftSum[i] = leftSum[i - 1] + numbers[i - 1];}// rightSum[i] 表示从右端取i个元素的和int[] rightSum = new int[n + 1];for (int i = 1; i <= n; i++) {rightSum[i] = rightSum[i - 1] + numbers[len - i];}int maxSum = 0;for (int i = 0; i <= n; i++) {maxSum = Math.max(maxSum, leftSum[i] + rightSum[n - i]);}return maxSum;}
}

Python实现

def max_bananas_dp(numbers, n):length = len(numbers)# left_sum[i] 表示从左端取i个元素的和left_sum = [0] * (n + 1)for i in range(1, n + 1):left_sum[i] = left_sum[i - 1] + numbers[i - 1]# right_sum[i] 表示从右端取i个元素的和right_sum = [0] * (n + 1)for i in range(1, n + 1):right_sum[i] = right_sum[i - 1] + numbers[length - i]max_sum = 0for i in range(n + 1):max_sum = max(max_sum, left_sum[i] + right_sum[n - i])return max_sumdef solve_dp():length = int(input())numbers = list(map(int, input().split()))n = int(input())result = max_bananas_dp(numbers, n)print(result)solve_dp()

C++实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;int maxBananasDP(vector<int>& numbers, int n) {int len = numbers.size();// leftSum[i] 表示从左端取i个元素的和vector<int> leftSum(n + 1, 0);for (int i = 1; i <= n; i++) {leftSum[i] = leftSum[i - 1] + numbers[i - 1];}// rightSum[i] 表示从右端取i个元素的和vector<int> rightSum(n + 1, 0);for (int i = 1; i <= n; i++) {rightSum[i] = rightSum[i - 1] + numbers[len - i];}int maxSum = 0;for (int i = 0; i <= n; i++) {maxSum = max(maxSum, leftSum[i] + rightSum[n - i]);}return maxSum;
}int main() {int len;cin >> len;vector<int> numbers(len);for (int i = 0; i < len; i++) {cin >> numbers[i];}int n;cin >> n;int result = maxBananasDP(numbers, n);cout << result << endl;return 0;
}

算法复杂度分析

解法一:前缀和枚举法

  • 时间复杂度:O(N + K),其中N是数组长度,K是取的次数
  • 空间复杂度:O(N),存储前缀和数组

解法二:滑动窗口法

  • 时间复杂度:O(N + K),初始化窗口O(K),滑动窗口O(K)
  • 空间复杂度:O(1),只使用常数额外空间

解法三:优化的动态规划法

  • 时间复杂度:O(K),只需要计算左右两端的累积和
  • 空间复杂度:O(K),存储左右累积和数组

算法原理详解

核心思想

这个问题的本质是在数组两端选择K个元素使和最大。关键洞察:

  1. 选择的K个元素必定是左端连续i个 + 右端连续(K-i)个的组合
  2. 需要枚举所有可能的i值(0 ≤ i ≤ K)
  3. 使用前缀和或滑动窗口优化计算效率

三种解法的特点

  1. 前缀和枚举法

    • 思路直观,易于理解
    • 预计算前缀和,快速计算区间和
    • 适合理解问题本质
  2. 滑动窗口法

    • 空间效率最高
    • 通过窗口滑动避免重复计算
    • 代码相对复杂,但效率高
  3. 动态规划法

    • 分别计算左右两端的累积和
    • 逻辑清晰,代码简洁
    • 时间复杂度最优

示例分析

示例一详细分析

数组:[1, 2, 2, 7, 3, 6, 1],N = 3

所有可能的取法:

  1. 左端取0个,右端取3个:0 + (3+6+1) = 10
  2. 左端取1个,右端取2个:1 + (6+1) = 8
  3. 左端取2个,右端取1个:(1+2) + 1 = 4
  4. 左端取3个,右端取0个:(1+2+2) + 0 = 5

最大值为10,对应策略:从右端取3个元素(3,6,1)。

图示表示

数组: [1, 2, 2, 7, 3, 6, 1]↑           ↑  ↑  ↑可能取      最优策略:取这3个策略枚举:
- 取法1: [] + [3,6,1] = 10 ✓ (最优)
- 取法2: [1] + [6,1] = 8
- 取法3: [1,2] + [1] = 4  
- 取法4: [1,2,2] + [] = 5

优化技巧

1. 边界条件优化

// 特殊情况:如果N等于数组长度,直接返回所有元素和
if (n == numbers.length) {return Arrays.stream(numbers).sum();
}

2. 提前终止优化

// 如果当前和已经是理论最大值,可以提前终止
if (currentSum == totalSum) {return currentSum;
}

3. 空间优化

// 滑动窗口法不需要额外的数组空间
// 只需要维护当前窗口的和
int windowSum = 0;
for (int i = 0; i < n; i++) {windowSum += numbers[i];
}

扩展应用

1. 股票买卖问题

可以应用于股票交易中选择最优的买卖时机。

2. 游戏策略问题

在游戏中选择最优的行动策略,最大化收益。

3. 资源分配问题

在有限的操作次数内,选择最优的资源获取策略。

常见错误

1. 边界处理错误

// 错误:没有考虑取0个的情况
for (int i = 1; i <= n; i++) { // 应该从0开始// ...
}// 正确:
for (int i = 0; i <= n; i++) {// ...
}

2. 索引计算错误

// 错误:右端索引计算错误
rightSum = prefixSum[len] - prefixSum[len - rightCount - 1]; // 多减了1// 正确:
rightSum = prefixSum[len] - prefixSum[len - rightCount];

3. 滑动窗口更新错误

// 错误:窗口更新逻辑错误
currentSum = currentSum - numbers[i] + numbers[len - 1 - i]; // 索引错误// 正确:
currentSum = currentSum - numbers[n - 1 - i] + numbers[len - 1 - i];

总结

三种解法都能正确解决问题,选择建议:

  1. 前缀和枚举法

    • 思路最直观,易于理解和实现
    • 适合初学者和面试场景
    • 推荐用于学习和理解问题
  2. 滑动窗口法

    • 空间效率最高,只需O(1)额外空间
    • 适合内存受限的场景
    • 推荐用于性能要求高的场景
  3. 动态规划法

    • 时间复杂度最优O(K)
    • 代码简洁,逻辑清晰
    • 推荐用于竞赛和实际应用

核心要点:

  • 理解问题的本质:在数组两端选择元素
  • 掌握枚举所有可能组合的方法
  • 使用前缀和或滑动窗口优化计算
  • 注意边界条件的处理

对于华为OD机试,建议掌握前缀和枚举法,因为它最容易理解和实现,不容易出错,且能很好地展示对问题的理解。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/news/921868.shtml
繁体地址,请注明出处:http://hk.pswp.cn/news/921868.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Flie ,IO流(一)

一.File&#xff0c;IO流概述二.File文件1.File文件对象的创建&#xff08;路径&#xff1a;&#xff09;2.常用方法1:判断文件类型、获取文件信息&#xff08;注意&#xff1a;&#xff09;3.常用方法2:创建文件、删除文件&#xff08;creatNewFile&#xff08;&#xff09;会…

第2讲 机器学习 - 导论

我们正处在一个"数据时代"&#xff0c;更强的计算能力和更丰富的存储资源使数据总量与日俱增。然而真正的挑战在于如何从海量数据中提取价值。企业与组织正通过数据科学、数据挖掘和机器学习的技术体系构建智能系统应对这一挑战。其中&#xff0c;机器学习已成为计算…

如何解决pip安装报错ModuleNotFoundError: No module named ‘python-dateutil’问题

【Python系列Bug修复PyCharm控制台pip install报错】如何解决pip安装报错ModuleNotFoundError: No module named ‘python-dateutil’问题 摘要 在日常 Python 开发过程中&#xff0c;我们经常会遇到各种 pip install 的报错&#xff0c;尤其是在 PyCharm 2025 控制台环境下&…

GitHub Pages 部署

地址&#xff1a;https://github.com/ 参考&#xff1a;https://blog.csdn.net/qq_45802269/article/details/127310952?ops_request_misc&request_id&biz_id102&utm_term%E5%9F%BA%E4%BA%8Egithub%E5%B9%B3%E5%8F%B0%EF%BC%8C%E5%8F%91%E5%B8%83vue%E9%A1%B9%E7%…

redis分布式锁为什么采用Lua脚本实现。而不是事务

Redis 分布式锁使用 Lua 脚本而非事务&#xff0c;核心原因是 Lua 脚本能保证分布式锁操作的 “原子性” 和 “灵活性”&#xff0c;而 Redis 事务在某些场景下无法满足分布式锁的核心需求。一、Redis事务的局限性redis分布式锁的核心是先判断自己是否持有锁&#xff0c;然后在…

Flutter之riverpod状态管理Widget UI详解

一、riverpod状态管理中所涉及到的widget UI组件对比分析UI 组件状态类型语法形式特点ConsumerWidget有状态无状态形式最常用&#xff0c;通过WidgetRef访问provider&#xff0c;所谓无状态&#xff0c;是指ConsumerWidegt不像StatefulWidegt那样创建state,在它内部不可以定义状…

什么是测试

文章目录软件测试是干什么的&#xff1f;软件测试开发工程师是干什么的&#xff1f;测试工程师是干什么的&#xff1f;软件测试开发工程师和测试工程师的区别效率工具能不能替代测试人员&#xff1f;测开人员的上手路线找工作/实习的时候怎么确定自己找的是测开还是测试呢&…

搭建分片集群

主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决&#xff1a;海量数据存储问题高并发写的问题使用分片集群可以解决上述问题&#xff0c;如图:分片集群特征&#xff1a;集群中有多个master&#xff0c;每个master保存不同数据每个master都可以有多个sla…

在ubuntu系统中如何将docker安装在指定目录

在 Ubuntu 系统中&#xff0c;Docker 默认安装路径&#xff08;程序文件&#xff09;通常在/usr/bin等系统目录&#xff0c;而核心数据&#xff08;镜像、容器、卷等&#xff09;默认存储在/var/lib/docker。若需将数据目录指定到其他位置&#xff08;这是更常见的需求&#xf…

服务器都是用的iis, 前端部署后报跨域,不是用同一个服务器 是前端项目的服务器做Nginx转发,还是后端项目的服务器做Nginx转发?

当服务器环境为 IIS&#xff08;而非 Nginx&#xff09;&#xff0c;且前端、后端部署在不同服务器导致跨域时&#xff0c;核心思路与 Nginx 场景一致&#xff0c;但实现工具从「Nginx」替换为「IIS 配置」。此时依然存在 “后端服务器配置跨域头” 和 “前端服务器配置反向代理…

【大前端】前端生成二维码

前端生成二维码有很多方法&#xff0c;常见的做法是使用 JavaScript 库 来生成二维码。下面整理几种常用方案&#xff0c;并附示例代码。1️⃣ 使用 qrcode 库&#xff08;推荐&#xff09;qrcode 是一个非常流行的前端 JS 库&#xff0c;可以生成 Canvas 或者 SVG 的二维码。安…

LeetCode 刷题【71. 简化路径】

71. 简化路径 自己做 解&#xff1a;遍历检查 class Solution { public:string simplifyPath(string path) {int p 0;string res;while(p < (int)path.size()){//情况1&#xff1a;遇到"/./" 》p跳过"/."if(p < (int)path.size() - 2 && p…

《算法闯关指南:优选算法-双指针》--01移动零,02复写零

&#x1f525;个人主页&#xff1a;草莓熊Lotso &#x1f3ac;作者简介&#xff1a;C研发方向学习者 &#x1f4d6;个人专栏&#xff1a;《C知识分享》《Linux 入门到实践&#xff1a;零基础也能懂》《数据结构与算法》《测试开发实战指南》《算法题闯关指南》 ⭐️人生格言&am…

【小白笔记】命令不对系统:无法将‘head’项识别为 cmdlet、函数、脚本文件或可运行程序的名称

head : 无法将“head”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。请检查名称的拼写&#xff0c;如果包括路径&#xff0c;请确保路径正确&#xff0c;然后再试一次。所在位置 行:1 字符: 1 head -5 train_data.csv ~~~~ CategoryInfo : ObjectNotFound: (h…

宋红康 JVM 笔记 Day15|垃圾回收相关算法

一、今日视频区间 P138-P153 二、一句话总结 标记阶段&#xff1a;引用计数算法&#xff1b;标记阶段&#xff1a;可达性分析算法&#xff1b;对象的finalization机制&#xff1b;MAT与JProfiler的GC Roots溯源&#xff1b;清除阶段&#xff1a;标记-清除算法&#xff1b;清除阶…

Go基础(③Cobra)

Cobra 是帮你快速开发命令行工具的框架 假设你想做一个叫 todo 的命令行工具&#xff0c;实现这些功能&#xff1a; todo add "买牛奶" → 添加待办 todo list → 查看所有待办 todo done 1 → 标记第 1 个待办为已完成 没有 Cobra 的话&#xff0c;你需要自己写代…

从 scheduler_tick 到上下文切换:深入解析 Linux 内核的 TIF_NEED_RESCHED 标志设置流程

Linux 是如何决定何时进行上下文切换的&#xff1f; 在Linux中&#xff0c;CPU 上下文切换是指当操作系统将 CPU 从一个进程切换到另一个进程时&#xff0c;保存当前进程的执行状态&#xff0c;并加载新进程的执行状态的过程就称为上下文切换。 但在 Linux 内核中&#xff0c…

Redis 深度解析:数据结构、持久化与集群

Redis (Remote Dictionary Server) 是一种高性能的键值&#xff08;Key-Value&#xff09;内存数据库&#xff0c;以其丰富的数据结构、极低的延迟、出色的稳定性和强大的集群能力&#xff0c;在现代应用程序的开发中扮演着至关重要的角色。无论是作为缓存、消息队列、会话存储…

HTTPS优化简单总结

性能损耗选择椭圆曲线&#xff0c;并生成椭圆曲线的计算耗时CA证书验证的耗时计算pre-master的耗时硬件优化HTTPS是计算密集型任务&#xff0c;不是IO密集型任务所以硬件最好买更高级的CPU&#xff0c;而不是网卡&#xff0c;磁盘协议优化ECDHE代替RSA&#xff0c;因为ECDHE可以…

从IFA再出发:中国制造与海信三筒洗衣机的“答案”

当全球消费电子行业的目光再次聚焦柏林&#xff0c;柏林国际电子消费品展览会(IFA2025)不仅成为创新产品的秀场&#xff0c;更悄然变身为中国企业讲述全球化进阶故事的重要舞台。近日&#xff0c;海信旗下三筒洗衣机——棉花糖Ultra全家筒迎来它的国际首秀&#xff0c;首次海外…