华为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 后来人们 ...
随机推荐
- Java线程池实现原理与源码解析(jdk1.8)
为什么需要线程池?线程池能够对线程进行统一分配,调优和监控:- 降低资源消耗(线程无限制地创建,然后使用完毕后销毁)- 提高响应速度(无须创建线程)- 提高线程的可管理性 Java是如何实现和管理线程 ...
- Mac使用经验汇总
在此记录使用Mac的一些经验技巧. 安装brew 如果按照官网提示安装,巨慢无比,如下: /bin/bash -c "$(curl -fsSL https://raw.githubuserc ...
- linux:权限管理
权限概述 linux一般讲文件可存 / 取 访问的身份分为3个类别:owner.group.others,且3种身份各有 read.write.execute等权限 权限介绍 在多用户计算机系统中,权 ...
- 搭建个人AI知识库-DIFY
前提 本地目前没有显卡,只能用cpu刚. 如果不想自己搭建本地模型,完全可以掏钱使用现成的API即可. 需要了解一些docker知识 搭建本地模型 环境 os: archlinux 内存: 32g c ...
- C# LinkedList 删除元素
开发中经常有任务队列的设计,主要用于存储待执行的任务.由于任务来源的多样性,因此有时候需要一颗后悔药,将队列中某些待执行任务删除.此时使用LinkedList比较合适. public LinkedLi ...
- ubuntu通过tar包安装mysql5.7.21
作者:zuoguohui 一.场景:最近想搞mysql主从复制,需要在两台服务器上安装mysql,之前有一台已经装好了mysql5.7.21,于是在另外一台上也装mysql5.7.21,安装过程中碰到 ...
- Q:Win10关闭内存压缩功能
微软在Win10中就已经启用了内存压缩机制,在Win11当中继续了这一设定. 通过任务管理器查看. taskmgr ·通过命令行查看. 使用系统管理员权限,打开PowerShell,然后输入以下命令: ...
- js 字符串“http%3A%2F%2F”转换成http://详解
字符串"http%3A%2F%2F"转换成http://详解 我截获下来一个URL的字符串格式是"http%3A%2F%2F",但是实际应该是http:// 而 ...
- MySQL - [02] 常用SQL
题记部分 一.连接MySQL服务器 1.常规连接方式 # 连接本地mysql服务器 mysql -u 用户名 -p # 连接到指定mysql服务器,回车执行该命令之后需要输入密码 mysql -h 主 ...
- SpringBoot - [00] 注解大全
原文链接:https://mp.weixin.qq.com/s/DgNhohtJyEq4vMGEzqrP8A @SpringBootApplication 这个注解用于标识一个SpringBoot应用 ...