自学内容网 自学内容网

LeetCode题解:30.串联所有单词的子串【Python题解超详细,KMP搜索、滑动窗口法】,知识拓展:Python中的排列组合

题目描述

        给定一个字符串 s 和一个字符串数组 words words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含  words 中所有字符串以任意顺序排列连接起来的子串。例如,如果 words = ["ab","cd","ef"], 那么 "abcdef", "abefcd""cdabef", "cdefab""efabcd"和 "efcdab" 都是串联子串。 "acdbef" 不是串联子串,因为他不是任何 words 排列的连接。

        返回所有串联子串在 s 中的开始索引。你可以以 任意顺序 返回答案。

示例 1:

输入:s = "barfoothefoobarman", words = ["foo","bar"]
输出:[0,9]
解释:因为 words.length == 2 同时 words[i].length == 3,连接的子字符串的长度必须为 6。
子串 "barfoo" 开始位置是 0。它是 words 中以 ["bar","foo"] 顺序排列的连接。
子串 "foobar" 开始位置是 9。它是 words 中以 ["foo","bar"] 顺序排列的连接。
输出顺序无关紧要。返回 [9,0] 也是可以的。

示例 2:

输入:s = "wordgoodgoodgoodbestword", words = ["word","good","best","word"]
输出:[]
解释:因为 words.length == 4 并且 words[i].length == 4,所以串联子串的长度必须为 16。
s 中没有子串长度为 16 并且等于 words 的任何顺序排列的连接。
所以我们返回一个空数组。

示例 3:

输入:s = "barfoofoobarthefoobarman", words = ["bar","foo","the"]
输出:[6,9,12]
解释:因为 words.length == 3 并且 words[i].length == 3,所以串联子串的长度必须为 9。
子串 "foobarthe" 开始位置是 6。它是 words 中以 ["foo","bar","the"] 顺序排列的连接。
子串 "barthefoo" 开始位置是 9。它是 words 中以 ["bar","the","foo"] 顺序排列的连接。
子串 "thefoobar" 开始位置是 12。它是 words 中以 ["the","foo","bar"] 顺序排列的连接。

提示:

  • 1 <= s.length <= 104
  • 1 <= words.length <= 5000
  • 1 <= words[i].length <= 30
  • words[i] 和 s 由小写英文字母组成

解答

