华为OD笔试机试 - 攀登者2 (python/c++/java 2024年C卷D卷真题算法)

华为OD机试(C卷+D卷)2024真题目录(Java & c++ & python)

题目描述

攀登者喜欢寻找各种地图,并且尝试攀登到最高的山峰。

地图表示为一维数组,数组的索引代表水平位置,数组的元素代表相对海拔高度。其中数组元素0代表地面。

例如:[0,1,2,4,3,1,0,0,1,2,3,1,2,1,0],代表如下图所示的地图,地图中有两个山脉位置分别为 1,2,3,4,5 和 8,9,10,11,12,13,最高峰高度分别为 4,3。最高峰位置分别为3,10。

一个山脉可能有多座山峰(高度大于相邻位置的高度,或在地图边界且高度大于相邻的高度)。
在这里插入图片描述

登山时会消耗登山者的体力(整数),

  • 上山时,消耗相邻高度差两倍的体力
  • 下山时,消耗相邻高度差一倍的体力
  • 平地不消耗体力

登山者体力消耗到零时会有生命危险。

例如,上图所示的山峰:

  • 从索引0,走到索引1,高度差为1,需要消耗 2 * 1 = 2 的体力,
  • 从索引2,走到索引3,高度差为2,需要消耗 2 * 2 = 4 的体力。
  • 从索引3,走到索引4,高度差为1,需要消耗 1 * 1 = 1 的体力。
    攀登者想要评估一张地图内有多少座山峰可以进行攀登,且可以安全返回到地面,且无生命危险。

例如上图中的数组,有3个不同的山峰,登上位置在3的山可以从位置0或者位置6开始,从位置0登到山顶需要消耗体力 1 * 2 + 1 * 2 + 2 * 2 = 8,从山顶返回到地面0需要消耗体力 2 * 1 + 1 * 1 + 1 * 1 = 4 的体力,按照登山路线 0 → 3 → 0 需要消耗体力12。攀登者至少需要12以上的体力(大于12)才能安全返回。

输入描述

第一行输入为地图一维数组

第二行输入为攀登者的体力

输出描述

确保可以安全返回地面,且无生命危险的情况下,地图中有多少山峰可以攀登。

用例1

输入

0,1,4,3,1,0,0,1,2,3,1,2,1,0
13

输出

3

说明 登山者只能登上位置10和12的山峰,7 → 10 → 7,14 → 12 → 14

用例2

输入

1,4,3
999

输出

0

说明 没有合适的起点和终点

用例解析

用例说明中用的位置应该是从1开始计数的,换成数组索引的话,如下:

登山者只能登上位置9和11的山峰,6 → 9 → 6,13 → 11 → 13

登上索引9山峰的路线:6 → 9 → 6,消耗体力 = 9 < 13

登上索引11山峰的路线:13 → 11 → 13,消耗体力 = 6 < 13

另外还有一个索引2的山峰,有两条路线登山,分别为:

0 → 2 → 0,消耗体力 = 12 < 13

5 → 2 → 5,消耗体力 = 12 < 13

也能满足安全登山下山,但是题目用例说明没有给出,而题目输出是给的可攀登山峰数3,即索引位置2,9,11的三座山峰。

解题思路

首先找到正向第一个地面(高度0)位置,然后从此位置开始攀登:

定义两个变量 upCost,downCost,分别代表上山体力消耗,下山体力消耗。
初始时都为0

假设索引 i 执行下一个位置,那么 height[i] - height[i-1] 就是高度差diff:

  • 如果diff > 0,那么当前处于上坡路程,此时upCost += diff * 2,downCost += diff

上山时如果遇到上坡,那么相对应的,下山时,这段路程就变成了下坡

  • 如果diff < 0,那么当前处于下坡路程,此时upCost -= diff,downCost -= diff * 2

上山时如果遇到下坡,

PS:为什么上山会遇到下坡路程?可以看下图,如果我想从索引6地面攀登到索引11山峰,那么中间必然要经过更高的索引9山峰,产生下坡路程

在这里插入图片描述

上山时如果遇到下坡,那么相对应的,下山时,这段路程就变成了上坡路。

注意:上面diff高度差 < 0,因此 upCost += -diff,就变为了 upCost -= diff。downCost同理。

  • 如果diff == 0,那么当前不消耗体力
    上面 diff = heights[i] - heights[i - 1],如果 diff < 0 以及 diff == 0时,说明位置 i 的山不是山顶。

