专栏名称: 吴师兄学算法
和程序员小吴一起从初学者的角度学习算法,以动画的形式呈现解题的思路。每周四篇原创文章,期待你的鉴赏!
目录
相关文章推荐
河北交通广播  ·  【992 | ... ·  16 小时前  
河北交通广播  ·  【992 | ... ·  2 天前  
Python爱好者社区  ·  刚刚,DeepSeek放出重磅论文!梁文锋亲 ... ·  3 天前  
河北交通广播  ·  【992 | 关注】低至200元!价格“大跳水”! ·  2 天前  
河北交通广播  ·  【992 | 明确】减免!名单公布→ ·  3 天前  
51好读  ›  专栏  ›  吴师兄学算法

玩转华为OD机试:2024E-荒岛求生

吴师兄学算法  · 公众号  ·  · 2024-12-11 11:30

正文

大家好,我是吴师兄。

2024 年 8 月开始,华为 OD 笔试从 2024D 卷改为2024E 卷。

E 卷里 70% 左右的题目都是以前 A/B/C/D 卷复用的旧题。如果之前的题目刷了不少,那么很大概率都能遇到原题。所以刷过往的题目也是非常有效果的。

相比起 D 卷,E 卷的题目难度平均了很多,删去了很多有争议的难题、偏题、错题。

D卷里面的:

  • 难题【最短路问题】2024D-快递员的烦恼、【回溯】2024D-田忌赛马、【最小生成树】2024D-5G网络建设

  • 偏题【模拟】2024D-学生重新排队、【模拟】2024D-移动元素获得最大数组和、【模拟】2024D-攀登者2

  • 错题【DP】2024D-抢7游戏、【贪心】2024D-小朋友来自多少小区、【双指针】2024D-提取字符串中最长数学表达式

都删掉了。

E 卷中难度为困难的题目少了很多,难度为简单的题目也少了很多,超过 50% 的题目(无论分值)都属于中等难度的题目。

这样的难度调整对认真学习、打牢基础的同学是更有利的,能够有效避免因为运气原因遇到太难或描述有问题的题目而考不到理想分数的情况。

截止目前, 我们已经直播讲解了 151 道 E 卷的题目 ,每一道题目都有详细的答题思路、视频讲解、搭配三到六种代码和逐行的中文注释、答疑,同时每周都会迭代和新增三到五题。

题目描述

有一个荒岛,只有左右两个港口,只有一座桥连接这两个港口,现在有一群人需要从两个港口逃生,有的人往右逃生,有的往左逃生,如果两个人相遇,则 pk,体力值大的能够打赢体力值小的,体力值相同则同归于尽,赢的人才能继续往前逃生,并减少相应的体力

输入描述

系列非 0 整数,用空格隔开, 正数代表向右逃生,负数代表向左逃生

输出描述

最终能够逃生的人数

示例一

输入

5 10 8 -8 -5

输出

5 5

说明

8` 与 `-8` 相遇,同归于尽,`10` 遇到`-5`,打赢并减少五点体力,最终逃生的为`[5,5]`,均从右侧港口逃生,输出 `2

示例二

输入

5 6 -10

输出

1

解题思路

注意,本题和LC735. 行星碰撞几乎完全一致。唯一的区别在于,本题出现体力不相等的情况,需要进行减法操作而不是直接的吞并操作。

题意理解以及栈模拟思路的总框架

首先需要理解题意,题目中数字的的 正负性代表的含义 。正数表示向右移动,负数表示向左移动。

以示例一为例,可以用下图来表示

从上图可以看出,上述两个最先相遇的 8 -8 进行匹配,并且决出胜负结果

这个过程实际上和LC20. 有效的括号这类括号配对问题非常类似,只不过是从括号配对换成了 向左的数字和最近的向右数字进行匹配

可以 将向右的数字类比成左括号,向左的数字类比成右括号 ,来进行数字匹配的思考。

很显然也应该考虑栈来完成本题目。整体的代码框架如下

