青训营豆包第六课 | 豆包MarsCode AI 刷题

51 阅读10分钟

小C 需要对一个字符串进行特殊排序,这个字符串只包含三种字符类型:字母(大小写)、数字和问号。要求你按照以下规则进行排序:

  1. 问号的原始位置必须保持不变。
  2. 数字的原始位置必须保持不变,但数字要按照从大到小排序。
  3. 字母的原始位置必须保持不变,但字母要按照字典序从小到大排序。

你需要编写一个程序,帮助小C实现这个字符串的排序功能。

def solution(inp):
    # 分离字符类型
    letters = [c for c in inp if c.isalpha()]
    digits = [c for c in inp if c.isdigit()]
    question_marks = [i for i, c in enumerate(inp) if c == '?']
    
    # 对字母和数字进行排序
    letters.sort()
    digits.sort(reverse=True)
    
    # 重组字符串
    result = list(inp)
    letter_index = 0
    digit_index = 0
    
    for i in range(len(result)):
        if result[i] == '?':
            continue
        elif result[i].isalpha():
            result[i] = letters[letter_index]
            letter_index += 1
        elif result[i].isdigit():
            result[i] = digits[digit_index]
            digit_index += 1
    
    return ''.join(result)


if __name__ == "__main__":
    # Add your test cases here
    print(solution("12A?zc") == "21A?cz")
    print(solution("1Ad?z?t24") == "4Ad?t?z21")
    print(solution("???123??zxy?") == "???321??xyz?")

题目分析

问题理解

题目要求对一个字符串进行特殊排序,该字符串只包含三种字符类型:字母(大小写)、数字和问号。排序规则如下:

  1. 问号的原始位置必须保持不变
  2. 数字的原始位置必须保持不变,但数字要按照从大到小排序
  3. 字母的原始位置必须保持不变,但字母要按照字典序从小到大排序

数据结构的选择

为了实现上述排序规则,我们需要选择合适的数据结构来存储和处理字符串中的不同字符类型。具体来说:

  1. 字母:我们需要一个列表来存储所有的字母,并对这个列表进行排序。
  2. 数字:我们需要一个列表来存储所有的数字,并对这个列表进行从大到小的排序。
  3. 问号:我们需要一个列表来存储所有问号在原始字符串中的位置,以便在重组字符串时保持这些位置不变。

算法步骤

  1. 分离字符类型

    • 遍历原始字符串,将字母、数字和问号分别存储到不同的列表中。
    • 对于问号,我们不仅需要存储问号本身,还需要记录它们在原始字符串中的位置。
  2. 排序

    • 对字母列表进行字典序排序。
    • 对数字列表进行从大到小的排序。
  3. 重组字符串

    • 创建一个与原始字符串长度相同的列表,用于存储重组后的字符。
    • 遍历原始字符串,根据字符类型将排序后的字母和数字插入到对应的位置,保持问号的位置不变。

详细步骤

  1. 分离字符类型

    • 使用列表推导式遍历原始字符串,将字母存储到 letters 列表中,将数字存储到 digits 列表中。
    • 使用列表推导式遍历原始字符串,记录问号的位置,存储到 question_marks 列表中。
  2. 排序

    • 使用 sort() 方法对 letters 列表进行字典序排序。
    • 使用 sort(reverse=True) 方法对 digits 列表进行从大到小的排序。
  3. 重组字符串

    • 创建一个与原始字符串长度相同的列表 result,用于存储重组后的字符。
    • 遍历 result 列表,根据字符类型将排序后的字母和数字插入到对应的位置。
    • 如果当前字符是问号,则保持其位置不变。
    • 如果当前字符是字母,则从 letters 列表中取出排序后的字母,并将其插入到当前位置。
    • 如果当前字符是数字,则从 digits 列表中取出排序后的数字,并将其插入到当前位置。

复杂度分析

  • 时间复杂度

    • 分离字符类型的时间复杂度为 O(n),其中 n 是字符串的长度。
    • 排序的时间复杂度为 O(m log m),其中 m 是字母或数字的数量。由于 m 最大为 n,所以排序的时间复杂度可以认为是 O(n log n)。
    • 重组字符串的时间复杂度为 O(n)。
    • 总体时间复杂度为 O(n log n)。
  • 空间复杂度

    • 需要额外的空间来存储字母、数字和问号的位置,空间复杂度为 O(n)。

总结

通过上述分析,我们可以看到,该问题的核心在于如何有效地分离字符类型、排序以及重组字符串。选择合适的数据结构和算法步骤是解决问题的关键。通过分离字符类型、排序和重组字符串,我们可以确保问号的位置保持不变,数字和字母按照题目要求的顺序进行排序。

代码逻辑分析

总体思路

该代码的主要目标是根据题目要求对字符串进行特殊排序。具体来说,字符串中包含字母、数字和问号三种字符类型,排序规则如下:

  1. 问号的原始位置必须保持不变
  2. 数字的原始位置必须保持不变,但数字要按照从大到小排序
  3. 字母的原始位置必须保持不变,但字母要按照字典序从小到大排序

