【Py/Java/C++三种语言OD独家2024E卷真题】20天拿下华为OD笔试之【模拟】2024E-一种字符串压缩表示的解压【欧弟算法】全网注释最详细分类最全的华为OD真题题解

可上 欧弟OJ系统 练习华子OD、大厂真题
绿色聊天软件戳 od1441了解算法冲刺训练(备注【CSDN】否则不通过)

相关推荐阅读

从2024年8月14号开始,OD机考全部配置为2024E卷
注意几个关键点:

  1. 大部分的题目仍为往期2023A+B+C以及2024D的旧题。注意万变不离其宗,把方法掌握,无论遇到什么题目都可以轻松应对。
  2. 支持多次提交题目,以最后一次提交为准。可以先做200的再做100的,然后可以反复提交。
  3. E卷仍然为单机位+屏幕监控的形式进行监考。
  4. 进入考试界面新加入了这样一段话并且用红字标出,可以看出华子对作弊代考等行为是0容忍的,请各位同学认真学习,不要妄图通过其他违规途径通过考试。
    在这里插入图片描述

在这里插入图片描述

题目描述与示例

题目描述

有一种简易压缩算法: 针对全部由小写英文字母组成的字符串,将其中连续超过两个相同字母的部分压缩为连续个数加该字母,其他部分保持原样不变

例如: 字符串"aaabbccccd"经过压缩成为字符串"3abb4cd"

请您编写解压函数,根据输入的字符串,判断其是否为合法压缩过的字符串若输入合法则输出解压缩后的字符串,否则输出字符串"!error"来报告错误。

输入描述

输入一行,为一个ASCII字符串,长度不会超过100字符,用例保证输出的字符串长度也不会超过100字符。

输出描述

若判断输入为合法的经过压缩后的字符串,则输出压缩前的字符串若输入不合法,则输出字符串"!error"

示例一

输入

4dff

输出

ddddff

示例二

输入

2dff

输出

!error

说明

两个d不需要压缩,故输入不合法。

示例三

输入

4d@A

输出

!error

说明

全部由小写英文字母组成的字符串压缩后不会出现特殊字符@和大写字母A,故输入不合法

解题思路

题意理解

本题的难点在于,关于异常的表示并没有在题意中讲解的非常清楚

实际上,以下情况都应该属于异常

  • 情况一:出现非数字和非小写字符,比如示例三的4d@A中出现了@A
  • 情况二:数字出现了单个的012的情况,譬如示例二的2dff,或者例子0x1y
  • 情况三:出现了连续3个或3个以上的字符,没有在原字符串中被压缩,比如例子3dfff3dd
  • 情况四:原本应该被压缩为在一起的字符并没有压缩在一起,比如例子a4a3b4bcc5c等等
  • 情况五:对于数字而言,出现了先导0,比如例子04aa05b等等
  • 情况六:原字符串的末尾出现了数字

分类讨论

首先可以先设置一个标记isError来表示遍历过程中是否出现异常。

一旦出现异常则将isError修改为True,且直接退出循环即可。

另外,我们还需要一个数字num来记录遍历过程中得到的数字,以及一个字符串ans来储存解压后的结果。

情况六是最容易判断的,我们可以在遍历结束后判断num是否为0,或者s的最后一个字符s[-1]是否为数字就可以判断出来。

# 在最末尾写上该判断
# 或者判断s[-1].isdigit()
if num != 0:
    isError = True

在理清楚题意之后,剩余内容就是进行分类讨论了。遍历字符串过程中,最容易分类的一共是3种情况。

  • ch为小写字符
  • ch为数字
  • ch为其他无关字符

当ch为无关字符

显然当ch为其他无关字符时,对应异常情况一。因此可以构建出整体代码框架为

for i, ch in enumerate(s):
    # 如果ch是小写字符
    if ch.islower():
        pass
    # 如果ch是数字
    elif ch.isdigit():
        pass
    # 情况一:
    # 遇到非小写字符、非数字,出现异常
    # 修改isError标记为True,表示出现异常,直接退出循环
    else:
        isError = True
        break

紧接着我们需要分别讨论ch为小写字符和ch为数字的情况。

当ch为数字

ch为数字的时候,有可能发生在此处的错误是情况五,出现先导0

这在代码上也是非常好处理的,先导0需要满足以下条件:

  1. 该数字是0
  2. 该数字前面是一个非数字或前面没有字符
  3. 该数字后面是一个数字

由于在本题中,单独的0也不能作为压缩后的数字出现(情况二)。