class Solution(object):
    def findSubstring(self, s, words):
        """
        :type s: str
        :type words: List[str]
        :rtype: List[int]
        """
        # # 思路一:KMP搜索法
        # def prefix_table(pattern):
        #     m=len(pattern)
        #     prefix=[0]*m
        #     j=0
        #     for i in range(1,m):
        #         while j>0 and pattern[i]!=pattern[j]:
        #             j=prefix[j-1]
        #         if pattern[i]==pattern[j]:
        #             j+=1
        #         prefix[i]=j
        #     return prefix
        # def kmp_search(pattern,text):
        #     m,n=len(pattern),len(text)
        #     prefix=prefix_table(pattern)
        #     j=0
        #     result=[]
        #     for i in range(n):
        #         while j>0 and text[i]!=pattern[j]:
        #             j=prefix[j-1]
        #         if text[i]==pattern[j]:
        #             j+=1
        #         if j==m:
        #             result.append(i+1-m)
        #             j=prefix[j-1]
        #     return result

        # # 生成所有可能的串联子串
        # all_patterns=set()
        # for perm in permutations(words):
        #     all_patterns.add("".join(perm))
        # result = []
        # # 对每个可能的目标串进行 KMP 匹配
        # for pattern in all_patterns:
        #     result.extend(kmp_search(pattern,s))
            
        # return result

        # 思路二:
        if not s or not words:
            return []

        # 初始化变量
        word_len = len(words[0])
        word_count = len(words)
        total_len = word_len * word_count
        words_freq = {}

        # 统计 words 中每个单词的频率
        for word in words:
            words_freq[word] = words_freq.get(word, 0) + 1

        result = []

        # 遍历所有可能的起始点 (最多 word_len 种不同的对齐方式)
        for i in range(word_len):
            left = i  # 当前窗口的起始点
            right = i  # 当前窗口的结束点
            window_freq = {}
            count = 0  # 当前窗口内匹配的单词数

            # 滑动窗口
            while right + word_len <= len(s):
                # 取出当前单词
                word = s[right:right + word_len]
                right += word_len

                # 如果是有效单词
                if word in words_freq:
                    window_freq[word] = window_freq.get(word, 0) + 1

                    # 如果频率不超过要求,增加匹配的单词数
                    if window_freq[word] <= words_freq[word]:
                        count += 1
                    else:
                        # 如果频率超过要求,调整窗口,直到频率合法
                        while window_freq[word] > words_freq[word]:
                            left_word = s[left:left + word_len]
                            window_freq[left_word] -= 1
                            if window_freq[left_word] < words_freq[left_word]:
                                count -= 1
                            left += word_len

                    # 如果窗口内匹配了所有单词,记录起始点
                    if count == word_count:
                        result.append(left)
                else:
                    # 如果遇到无效单词,直接重置窗口
                    window_freq.clear()
                    count = 0
                    left = right

        return result          

        思路一,KMP算法:该方法通过生成目标单词的所有排列,并利用 KMP 字符串匹配算法在目标字符串中寻找这些排列的匹配。KMP 算法通过预处理模式字符串,构建前缀函数来加速匹配过程,避免了暴力匹配中的重复计算。尽管 KMP 本身具有线性时间复杂度,但由于需要生成所有可能的单词排列,这使得该方法在多单词组合的情况下计算量大幅增加,导致整体效率不高。

        思路二,滑动窗口法:滑动窗口法通过一个固定大小的窗口在目标字符串中滑动,窗口内的词频统计与目标单词集中的词频进行比较。如果匹配则记录窗口的起始位置,并通过调整窗口来维护频率的合法性。这种方法通过局部更新窗口内容,避免了对整个字符串的重新扫描,通常能够较为高效地处理多个单词的匹配,特别是在目标单词集合较大时具有较低的计算复杂度。

知识拓展: Python中的排列组合

        在 Python 中,排列组合的计算通常可以借助 itertools 库进行实现,或者直接使用 math 库中的一些数学函数来进行相关的数学计算。下面将介绍一些常见的排列组合问题及其在 Python 中的实现方法。

1. 排列(Permutation)

        排列是从一组元素中按照特定顺序选取元素。Python 中没有直接的排列函数,但可以使用 itertools.permutations 来生成排列。

import itertools

# 生成从列表 [1, 2, 3] 中选择 2 个元素的所有排列
arr = [1, 2, 3]
perms = itertools.permutations(arr, 2)

# 打印所有排列
for perm in perms:
    print(perm)

# 输出为:
# (1, 2)
# (1, 3)
# (2, 1)
# (2, 3)
# (3, 1)
# (3, 2)

        计算排列数:排列数可以通过 math 模块中的 factorial 函数来计算。

import math

# 计算从 5 个元素中选取 3 个的排列数
n = 5
k = 3
permutation_count = math.factorial(n) // math.factorial(n - k)
print(permutation_count)

# 输出60

2. 组合(Combination)

        组合是从一组元素中选择元素,但不考虑顺序。Python 中可以使用 itertools.combinations 来生成组合。

import itertools

# 生成从列表 [1, 2, 3] 中选择 2 个元素的所有组合
arr = [1, 2, 3]
combs = itertools.combinations(arr, 2)

# 打印所有组合
for comb in combs:
    print(comb)

# 输出:
# (1, 2)
# (1, 3)
# (2, 3)

        计算组合数:组合数同样可以通过 math 模块中的 factorial 函数来计算。

import math

# 计算从 5 个元素中选取 3 个的组合数
n = 5
k = 3
combination_count = math.factorial(n) // (math.factorial(k) * math.factorial(n - k))
print(combination_count)

# 输出10

感谢阅读,希望对你有所帮助~


原文地址:https://blog.csdn.net/m0_74420622/article/details/143990010

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!