stack = list()

# 遍历所有数字
for num in nums:
    # 处理向右的数字
    if num > 0:
        pass
    # 处理向左的数字
    else:
        pass

向右数字的处理

遇到向右的数字的时候,非常好处理, 直接入栈即可

因为遇到一个向右数字的时候, 即使栈中存在一些向左移动的人,他们是背向移动的,并不会出现匹配

譬如下图遍历到向右的 5 的时候,并不会跟前面的 -8 -5 发生匹配。

故整体的代码为

stack = list()

# 遍历所有数字
for num in  nums:
    # 当前逃生的人向右移动的情况
    if num > 0:
        # 无需考虑栈顶元素的方向或值,直接入栈即可
        stack.append(num)
    # 处理向左的数字
    else:
        pass

向左数字的处理

向左数字的处理,相对来说就比较复杂了。

如果在遍历过程中遇到一个向左的数字,那么我们可以分为以下几种情况来讨论:

  1. 栈中没有元素
  2. 栈顶元素是向左的数字
  3. 栈顶元素是向右的数字

对于前两种情况而言,也是无需发生匹配的,我们直接令这个向左的数字入栈即可。

但对于第三种情况,是需要进行数字的匹配,那么这个时候就需要进行栈顶向右数字的相关操作。

根据题意,这里可能会出现三种情况:

  1. 向左数字的绝对值 abs(num) < 向右数字 stack[-1]
  2. 向左数字的绝对值 abs(num) = 向右数字 stack[-1]
  3. 向左数字的绝对值 abs(num) > 向右数字 stack[-1]

这里提到绝对值是因为向左数字在数值上必然是一个负数,使用绝对值来讨论是更加严谨的说明。

对于情况 1 ,向左的人会倒下体力修改为 0 ,而向右的人的体力会减去 abs(num) ,故对应代码为

if abs(num) -1]:
    # 向右的人损失abs(num)点体力,但仍未倒下,无需出栈
    # 此处使用stack[-1] -= abs(num)也可以
    stack[-1] += num
    # 当前向左的人倒下,修改num为0
    num = 0

对于情况 2 ,向左的人会倒下体力修改为 0 ,而向右的人也倒下 可以直接令其出栈 ,故对应代码为

# 当前向左的人和栈顶向右的人的体力相等,则两个人均倒下
if abs(num) == stack[-1]:
    # 向右的人倒下,出栈
    stack.pop()
    # 当前向左的人倒下,修改num为0
    num = 0

对于情况 3 ,向左的人体力会减去 stack[-1] ,而向右的人倒下 可以直接令其出栈 ,故对应代码为

# 当前向左的人的体力大于栈顶向右的人
if abs(num) > stack[-1]:
    # 向右的人倒下,出栈
    right = stack.pop()
    # 当前向左的人损失right点体力
    # 注意num是个负数,right是个正数,使用加法修改num
    num += right

到这里我们发现一个有意思的问题,如果是情况 3 出现,这个向左的人的体力并没有降为 0

如果此时栈顶元素仍然是一个向右移动的人的话,仍然需要继续进行上述的判断。

因此,这里我们需要使用一个 while 循环来进行。

while 循环持续进行的条件有 3 个:

  1. 栈不为空
  2. 栈顶元素向右
  3. 当前向左的人体力没有降到 0

即对应的代码为

while len(stack) != 0 and stack[-1] > 0 and num != 0:
    pass

while 循环中,需要加上上述 3 个条件语句,来 判断当前向左数字和栈顶向右数字的匹配情况

而在 while 循环结束之后, num 仍然不一定降为 0

举个例子, num 是一个向左移动且体力极其充沛的人(绝对值很大),大杀四方赢了所有栈顶元素中所有向右移动的人,那么 num 仍然不降为 0

在这种情况下,我们必须在 while 外面再加一个判断,若 num 不为 0 则可以入栈。







请到「今天看啥」查看全文