为了实现这一目标,代码采用了以下步骤:

  1. 分离字符类型:将字符串中的字母、数字和问号分别提取出来,并记录问号在原始字符串中的位置。
  2. 排序:对提取出的字母和数字进行排序。
  3. 重组字符串:根据原始字符串中各个字符的位置,将排序后的字母和数字重新插入到对应的位置,保持问号的位置不变。

详细步骤

  1. 分离字符类型

    • 使用列表推导式遍历原始字符串,将字母存储到 letters 列表中,将数字存储到 digits 列表中。
    • 使用列表推导式遍历原始字符串,记录问号的位置,存储到 question_marks 列表中。

    这部分代码通过遍历字符串 inp,将字母和数字分别存储到 lettersdigits 列表中。同时,通过遍历字符串并记录问号的位置,将这些位置存储到 question_marks 列表中。

  2. 排序

    • letters 列表进行字典序排序。
    • digits 列表进行从大到小的排序。

    这部分代码使用 sort() 方法对 letters 列表进行字典序排序,使用 sort(reverse=True) 方法对 digits 列表进行从大到小的排序。

  3. 重组字符串

    • 创建一个与原始字符串长度相同的列表 result,用于存储重组后的字符。
    • 遍历 result 列表,根据字符类型将排序后的字母和数字插入到对应的位置,保持问号的位置不变。

    这部分代码首先创建一个与原始字符串长度相同的列表 result,用于存储重组后的字符。然后,通过遍历 result 列表,根据字符类型将排序后的字母和数字插入到对应的位置。具体来说:

    • 如果当前字符是问号,则保持其位置不变。
    • 如果当前字符是字母,则从 letters 列表中取出排序后的字母,并将其插入到当前位置。
    • 如果当前字符是数字,则从 digits 列表中取出排序后的数字,并将其插入到当前位置。
  4. 返回结果

    • result 列表转换为字符串并返回。

    这部分代码将 result 列表转换为字符串并返回,得到最终的排序结果。

代码结构

整个代码的结构如下:

  1. 函数定义:定义 solution 函数,接受一个字符串 inp 作为输入。
  2. 分离字符类型:使用列表推导式分离字母、数字和问号。
  3. 排序:对字母和数字进行排序。
  4. 重组字符串:根据原始字符串中各个字符的位置,将排序后的字母和数字重新插入到对应的位置,保持问号的位置不变。
  5. 返回结果:将重组后的字符列表转换为字符串并返回。

复杂度分析

  • 时间复杂度

    • 分离字符类型的时间复杂度为 O(n),其中 n 是字符串的长度。
    • 排序的时间复杂度为 O(m log m),其中 m 是字母或数字的数量。由于 m 最大为 n,所以排序的时间复杂度可以认为是 O(n log n)。
    • 重组字符串的时间复杂度为 O(n)。
    • 总体时间复杂度为 O(n log n)。
  • 空间复杂度

    • 需要额外的空间来存储字母、数字和问号的位置,空间复杂度为 O(n)。

总结

通过上述分析,我们可以看到,该代码的核心在于如何有效地分离字符类型、排序以及重组字符串。选择合适的数据结构和算法步骤是解决问题的关键。通过分离字符类型、排序和重组字符串,我们可以确保问号的位置保持不变,数字和字母按照题目要求的顺序进行排序。

总结和反思

总结

通过实现题目要求的字符串特殊排序功能,我们成功地解决了以下问题:

  1. 问号的原始位置必须保持不变
  2. 数字的原始位置必须保持不变,但数字要按照从大到小排序
  3. 字母的原始位置必须保持不变,但字母要按照字典序从小到大排序

代码的主要步骤包括:

  1. 分离字符类型:将字符串中的字母、数字和问号分别提取出来,并记录问号在原始字符串中的位置。
  2. 排序:对提取出的字母和数字进行排序。
  3. 重组字符串:根据原始字符串中各个字符的位置,将排序后的字母和数字重新插入到对应的位置,保持问号的位置不变。

反思

  1. 代码的可读性

    • 代码结构清晰,逻辑明确,易于理解和维护。通过注释和变量命名,可以清楚地了解每一步的操作。
  2. 性能优化

    • 当前代码的时间复杂度为 O(n log n),空间复杂度为 O(n)。对于较大的输入字符串,性能表现良好。如果输入字符串非常大,可以考虑进一步优化排序算法,例如使用更高效的排序算法或并行处理。
  3. 错误处理

    • 代码假设输入字符串只包含字母、数字和问号。如果输入包含其他字符,代码可能会出现错误。可以添加输入验证,确保输入字符串符合预期格式。
  4. 扩展性

    • 如果题目要求扩展到更多字符类型或不同的排序规则,代码需要进行相应的修改。可以通过参数化排序规则和字符类型,提高代码的灵活性和可扩展性。
  5. 测试用例

    • 代码中包含了基本的测试用例,覆盖了不同的情况。可以进一步增加测试用例,确保代码在各种边界条件下都能正确运行。

总结

通过本次编程练习,我们不仅成功实现了题目要求的字符串特殊排序功能,还对代码的可读性、性能优化、错误处理和扩展性进行了反思。这些反思有助于我们在未来的编程实践中编写更加健壮和高效的代码。