因此我们在判断先导0的过程中无需判断该数字后面是否是一个数字。

一旦出现某个数字0其前面的字符是一个非数字或者没有字符,则直接说明出现异常。对应的代码为

for i, ch in enumerate(s):
    # 如果ch是小写字符
    if ch.islower():
        pass
    # 如果ch是数字
    elif ch.isdigit():
        if len(ans) > 0 and ans[-1] == ch:
            isError = True
            break
        pass
    # 如果ch是其他无关字符
    else:
         pass

同时,ch如果只是一个普通的数字,那么需要将ch记录在num中,因为此时我们还不确定这个数字的位数。

对应的代码为

for i, ch in enumerate(s):
    # 如果ch是小写字符
    if ch.islower():
        pass
    # 如果ch是数字
    elif ch.isdigit():
        if len(ans) > 0 and ans[-1] == ch:
            isError = True
            break
        num = num * 10 + int(ch)
    # 如果ch是其他无关字符
    else:
         pass

当ch为字母

ch为字母时,这种情况最为复杂,包含了3种异常情况。

获得一个字母的时候,其前一个字符的情况只可能是两种:

  • 前一个字符是数字
  • 前一个字符是字母

如果前一个字符是字母,则说明这个连续的字母必须是以不超过连续两个的情况出现的。

此时对应的num的值是初始化值0,我们需要判断情况三是否出现。

如果通过了情况三的检查,则这个字符ch可以加入到ans中。

故对应的代码为

for i, ch in enumerate(s):
    # 如果ch是小写字符
    if ch.islower():
        if num == 0:
            if len(ans) >= 2 and ans[-1] == ch and ans[-2] == ch:
                isError = True
                break
            else:
                ans += ch
        pass
    # 如果ch是数字
    elif ch.isdigit():
        pass
    # 如果ch是其他无关字符
    else:
         pass

如果前一个字符是数字,那么还需要继续分类讨论。

如果这个数字小于等于2,则说明情况二出现。

for i, ch in enumerate(s):
    # 如果ch是小写字符
    if ch.islower():
        if num == 0:
            pass
        elif num <= 2:
            isError = True
            break
        else:
            pass
    # 如果ch是数字
    elif ch.isdigit():
        pass
    # 如果ch是其他无关字符
    else:
         pass

如果这个数字大于2,则可以进行解压操作。但同时还需要排除情况四的出现。

只有当所有的异常情况都没有出现时,才可以进行解压操作,并且将解压结果加入到ans末尾。

对应代码为

for i, ch in enumerate(s):
    # 如果ch是小写字符
    if ch.islower():
        if num == 0:
            pass
        elif num <= 2:
            pass
        else:
            if len(ans) > 0 and ans[-1] == ch:
                isError = True
                break
            ans += ch * num
            num = 0
    # 如果ch是数字
    elif ch.isdigit():
        pass
    # 如果ch是其他无关字符
    else:
         pass

把上述繁冗的分类讨论组织在一起,就构成了最终的代码。

代码

python

# 题目:2024E-一种字符串压缩表示的解压
# 分值:100
# 作者:许老师-闭着眼睛学数理化
# 算法:模拟
# 代码看不懂的地方,请直接在群上提问


s = input()
ans = str()
# 用于储存解压数字的变量num,初始化为0
num = 0
# 标记是否出现异常的变量
isError = False

# 遍历原字符串s中的所有字符
for i, ch in enumerate(s):
    # 如果ch是小写字符
    if ch.islower():
        # 如果num为0,说明ch无需解压,直接往ans后面延长ch即可
        if num == 0:
            # 情况三:
            # 在往ans后面延长之前,
            # 需要判断ans的倒数两个字符是否是ch
            # 如果出现连续三个字符相等,
            # 则此处的ch不应该作为单个字符出现
            # 即ch对应的num不应该是0,而应该是一个不小于3的数
            # 出现这种情况,说明原字符串的压缩不合理
            if len(ans) >= 2 and ans[-1] == ch and ans[-2] == ch:
                isError = True
                break
            else:
                ans += ch
        # 情况二:
        # 如果num为小于等于2的数字(即1或2),
        # 这是不合法的操作,因为压缩的重复字符数目至少为3
        # 修改isError标记为True,表示出现异常,直接退出循环
        elif num <= 2:
            isError = True
            break
        # 如果num为≥ 3 的数字,说明字符ch需要解压,
        # 将ch重复num次后,加入在ans后面
        # 需要注意,使用完num后要将其重置为0
        else:
            # 情况四:
            # 如果此时答案中最后一个元素和当前解压元素相等
            # 说明原本应该被压缩为在一起的字符并没有压缩在一起
            # 修改isError标记为True,表示出现异常,直接退出循环
            if len(ans) > 0 and ans[-1] == ch:
                isError = True
                break
            ans += ch * num
            num = 0
    # 如果遇到数字
    elif ch.isdigit():
        # 情况五:
        # 首先判断先导0的情况
        # 若ch是0且其前一个元素是非数字或者该0是s的第一个字符
        # 则说明这个0是一个先导0(包括数字0在内)
        # 修改isError标记为True,表示出现异常,直接退出循环
        if int(ch) == 0:
            if (i > 0 and not s[i-1].isdigit()) or i == 0:
                isError = True
                break
        # 如果不是先导0,则需要将num扩大10倍后加上int(ch)
        # 用于解决遇到数字位数大于1的情况
        num = num * 10 + int(ch)
    # 情况一:
    # 遇到非小写字符、非数字,出现异常
    # 修改isError标记为True,表示出现异常,直接退出循环
    else:
        isError = True
        break

