Leetcode算法——30、寻找所有词语拼接而成的子串

76 篇文章 1 订阅

给定一个字符串s、一个数组words,里面每个元素都是一个词语,所有词的长度相等。

在s中寻找所有子串的索引,子串需要是words中每个词首尾拼接而成,词之间没有其他字符插入,词的拼接顺序没有要求。

示例:

Example 1:
Input:
  s = "barfoothefoobarman",
  words = ["foo","bar"]
Output: [0,9]
Explanation: Substrings starting at index 0 and 9 are "barfoor" and "foobar" respectively.
The output order does not matter, returning [9,0] is fine too.

Example 2:
Input:
  s = "wordgoodstudentgoodword",
  words = ["word","student"]
Output: []

思路

1、暴力法

由于每个词长度相等,因此每次取出子串的长度是固定的,而且很容易计算出所有词语拼接起来的长度。假设所有词拼接起来的长度为 len。

定义一个指针,从左往右遍历s,每次遍历都判断当前指针开始往后的长度为 len 的子串是否符合要求:

  • 对子串按照词语长度进行切分,分成候选词语,候选词语的个数为数组 words 的词语个数。
  • 对于每一个候选词语,都与数组 words 中的词语进行匹配,如果正好可以一一匹配上,则说明符合要求。

小技巧:

可以将words中的每个词的个数提前统计出来,每次匹配上一个,个数-1,直至所有的词的个数都变为0,则说明匹配成功。

2、改进版

当某个分词匹配不上words时,所有包含这个分词的子串肯定都是不符合要求的,可以迅速忽略掉。

比如一个子串 ‘aaabbbccc’,被切分成了3个候选词语 ‘aaa’,‘bbb’,‘ccc’,然后依次与words中的词语进行匹配,结果发现words中并没有 ‘ccc’ 这个词语,于是这个子串匹配失败。

但是我们还可以获得一个信息,就是当指针继续向右移动3次,需要判断 ‘bbbcccddd’ 这个子串是否符合要求时,
我们可以迅速知道肯定是不符合要求的,因为这个子串也会切分出 ‘ccc’ 这个词语。

关键点在于,是指针向右移动 3 次之后的子串可以迅速判断不符合要求,这个 3 便是每个词语的长度。

因此,从当前指针开始,到匹配不上的候选词语的起始位置结束,这之间的子串,按照词语长度进行切割,得到每个词语的起始位置,以这些位置开头的子串都不符合条件,可以迅速略过。

python实现

import copy
import re

def findSubstring(s, words):
    """
    :type s: str
    :type words: List[str]
    :rtype: List[int]
    暴力法。
    """
    if not s or not words:
        return []
    
    l_word = len(words[0]) # 每个词的长度
    l_total = l_word * len(words) # 所有词拼接起来的长度
    
    if len(s) < l_total: # s长度小于所有词拼接起来的长度
        return []
    
    # 统计words中的词频
    word_count_dict = dict()
    for word in words:
        if word in word_count_dict:
            word_count_dict[word] += 1
        else:
            word_count_dict[word] = 1
    
    # 遍历s
    result = []
    for i in range(0, len(s) - l_total + 1):
        cur_dict = copy.copy(word_count_dict)
        split_list = re.findall(f'.{{{l_word}}}', s[i:i+l_total]) # 按词长度切割子串
        for split_word in split_list:
            if split_word in cur_dict: # 可以匹配上words中的某个词
                if cur_dict[split_word] > 1: # 次数-1
                    cur_dict[split_word] -= 1
                else:
                    cur_dict.pop(split_word)
                # 如果词典为空,则说明全部匹配了一遍
                if not cur_dict:
                    result.append(i)
            else: # 匹配不上,说明当前子串不合格
                break
    return result

def findSubstring2(s, words):
    """
    :type s: str
    :type words: List[str]
    :rtype: List[int]
    改进版。
    当某个分词匹配不上words时,所有包含这个分词的子串肯定都是不符合要求的,可以迅速忽略掉。
    """
    if not s or not words:
        return []
    
    l_word = len(words[0]) # 每个词的长度
    l_total = l_word * len(words) # 所有词拼接起来的长度
    
    if len(s) < l_total: # s长度小于所有词拼接起来的长度
        return []
    
    # 统计words中的词频
    word_count_dict = dict()
    for word in words:
        if word in word_count_dict:
            word_count_dict[word] += 1
        else:
            word_count_dict[word] = 1
    
    # 遍历s
    result = []
    ignore_idx_set = set() # 肯定不符合要求的索引
    for i in range(0, len(s) - l_total + 1):
        if i in ignore_idx_set:
            continue
        cur_dict = copy.copy(word_count_dict)
        for j in range(0, len(words)): # 每次遍历子串的一个分词
            split_word_start = i + j*l_word # 分词的起始索引
            split_word = s[split_word_start : split_word_start + l_word] # 子串的第j个分词
            if split_word in cur_dict: # 可以匹配上words中的某个词
                if cur_dict[split_word] == 0: # 次数已经用尽,说明此词是多余的,子串不符合要求
                    break
                cur_dict[split_word] -= 1
                if j == len(words) - 1: # 已经遍历完最后一个分词,说明子串符合要求
                    result.append(i)
            else: # 分词不存在于words中,则所有包含这个分词的子串都肯定不符合要求
                k = i + l_word # 包含这个分词的子串的起始位置
                while(k <= split_word_start):
                    ignore_idx_set.add(k)
                    k += l_word
                break
    return result

if '__main__' == __name__:
    s = "wordgoodgoodgoodbestword"
    words = ["word","good","best","good"]
    print(findSubstring2(s, words))
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值