而 diff > 0 时,位置 i 是有可能为山顶的,我们只需要检查 heights[i] > heights[i+1]即可,比如

在这里插入图片描述
如果确定位置 i 是山顶,则攀登此山顶的体力消耗(上山+下山)= upCost + downCost

如果此时 upCost + downCost <= 自身体力(第二行输入),那么位置 i 山顶可攀登。

另外,我们在不断向后攀登的过程中,可能会重回地面(height[i] == 0),入下图绿色框部分
在这里插入图片描述
那么此时,对于后面的山峰来说,我们更优的攀登起始位置是绿色部分,而不是一开始的地面位置,因此,此时,我们应该将 upCost、downCost 重置为0,相当于从绿色部分重新攀登后面的山。

以上就是求解思路。但是存在漏洞,比如下图,我们攀登索引11的山峰,如果只正向考虑的话,则最优路径是 6 → 11 → 6
在这里插入图片描述
但是逆向考虑的话,会有更优解
在这里插入图片描述
即路径为 13 → 11 → 13

因此,我们应该正向、逆向都爬一次,这样才能保证所有山峰都能找到最优路径

并且upCost 和 downCost 可以进行合并,具体请见代码注释。

攀登者体力必须要大于 上山、下山体力消耗之和,而不是大于等于。

C++、Java、Python代码如下:

C++参考代码

#include <bits/stdc++.h>
using namespace std;

class Solution {
public:
    int countClimbablePeaks(vector<int> &heights, int strength) {
        // 记录可攀登的山峰索引集合
        set<int> climbablePeaks;

        // 正向攀登
        climb(heights, strength, climbablePeaks, true);

        // 逆序攀登
        reverse(heights.begin(), heights.end());
        climb(heights, strength, climbablePeaks, false);

        return climbablePeaks.size();
    }

private:
    void climb(const vector<int> &heights, int strength, set<int> &climbablePeaks, bool isForward) {
        int n = heights.size();
        int start = 0;

        // 找到第一个地面位置
        while (start < n && heights[start] != 0) {
            start++;
        }

        // 攀登时的体力消耗
        int totalCost = 0;

        // 开始攀登
        for (int i = start + 1; i < n; i++) {
            // 如果遇到新的地面位置,重置体力消耗
            if (heights[i] == 0) {
                totalCost = 0;
                continue;
            }

            // 计算高度差
            int heightDiff = heights[i] - heights[i - 1];

            if (heightDiff > 0) { // 上坡
                totalCost += heightDiff * 3;

                // 判断当前位置是否为山顶
                if (i + 1 >= n || heights[i] > heights[i + 1]) {
                    if (totalCost <= strength) {
                        if (isForward) {
                            climbablePeaks.insert(i);
                        } else {
                            climbablePeaks.insert(n - i - 1);
                        }
                    }
                }
            } else if (heightDiff < 0) { // 下坡
                totalCost -= heightDiff * 3;
            }
        }
    }
};

int main() {
    vector<int> heights;

    // 输入山峰高度数组
    string input;
    getline(cin, input);

    stringstream ss(input);
    string token;

    while (getline(ss, token, ',')) {
        heights.emplace_back(stoi(token));
    }

    int strength;
    cin >> strength;

    // 计算可攀登的山峰数量
    cout << Solution().countClimbablePeaks(heights, strength) << endl;

    return 0;
}

Java参考代码

import java.util.Arrays;
import java.util.HashSet;
import java.util.Scanner;

public class Main {
    // 主函数,用于处理输入和输出
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // 读取山峰高度数组
        int[] heights = Arrays.stream(sc.nextLine().split(",")).mapToInt(Integer::parseInt).toArray();
        // 读取体力值
        int strength = Integer.parseInt(sc.nextLine());