# 情况六:
# 如果退出循环时,num不为0,说明原字符串s的末尾是数字,属于不合法输入
# 此处用s[-1].isdigit()进行判断也可以
# 修改isError标记为True,表示出现异常,直接退出循环
if num != 0:
    isError = True

# 如果出现异常,则输出"!error"
# 否则输出解压后的字符串ans
print("!error") if isError else print(ans)

java

import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String s = scanner.nextLine();
        StringBuilder ans = new StringBuilder();
        // 用于储存解压数字的变量num,初始化为0
        int num = 0;
        // 标记是否出现异常的变量
        boolean isError = false;

        // 遍历原字符串s中的所有字符
        for (int i = 0; i < s.length(); i++) {
            char ch = s.charAt(i);
            // 如果ch是小写字符
            if (Character.isLowerCase(ch)) {
                // 如果num为0,说明ch无需解压,直接往ans后面延长ch即可
                if (num == 0) {
                    // 情况三:
                    // 在往ans后面延长之前,
                    // 需要判断ans的倒数两个字符是否是ch
                    // 如果出现连续三个字符相等,
                    // 则此处的ch不应该作为单个字符出现
                    // 即ch对应的num不应该是0,而应该是一个不小于3的数
                    // 出现这种情况,说明原字符串的压缩不合理
                    if (ans.length() >= 2 && ans.charAt(ans.length() - 1) == ch && ans.charAt(ans.length() - 2) == ch) {
                        isError = true;
                        break;
                    } else {
                        ans.append(ch);
                    }
                }
                // 情况二:
                // 如果num为小于等于2的数字(即1或2),
                // 这是不合法的操作,因为压缩的重复字符数目至少为3
                // 修改isError标记为True,表示出现异常,直接退出循环
                else if (num <= 2) {
                    isError = true;
                    break;
                }
                // 如果num为≥ 3 的数字,说明字符ch需要解压,
                // 将ch重复num次后,加入在ans后面
                // 需要注意,使用完num后要将其重置为0
                else {
                    // 情况四:
                    // 如果此时答案中最后一个元素和当前解压元素相等
                    // 说明原本应该被压缩为在一起的字符并没有压缩在一起
                    // 修改isError标记为True,表示出现异常,直接退出循环
                    if (ans.length() > 0 && ans.charAt(ans.length() - 1) == ch) {
                        isError = true;
                        break;
                    }
                    for (int j = 0; j < num; j++) {
                        ans.append(ch);
                    }
                    num = 0;
                }
            }
            // 如果遇到数字
            else if (Character.isDigit(ch)) {
                // 情况五:
                // 首先判断先导0的情况
                // 若ch是0且其前一个元素是非数字或者该0是s的第一个字符
                // 则说明这个0是一个先导0(包括数字0在内)
                // 修改isError标记为True,表示出现异常,直接退出循环
                if (ch == '0') {
                    if ((i > 0 && !Character.isDigit(s.charAt(i - 1))) || i == 0) {
                        isError = true;
                        break;
                    }
                }
                // 如果不是先导0,则需要将num扩大10倍后加上int(ch)
                // 用于解决遇到数字位数大于1的情况
                num = num * 10 + (ch - '0');
            }
            // 情况一:
            // 遇到非小写字符、非数字,出现异常
            // 修改isError标记为True,表示出现异常,直接退出循环
            else {
                isError = true;
                break;
            }
        }

        // 情况六:
        // 如果退出循环时,num不为0,说明原字符串s的末尾是数字,属于不合法输入
        // 此处用s.charAt(s.length() - 1)进行判断也可以
        // 修改isError标记为True,表示出现异常,直接退出循环
        if (num != 0) {
            isError = true;
        }

        // 如果出现异常,则输出"!error"
        // 否则输出解压后的字符串ans
        if (isError) {
            System.out.println("!error");
        } else {
            System.out.println(ans.toString());
        }

        scanner.close();
    }
}

