华为OD机试_2025 B卷_判断字符串子序列(Python,100分)(附详细解题思路)

题目描述

给定字符串 target和 source,判断 target是否为 source 的子序列。

你可以认为target和 source 中仅包含英文小写字母。

字符串 source 可能会很长(长度~=500,000),而 target是个短字符串(长度<=100)。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。

(例如,”abc”是”aebycd”的一个子序列,而”ayb”不是)。

请找出最后一个子序列的起始位置。

输入描述
第一行为target,短字符串(长度 <=100)
第二行为source,长字符串(长度 ~= 500,000)

输出描述
最后一个子序列的起始位置,即最后一个子序列首字母的下标

备注
若在source中找不到target,则输出-1。

用例

输入 abc
abcaybec
输出 3
说明 这里有两个abc的子序列满足,取下标较大的,故返回3。

寻找最后一个子序列的起始位置:高效算法设计与实现

1. 核心解题思路

1.1 问题关键点

要高效解决此问题,需同时满足两点:

  • 子序列存在性:判断 target 是否为 source 的子序列。
  • 寻找最后一个起始位置:若存在多个子序列,需找到起始下标最大的那个。

1.2 逆序匹配法

传统子序列判断通常从前向后匹配,但为了找到最后一个起始位置,需从后向前逆向匹配,并通过贪心策略选择尽可能大的下标位置。核心步骤如下:

  1. 预处理字符位置:记录 source 中每个字符的所有出现位置。
  2. 逆序匹配:从 target 的最后一个字符开始,逐个向前匹配。
  3. 贪心选择最远位置:每次在 source 中选择当前字符的最大可行位置。

示例说明

以输入 target=abcsource=abcaybec 为例:

  • source 的字符位置预处理为:
    {'a': [0,3], 'b': [1,5], 'c': [2,6]}
    
  • 逆序匹配过程
    1. 匹配 target 的最后一个字符 c
      • sourcec 的位置列表 [2,6],最大允许位置为 6 → 记录当前位置 6
    2. 匹配 targetb
      • sourceb 的位置列表 [1,5],寻找小于 6-1=5 的最大位置 → 5
    3. 匹配 targeta
      • sourcea 的位置列表 [0,3],寻找小于 5-1=4 的最大位置 → 3
  • 起始位置:最终记录的 a 的位置 3 即为答案。

2. 算法设计与优化

2.1 预处理字符位置

  1. 数据结构:使用字典存储字符到位置列表的映射。
  2. 构建方式:遍历 source,记录每个字符的位置(升序存储)。
  3. 时间复杂度:O(n),其中 n 为 source 的长度。

示例

source = "abcaybec"
预处理结果:
{
    'a': [0, 3],
    'b': [1, 5],
    'c': [2, 6],
    'y': [4],
    'e': [7]
}

2.2 逆序匹配过程

  1. 初始化:设置初始允许的最大位置为 source 的末尾下标 len(source)-1
  2. 逆向遍历 target
    • 对于每个字符 c,检查其在 source 中的位置列表。
    • 若列表为空或无可行位置,返回 -1
  3. 更新允许位置:每次匹配后,更新允许的最大位置为当前匹配位置的前一位。

2.3 二分查找优化

为了快速找到每个字符在 source 中的最大可行位置,使用二分查找:

import bisect

pos_list = char_pos['c']  # 例如 [2,6]
current_max_pos = 5       # 当前允许的最大位置
idx = bisect.bisect_right(pos_list, current_max_pos)
if idx > 0:
    found_pos = pos_list[idx-1]  # 找到最大的可用位置
  • 时间复杂度:每次查找 O(log k),其中 k 为字符出现次数。

3. 代码实现

import bisect
from collections import defaultdict

def find_last_subsequence_start(target, source):
    # 预处理每个字符在source中的位置列表(升序)
    char_pos = defaultdict(list)
    for idx, c in enumerate(source):
        char_pos[c].append(idx)
    
    last_pos = len(source) - 1  # 初始允许的最大位置
    result = -1
    
    # 逆序遍历target
    for c in reversed(target):
        if c not in char_pos or not char_pos[c]:
            return -1
        pos_list = char_pos[c]
        # 二分查找找到最大的 <= last_pos 的位置
        idx = bisect.bisect_right(pos_list, last_pos)
        if idx == 0:
            return -1  # 当前字符无可行位置
        current_pos = pos_list[idx - 1]
        last_pos = current_pos - 1  # 更新允许的最大位置
        result = current_pos  # 记录当前字符的位置
    
    return result  # 返回起始下标

# 输入处理
target = input().strip()
source = input().strip()

if not target:
    print(-1 if not source else 0)
else:
    res = find_last_subsequence_start(target, source)
    print(res if res != -1 else -1)

4. 复杂度分析

  • 时间复杂度

    • 预处理阶段:O(n),遍历 source 一次。
    • 匹配阶段:O(m log k),其中 m 为 target 长度,k 为字符的平均出现次数。
    • 总时间复杂度:O(n + m log k),适用于 n ≤ 500,000 的大数据场景。
  • 空间复杂度

    • O(n),存储每个字符的位置列表。

5. 总结

通过逆序贪心匹配与二分查找的结合,本算法高效解决了以下问题:

  1. 子序列存在性验证:通过逆向匹配快速判断。
  2. 最后一个起始位置查找:贪心选择最远位置,确保结果正确。
  3. 大规模数据处理:预处理和二分查找优化使得算法能够处理极长的 source 字符串。

此方法在日志分析、基因序列匹配等场景中具有广泛应用价值。

你可能感兴趣的:(华为OD机试Python版,华为od,python,算法)