        // 计算并输出可攀登的山峰数量
        System.out.println(getClimbablePeaks(heights, strength));
    }

    // 核心算法,计算可攀登的山峰数量
    public static int getClimbablePeaks(int[] heights, int strength) {
        // 记录可攀登的山峰索引
        HashSet<Integer> climbablePeaks = new HashSet<>();

        // 正向攀登
        climb(heights, strength, climbablePeaks, true);

        // 逆序攀登
        reverse(heights);
        climb(heights, strength, climbablePeaks, false);

        // 返回可攀登的山峰数量
        return climbablePeaks.size();
    }

    // 执行攀登操作,标记可攀登的山峰
    public static void climb(int[] heights, int strength, HashSet<Integer> climbablePeaks, boolean isForward) {
        // 找到第一个地面位置(高度为0)
        int firstGroundIdx = 0;
        while (firstGroundIdx < heights.length && heights[firstGroundIdx] != 0) {
            firstGroundIdx++;
        }

        // 初始化攀登体力消耗
        int totalCost = 0;

        // 从地面位置开始攀登
        for (int i = firstGroundIdx + 1; i < heights.length; i++) {
            // 如果遇到新的地面位置,重置体力消耗
            if (heights[i] == 0) {
                totalCost = 0;
                continue;
            }

            // 计算当前高度差
            int heightDiff = heights[i] - heights[i - 1];

            if (heightDiff > 0) { // 上坡
                totalCost += heightDiff * 3;

                // 检查当前位置是否为山顶
                if (i + 1 >= heights.length || heights[i] > heights[i + 1]) {
                    // 判断体力是否足够攀登此山顶
                    if (totalCost <= strength) {
                        if (isForward) {
                            climbablePeaks.add(i);
                        } else {
                            climbablePeaks.add(heights.length - i - 1); // 逆序时需要转换索引
                        }
                    }
                }
            } else if (heightDiff < 0) { // 下坡
                totalCost -= heightDiff * 3;
            }
        }
    }

    // 数组逆序
    public static void reverse(int[] nums) {
        int left = 0;
        int right = nums.length - 1;

        while (left < right) {
            int temp = nums[left];
            nums[left] = nums[right];
            nums[right] = temp;
            left++;
            right--;
        }
    }
}

Python参考代码

# 输入获取
heights = list(map(int, input().split(",")))
strength = int(input())

def climb(idxs, direction):
    # 找到第一个地面位置
    j = 0
    while j < len(heights) and heights[j] != 0:
        j += 1

    # 攀登体力总消耗(包括上山,下山)
    cost = 0

    for i in range(j + 1, len(heights)):
        # 如果遇到了新的地面,则从新的地面位置重新计算攀登消耗的体力
        if heights[i] == 0:
            cost = 0
            continue

        # 记录高度差
        diff = heights[i] - heights[i - 1]

        if diff > 0:
            # 上坡
            cost += diff * 3

            # 如果位置 i 是山顶
            if i + 1 >= len(heights) or heights[i] > heights[i + 1]:
                # 判断体力是否足够
                if cost < strength:
                    if direction:
                        idxs.add(i)
                    else:
                        idxs.add(len(heights) - i - 1)

        elif diff < 0:
            # 下坡
            cost -= diff * 3

# 记录可攀登的山峰索引
idxs = set()

# 正向攀登
climb(idxs, True)

# 逆序攀登
heights.reverse()
climb(idxs, False)

# 输出可攀登的山峰数量
print(len(idxs))

  • 8
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
华为OD机试 - 攀登者1”是华为公司的一场在线岗位技术面试。在这场机试中,面试者扮演的角色是“攀登者1”,需要展示自己在软件开发和技术问题解决方面的能力。 首先,作为攀登者1,必须具备扎实的编程技能。这包括熟练掌握至少一种主流编程语言,并能够在给定的时间内编写出有效的代码。此外,对常见的数据结构算法也应有一定的了解,以便能够在解决问题时选择最优解决方案。 其次,攀登者1需要展示出对软件开发流程的理解。这包括了解需求分析、设计、编码和测试等一系列开发过程,并能够合理地组织和安排自己的开发任务。同时,对于代码质量的重视也是非常重要的,攀登者1需要注意代码的可读性、可维护性和扩展性等方面。 此外,在面试过程中,攀登者1需要展示自己在解决技术问题时的思考能力和解决能力。这意味着在面对复杂的技术问题时能够有条不紊地分析问题、确定解决方案,并能够迅速实施和测试解决方案。同时,攀登者1需要具备快速学习和适应新技术的能力,因为技术的更新换代很快,对新技术的学习和掌握能力是面试中的重要评价指标之一。 总结起来,“华为OD机试 - 攀登者1”是一场测试面试者在软件开发和技术问题解决方面能力的机试。面试者需要展示自己的编程技能、软件开发流程理解、问题解决能力和学习能力等方面的能力。只有在这些方面能够表现出色,攀登者1才能成功地通过这场机试
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

算法之旅

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

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

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

打赏作者

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

抵扣说明:

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

余额充值