cpp

#include <iostream>
#include <string>

using namespace std;

int main() {
    string s;
    cin >> s;
    string ans = "";
    // 用于储存解压数字的变量num,初始化为0
    int num = 0;
    // 标记是否出现异常的变量
    bool isError = false;

    // 遍历原字符串s中的所有字符
    for (int i = 0; i < s.length(); i++) {
        char ch = s[i];

        // 如果ch是小写字符
        if (islower(ch)) {
            // 如果num为0,说明ch无需解压,直接往ans后面延长ch即可
            if (num == 0) {
                // 情况三:
                // 在往ans后面延长之前,
                // 需要判断ans的倒数两个字符是否是ch
                // 如果出现连续三个字符相等,
                // 则此处的ch不应该作为单个字符出现
                // 即ch对应的num不应该是0,而应该是一个不小于3的数
                // 出现这种情况,说明原字符串的压缩不合理
                if (ans.length() >= 2 && ans[ans.length() - 1] == ch && ans[ans.length() - 2] == ch) {
                    isError = true;
                    break;
                } else {
                    ans += ch;
                }
            }
            // 情况二:
            // 如果num为小于等于2的数字(即1或2),
            // 这是不合法的操作,因为压缩的重复字符数目至少为3
            // 修改isError标记为True,表示出现异常,直接退出循环
            else if (num <= 2) {
                isError = true;
                break;
            }
            // 如果num为≥ 3 的数字,说明字符ch需要解压,
            // 将ch重复num次后,加入在ans后面
            // 需要注意,使用完num后要将其重置为0
            else {
                // 情况四:
                // 如果此时答案中最后一个元素和当前解压元素相等
                // 说明原本应该被压缩为在一起的字符并没有压缩在一起
                // 修改isError标记为True,表示出现异常,直接退出循环
                if (!ans.empty() && ans.back() == ch) {
                    isError = true;
                    break;
                }
                ans.append(num, ch);
                num = 0;
            }
        }
        // 如果遇到数字
        else if (isdigit(ch)) {
            // 情况五:
            // 首先判断先导0的情况
            // 若ch是0且其前一个元素是非数字或者该0是s的第一个字符
            // 则说明这个0是一个先导0(包括数字0在内)
            // 修改isError标记为True,表示出现异常,直接退出循环
            if (ch == '0') {
                if ((i > 0 && !isdigit(s[i - 1])) || i == 0) {
                    isError = true;
                    break;
                }
            }
            // 如果不是先导0,则需要将num扩大10倍后加上int(ch)
            // 用于解决遇到数字位数大于1的情况
            num = num * 10 + (ch - '0');
        }
        // 情况一:
        // 遇到非小写字符、非数字,出现异常
        // 修改isError标记为True,表示出现异常,直接退出循环
        else {
            isError = true;
            break;
        }
    }

    // 情况六:
    // 如果退出循环时,num不为0,说明原字符串s的末尾是数字,属于不合法输入
    // 此处用isdigit(s.back())进行判断也可以
    // 修改isError标记为True,表示出现异常
    if (num != 0) {
        isError = true;
    }

    // 如果出现异常,则输出"!error"
    // 否则输出解压后的字符串ans
    if (isError) {
        cout << "!error" << endl;
    } else {
        cout << ans << endl;
    }

    return 0;
}

时空复杂度

时间复杂度:O(N)。需要从头到尾一次遍历原字符串s

空间复杂度:O(1)。不考虑sans,仅需若干常数变量。


华为OD算法/大厂面试高频题算法练习冲刺训练

  • 华为OD算法/大厂面试高频题算法冲刺训练目前开始常态化报名!目前已服务300+同学成功上岸!

  • 课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化

  • 每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!

  • 60+天陪伴式学习,40+直播课时,300+动画图解视频,300+LeetCode经典题,200+华为OD真题/大厂真题,还有简历修改、模拟面试、专属HR对接将为你解锁

  • 可上全网独家的欧弟OJ系统练习华子OD、大厂真题

  • 可查看链接 大厂真题汇总 & OD真题汇总(持续更新)

  • 绿色聊天软件戳 od1336了解更多

  • 14
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值