华为od机考2025A卷真题 -补种未成活胡杨
题目描述与示例
题目描述
近些年来,我国防沙治沙取得显著成果。某沙漠新种植N
棵胡杨(编号1-N
),排成一排。
一个月后,有M
棵胡杨未能成活。
现可补种胡杨K
棵,请问如何补种(只能补种,不能新种) ,可以得到最多的连续胡杨树?
题目练习网址:https://www.algomooc.com/problem/P3254
输入描述
N`:总种植数量 `1<=N<=10``^5
M`:未成活胡杨数量 `1<=M<=N
M
个空格分隔的数,按编号从小到大排列
K`:最多可以补种的数量`0`` ``<=`` ``K`` ``<=`` ``M
输出描述
最多的连续胡杨棵树
示例一
输入
5
2
2 4
1
输出
3
说明
补种胡杨2
或4
,可以得到连续的胡杨[1, 2, 3]
或[3, 4, 5]
。
示例二
输入
10
3
2 4 7
1
输出
6
说明
补种胡杨7
,可以得到连续的胡杨[5, 6, 7, 8, 9, 10]
。
示例三
输入
20
3
4 9 15
2
输出
16
解题思路
不定滑窗解法
一种非常容易想到的做法是,对原来给定的数据进行平铺还原的操作。譬如对于示例二
10
3
2 4 7
1
我们将这个长度为10
的原数组平铺为
[1, 0, 1, 0, 1, 1, 0, 1, 1, 1]
其中0
表示尚未种树,1
表示已经种树。
注意,由于题目所给定的编号是
1-N
,而数组的下标是从0
开始的,此处的编号会存在一个1
的偏差。
这部分过程的代码如下
# 初始化平铺数组,初始化均为1
nums = [1] * N
# 遍历所有死树
for i in range(M):
# trees[i]是死树从1到N的编号
# trees[i]是死树在数组中从0到N-1的索引
nums[trees[i]-1] = 0
由于k = 1
,这意味着我们只能够最多种下一棵树。种下的这棵树,要使得数组存在最长的连续的1
。
所以问题就可以转变为,找到一个最长的连续子数组,这个子数组包含k
个0
。
因为这k
个0
都将会被替换为1
,将得到最长的连续1
的个数。
譬如对于示例二而言,我们仅需将索引为6
的未成活杨树进行补种,就可以得到最长的连续1
的数组。
[1, 0, 1, 0, 1, 1, 0, 1, 1, 1]
直接使用滑窗三问三答就很简单了
# 初始化左指针为0,初始化窗口中包含的0的个数为0,初始化答案为0
left = 0
win_0 = 0
ans = 0
# 进行滑窗过程
for right, num in enumerate(nums):
# A1
if num == 0:
win_0 += 1
# A2
while win_0 > K:
if nums[left] == 0:
win_0 -= 1
left += 1
# A3
ans = max(ans, right - left + 1)
print(ans)
注意到上述做法的时间复杂度是O(N)
的,当N
取上限值10^5
时,是可以通过所有用例的。
固定滑窗解法
固定滑窗的解法较抽象,但是时间复杂度可以达到更加优秀的
O(M)
的复杂度。感兴趣的同学可以自行学习。
从题目所给的几个例子可以看出,如果M
远小于N
,那么那些原先已经连续成活的树木,完全可以只用区间长度来进行表示。
譬如对于示例二,由于我们知道为未成活的杨树编号分别为
2 4 7
那么也就知道,原先就已经成活的杨树被分成了4
部分(4
个区间),其中数目分别为
1 1 2 3
这样就比不定滑窗解法中,将原先的数组进行平铺出来的这种做法
[1, 0, 1, 0, 1, 1, 0, 1, 1, 1]
进行了更进一步的数据压缩。
这个结果可以用下面代码得到
# 构建tree_left哈希表用于表示某棵死树
# 与其左边最近的死树(上一棵死树)之间,一共有多少棵连续的活树
tree_left = dict()
# 第一棵死树为trees[0],由于编号是从1开始
# 所以其左边一共有trees[0] - 1棵连续的活树
tree_left[trees[0]] = trees[0] - 1
# 遍历除了第一棵死树外的所有死树
for i in range(1, M):
# 当前死树,上一棵死树的编号分别为trees[i], trees[i-1]
tree, pre_tree = trees[i], trees[i-1]
# 两棵树之间的连续活树的数目为 tree - pre_tree - 1
tree_left[tree] = tree - pre_tree - 1
# 构建tree_right哈希表用于表示某棵死树
# 与其右边最近的死树(下一棵死树)的之间,一共有多少棵连续的活树
tree_right = dict()
# 最后一棵死树为trees[-1],最后一棵树的编号为N
# 所以其右边一共有N - trees[-1]棵连续的活树
tree_right[trees[-1]] = N - trees[-1]
# 遍历除了最后一棵死树外的所有死树
for i in range(M-1):
# 当前死树,下一棵死树的编号分别为trees[i], trees[i+1]
tree, nxt_tree = trees[i], trees[i+1]
# 两棵树之间的连续活树的数目为 nxt_tree - tree - 1
tree_right[tree] = nxt_tree - tree - 1
其中tree_left
表示,某个未成活杨树编号的左边,一共存在多少棵连续的活树。
同理tree_right
表示,某个未成活杨树编号的右边,一共存在多少棵连续的活树。
譬如对于上述例子存在
tree_left = {
2: 1,
4: 1,
7: 2}
tree_right = {
2: 1,
4: 2,
7: 3}
由于每种下一棵树,能够使得每一个死树的左右两边的连续活树连接起来。
因此我们会考虑,如果将其中原先的M
棵死树中的近邻的K
棵进行种植,则可以尽可能长地得到连续的活树了。
这就退化成了一个固定滑窗的问题。
考虑原来的长度为
M
的死树数组trees
,选择其中连续的K
个元素将其转为活树,使得连续的活树数目尽可能的多。求最大的连续活树的数目。
显然,每一个固定滑窗中连续存活的活树数目由三部分构成:
- 这
K
棵补种的活树 - 这
K
棵补种的活树中的最左边那棵补种的树,其左边连续的活树数目 - 这
K
棵补种的活树的每一棵树,其右边连续的活树数目
因此第一个固定滑窗的初始化为
win_sum = K + tree_left[trees[0]] + sum(tree_right[i] for i in trees[:K])
而每一次窗口移动的时候,最右边新补种的树的右边,要将加上tree_right[right]
棵新种下的树。(A1)
而最左边原先补种的树不再种植,那么这部分原先存在窗口中的树tree_left[left]
将被删去。(A2)
考虑固定滑窗三问三答,代码为
# 考虑第一个固定滑窗的情况,这个滑窗包含了最开始的K棵死树,如果把这些树都种下
# 第一个固定滑窗中的连续成活胡杨的数目由以下部分构成:
# 1. K棵补种的树
# 2. 第一棵死树trees[0]左边所有连续成活的树
# 3. 这K棵死树右边的连续成活的树
# 上述部分进行求和,即第一个固定滑窗的连续成活胡杨总和,用变量win_sum表示
win_sum = K + tree_left[trees[0]] + sum(tree_right[i] for i in trees[:K])
# 初始化答案变量为第一个固定滑窗的结果
ans = win_sum
# 进行固定滑窗过程
for right, idx in enumerate(trees[K:], K):
# Q1:对于每一个右指针right所指的元素idx,做什么操作?
# A1:将idx右边的连续成活胡杨,加入窗口和win_sum中
win_sum += tree_right[idx]
# Q2:对于left所指的元素idx_left,要做什么操作?
# A2:将idx_left左边的连续成活胡杨,从窗口和win_sum中减出
left = right-K
idx_left = trees[left]
win_sum -= tree_left[idx_left]
# Q3:如何更新ans?
# A3:取ans和win_sum中的较大值进行更新
ans = max(win_sum, ans)
代码
解法一:不定滑窗
Python
# 欢迎来到「欧弟算法 - 华为OD全攻略」,收录华为OD题库、面试指南、八股文与学员案例!
# 地址:https://www.odalgo.com
# 胡杨总数
N = int(input())
# 未成活数目
M = int(input())
# 未成活胡杨的位置
trees = list(map(int, input().split()))
# 补种数目
K = int(input())
# 初始化平铺数组,初始化均为1
nums = [1] * N
# 初始化平铺数组,初始化均为1
nums = [1] * N
# 遍历所有死树
for i in range(M):
# trees[i]是死树从1到N的编号
# trees[i]-1是死树在数组中从0到N-1的索引
nums[trees[i]-1] = 0
# 初始化左指针为0,初始化窗口中包含的0的个数为0,初始化答案为0
left = 0
win_0 = 0
ans = 0
# 进行滑窗过程
for right, num in enumerate(nums):
# A1
if num == 0:
win_0 += 1
# A2
while win_0 > K:
if nums[left] == 0:
win_0 -= 1
left += 1
# A3
ans = max(ans, right - left + 1)
# 输出答案
print(ans)
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 胡杨总数
int N = scanner.nextInt();
// 未成活数目
int M = scanner.nextInt();
// 未成活胡杨的位置
int[] trees = new int[M];
for (int i = 0; i < M; i++) {
trees[i] = scanner.nextInt();
}
// 补种数目
int K = scanner.nextInt();
// 初始化平铺数组,初始化均为1
int[] nums = new int[N];
for (int i = 0; i < N; i++) {
nums[i] = 1;
}
// 遍历所有死树
for (int i = 0; i < M; i++) {
// trees[i]是死树从1到N的编号
// trees[i]-1是死树在数组中从0到N-1的索引
nums[trees[i] - 1] = 0;
}
// 初始化左指针为0,初始化窗口中包含的0的个数为0,初始化答案为0
int left = 0;
int win_0 = 0;
int ans = 0;
// 进行滑窗过程
for (int right = 0; right < N; right++) {
// A1
if (nums[right] == 0) {
win_0++;
}
// A2
while (win_0 > K) {
if (nums[left] == 0) {
win_0--;
}
left++;
}
// A3
ans = Math.max(ans, right - left + 1);
}
// 输出答案
System.out.println(ans);
}
}
C++
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 胡杨总数
int N;
cin >> N;
// 未成活数目
int M;
cin >> M;
// 未成活胡杨的位置
vector<int> trees(M);
for (int i = 0; i < M; i++) {
cin >> trees[i];
}
// 补种数目
int K;
cin >> K;
// 初始化平铺数组,初始化均为1
vector<int> nums(N, 1);
// 遍历所有死树
for (int i = 0; i < M; i++) {
// trees[i]是死树从1到N的编号
// trees[i]-1是死树在数组中从0到N-1的索引
nums[trees[i] - 1] = 0;
}
// 初始化左指针为0,初始化窗口中包含的0的个数为0,初始化答案为0
int left = 0;
int win_0 = 0;
int ans = 0;
// 进行滑窗过程
for (int right = 0; right < N; right++) {
// A1
if (nums[right] == 0) {
win_0++;
}
// A2
while (win_0 > K) {
if (nums[left] == 0) {
win_0--;
}
left++;
}
// A3
ans = max(ans, right - left + 1);
}
// 输出答案
cout << ans << endl;
return 0;
}
时空复杂度
时间复杂度:O(N)
。仅需一次遍历平铺后的数组,每一个元素只会被right
和left
遍历到至多2
次。
空间复杂度:O(1)
。仅需若干常数变量
解法二:固定滑窗
Python
# 欢迎来到「欧弟算法 - 华为OD全攻略」,收录华为OD题库、面试指南、八股文与学员案例!
# 地址:https://www.odalgo.com
# 胡杨总数
N = int(input())
# 未成活数目
M = int(input())
# 未成活胡杨的位置
trees = list(map(int, input().split()))
# 补种数目
K = int(input())
# 构建tree_left哈希表用于表示某棵死树
# 与其左边最近的死树(上一棵死树)之间,一共有多少棵连续的活树
tree_left = dict()
# 第一棵死树为trees[0],由于编号是从1开始
# 所以其左边一共有trees[0] - 1棵连续的活树
tree_left[trees[0]] = trees[0] - 1
# 遍历除了第一棵死树外的所有死树
for i in range(1, M):
# 当前死树,上一棵死树的编号分别为trees[i], trees[i-1]
tree, pre_tree = trees[i], trees[i-1]
# 两棵树之间的连续活树的数目为 tree - pre_tree - 1
tree_left[tree] = tree - pre_tree - 1
# 构建tree_right哈希表用于表示某棵死树
# 与其右边最近的死树(下一棵死树)的之间,一共有多少棵连续的活树
tree_right = dict()
# 最后一棵死树为trees[-1],最后一棵树的编号为N
# 所以其右边一共有N - trees[-1]棵连续的活树
tree_right[trees[-1]] = N - trees[-1]
# 遍历除了最后一棵死树外的所有死树
for i in range(M-1):
# 当前死树,下一棵死树的编号分别为trees[i], trees[i+1]
tree, nxt_tree = trees[i], trees[i+1]
# 两棵树之间的连续活树的数目为 nxt_tree - tree - 1
tree_right[tree] = nxt_tree - tree - 1
# 考虑第一个固定滑窗的情况,这个滑窗包含了最开始的K棵死树,如果把这些树都种下
# 第一个固定滑窗中的连续成活胡杨的数目由以下部分构成:
# 1. K棵补种的树
# 2. 第一棵死树trees[0]左边所有连续成活的树
# 3. 这K棵死树右边的连续成活的树
# 上述部分进行求和,即第一个固定滑窗的连续成活胡杨总和,用变量win_sum表示
win_sum = K + tree_left[trees[0]] + sum(tree_right[i] for i in trees[:K])
# 初始化答案变量为第一个固定滑窗的结果
ans = win_sum
# 进行固定滑窗过程
for right, idx in enumerate(trees[K:], K):
# Q1:对于每一个右指针right所指的元素idx,做什么操作?
# A1:将idx右边的连续成活胡杨,加入窗口和win_sum中
win_sum += tree_right[idx]
# Q2:对于left所指的元素idx_left,要做什么操作?
# A2:将idx_left左边的连续成活胡杨,从窗口和win_sum中减出
left = right-K
idx_left = trees[left]
win_sum -= tree_left[idx_left]
# Q3:如何更新ans?
# A3:取ans和win_sum中的较大值进行更新
ans = max(win_sum, ans)
print(ans)
C++
#include <iostream>
#include <unordered_map>
#include <vector>
using namespace std;
int main() {
int N, M, K;
cin >> N >> M; // 胡杨总数、未成活数目
vector<int> trees(M); // 未成活胡杨的位置
for (int i = 0; i < M; i++) {
cin >> trees[i];
}
cin >> K; // 补种数目
unordered_map<int, int> treeLeft; // 构建tree_left哈希表用于表示某棵死树
treeLeft[trees[0]] = trees[0] - 1; // 第一棵死树为trees[0],由于编号是从1开始,所以其左边一共有trees[0] - 1棵连续的活树
for (int i = 1; i < M; i++) {
int tree = trees[i]; // 当前死树
int preTree = trees[i - 1]; // 上一棵死树的编号
treeLeft[tree] = tree - preTree - 1; // 两棵树之间的连续活树的数目为 tree - pre_tree - 1
}
unordered_map<int, int> treeRight; // 构建tree_right哈希表用于表示某棵死树
treeRight[trees[M - 1]] = N - trees[M - 1]; // 最后一棵死树为trees[-1],最后一棵树的编号为N,所以其右边一共有N - trees[-1]棵连续的活树
for (int i = 0; i < M - 1; i++) {
int tree = trees[i]; // 当前死树
int nxtTree = trees[i + 1]; // 下一棵死树的编号
treeRight[tree] = nxtTree - tree - 1; // 两棵树之间的连续活树的数目为 nxt_tree - tree - 1
}
int winSum = K + treeLeft[trees[0]]; // 考虑第一个固定滑窗的情况,这个滑窗包含了最开始的K棵死树,如果把这些树都种下
for (int i = 0; i < K; i++) {
winSum += treeRight[trees[i]]; // 第一个固定滑窗中的连续成活胡杨的数目由以下部分构成:1. K棵补种的树 2. 第一棵死树trees[0]左边所有连续成活的树 3. 这K棵死树右边的连续成活的树
}
int ans = winSum; // 初始化答案变量为第一个固定滑窗的结果
for (int right = K, left = 0; right < M; right++, left++) {
int idx = trees[right]; // 对于每一个右指针right所指的元素idx,将idx右边的连续成活胡杨,加入窗口和win_sum中
winSum += treeRight[idx];
int idxLeft = trees[left]; // 对于left所指的元素idxLeft,将idxLeft左边的连续成活胡杨,从窗口和win_sum中减出
winSum -= treeLeft[idxLeft];
ans = max(ans, winSum); // 更新ans,取ans和win_sum中的较大值进行更新
}
cout << ans << endl;
return 0;
}
时空复杂度
时间复杂度:O(``M``)
。构建哈希表和滑窗过程,均需要遍历M
棵未成活胡杨树。
空间复杂度:O(``M``)
。两个哈希表所占空间。
华为od机考2025A卷真题 -补种未成活胡杨的更多相关文章
- 华为OD机试题
"""最长回文字符串问题"""# 说明:方法很多,这个是最简单,也是最容易理解的一个,利用了动态规化.# 先确定回文串的右边界i,然后以右边 ...
- 2020年最新阿里、字节、腾讯、京东等一线大厂高频面试(Android岗)真题合集,面试轻松无压力
本文涵盖了阿里巴巴.腾讯.字节跳动.京东.华为等大厂的Android面试真题,不管你是要面试大厂还是普通的互联网公司,这些面试题对你肯定是有帮助的,毕竟大厂一定是行发展的标杆,很多公司的面试官同样会研 ...
- 软考之信息安全工程师(包含2016-2018历年真题详解+官方指定教程+VIP视频教程)
软考-中级信息安全工程师2016-2018历年考试真题以及详细答案,同时含有信息安全工程师官方指定清华版教程.信息安全工程师高清视频教程.持续更新后续年份的资料.请点赞!!请点赞!!!绝对全部货真价实 ...
- PMP全真模拟题真题試題含答案解析 2019年下半年PMP考試适用 PMP中文文对照试题 【香港台灣地區PMP考試也可用】
PMP全真模拟题真题试题 含答案解析 2019年下半年PMP考试适用 PMP中文文对照试题 [香港台灣地區PMP考試也可用]PMP全真模擬題真題試題 含答案解析 2019年下半年PMP考試适用 PMP ...
- 肖sir__ 代码题 ___华为od练习
www.online1987.com 这个网站,有概率看到机考原题,后续内招,这个网站做到了原题
- 数百道BAT等大厂最新Python面试真题,学到你手软!
春招临近,无论是要找工作的准毕业生,还是身在职场想要提升自己的程序员,提升自己的算法内功心法.提升 Python 编程能力,总是大有裨益的.今天,小编发现了一份好资源:Python 实现的面试题集锦! ...
- 华为OD两轮技术面试
华为OD面试1性格测试选积极向上的选项,注意,性格测试也会挂人,我一个朋友性格测试就没过.2机试 一道变成题目 1h 用例60%通过即可任给一个数组,元素有20M,1T,300G之类的,其中1T=10 ...
- 秋招如何抱佛脚?2022最新大厂Java面试真题合集(附答案
2022秋招眼看着就要来了,但是离谱的是,很多同学最近才想起来还有秋招这回事,所以纷纷临时抱佛脚,问我有没有什么快速磨枪的方法, 我的回答是:有! 说起来,临阵磨枪没有比背八股文更靠谱的了,很多人对这 ...
- 你只是看起来很努力(只是做了一遍真题,草草的对了一遍答案,然后冲出自习室继续她学生会的事情了,骗自己更容易)good——想起了自己在六大时候的无奈
(转)你只是看起来很努力一次上课,一个女孩子垂头丧气的跟我说,老师,我考了四次四级,还没过,究竟是为什么. 我说,你真题做了吗?单词背了吗?她拿出已经翻破了的真题,跟我说,你讲的所有的题目我连答案都记 ...
- 第四届蓝桥杯 c/c++真题
第四届蓝桥杯 c/c++真题 <1>高斯日记 问题 大数学家高斯有个好习惯:无论如何都要记日记. 他的日记有个与众不同的地方,他从不注明年月日,而是用一个整数代替,比如:4210 后来人们 ...
随机推荐
- ORACLE存储过程,函数,包,游标
1. PL/SQL语句块PL/SQL语句块只适用于Oracle数据库,使用时临时保存在客户端,而不是保存在数据库.基本语法: declare 变量声明.初始化 begin 业务处理.逻辑代码 exc ...
- 创建多线程的方式二:实现Runnable接口
/** * 创建多线程的方式二:实现Runnable接口 * 1. 创建一个实现了Runnable接口的类 * 2. 实现类去实现Runnable中的抽象方法:run() * 3. 创建实现类的对 ...
- # Vue3.5常用特性整理
Vue3.5 发布已近半年,抽空整理下常用的新增/改动特性 响应式 Props 解构 Vue3.5 中 Props 正式支持解构了,并添加了响应式跟踪 设置默认值 使用 JavaScript 原生的默 ...
- 深入探讨数据库索引类型:B-tree、Hash、GIN与GiST的对比与应用
title: 深入探讨数据库索引类型:B-tree.Hash.GIN与GiST的对比与应用 date: 2025/1/26 updated: 2025/1/26 author: cmdragon ex ...
- 容器、容器云和容器化PaaS平台之间到底是什么关系?
本文分享自天翼云开发者社区<容器.容器云和容器化PaaS平台之间到底是什么关系?>,作者:s****n 一直都有很多人迷惑于容器应该属于 IaaS 或是 PaaS 层,也搞不清楚容器云到底 ...
- 初探ASP.NET Core 3.x (2) - ASP.NET Core与ASP.NET前世今生
本文地址:https://www.cnblogs.com/oberon-zjt0806/p/12210662.html 注意 本节是历史课,且绝大多数内容来自于百科或者其他的什么资料来源,如果不感兴趣 ...
- P4774 [NOI2018] 屠龙勇士 题解
传送门 题解 思路 由题目可知,一条龙被攻击 \(x\) 次并回复若干次后生命值恰好为 \(0\) 则死亡,可以得出如下式子: \[\large ATK_i \cdot x \equiv a_i(\m ...
- flutter-修改Android包名
- 解密prompt系列49. 回顾R1之前的思维链发展路线
在所有人都在谈论R1的今天,作为算法也是有些千头万绪无从抓起.所以这一章先复盘,我先按照自己的思路来梳理下R1之前整个模型思维链的发展过程.下一章再展望主要去看RL在Agent上的一些尝试,毕竟Age ...
- Clean WeChat X 微信垃圾清理工具,提升硬盘空间
Clean WeChat X是一款专业的微信清理工具,其拥有软件轻巧.干净.高效.免费等等特点,其能识别你电脑里的微信缓存.聊天记录.文件备份.小程序等等信息,方便大家选择性的清理文件. 多账号登录: ...