2021-3

3. 无重复字符的最长子串(中等)

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1:

输入: "abcabcbb"

输出: 3

解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

示例 2:

输入: "bbbbb"

输出: 1

解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。

示例 3:

输入: "pwwkew"

输出: 3

解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。

     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

方法:滑动窗口

class Solution:

    def lengthOfLongestSubstring(self, s: str) -> int:

        if not s:

            return 0

        n = len(s)

        lookup = set()

        left = 0

        cur_len, max_len = 00

        for i in range(n):

            cur_len += 1

            while s[i] in lookup:

                lookup.remove(s[left])

                cur_len -= 1

                left += 1

            if cur_len > max_len:

                max_len = cur_len

            lookup.add(s[i])

        return max_len

76. 最小覆盖子串(困难)

给你一个字符串 S、一个字符串 T 。请你设计一种算法,可以在 O(n) 的时间复杂度内,从字符串 S 里面找出:包含 T 所有字符的最小子串。

 

示例:

输入:S = "ADOBECODEBANC", T = "ABC"

输出:"BANC"

                                         

提示:

如果 S 中不存这样的子串,则返回空字符串 ""

如果 S 中存在这样的子串,我们保证它是唯一的答案。

方法: 滑动窗口的思想:

i,j表示滑动窗口的左边界和右边界,通过改变i,j来扩展和收缩滑动窗口,可以想象成一个窗口在字符串上游走,当这个窗口包含的元素满足条件,即包含字符串T的所有元素,记录下这个滑动窗口的长度j-i+1,这些长度中的最小值就是要求的结果。

 

步骤一

不断增加j使滑动窗口增大,直到窗口包含了T的所有元素

 

步骤二

不断增加i使滑动窗口缩小,因为是要求最小字串,所以将不必要的元素排除在外,使长度减小,直到碰到一个必须包含的元素,这个时候不能再扔了,再扔就不满足条件了,记录此时滑动窗口的长度,并保存最小值

 

步骤三

i再增加一个位置,这个时候滑动窗口肯定不满足条件了,那么继续从步骤一开始执行,寻找新的满足条件的滑动窗口,如此反复,直到j超出了字符串S范围。

 

面临的问题:

如何判断滑动窗口包含了T的所有元素?

我们用一个字典need来表示当前滑动窗口中需要的各元素的数量,一开始滑动窗口为空,用T中各元素来初始化这个need,当滑动窗口扩展或者收缩的时候,去维护这个need字典,例如当滑动窗口包含某个元素,我们就让need中这个元素的数量减1,代表所需元素减少了1个;当滑动窗口移除某个元素,就让need中这个元素的数量加1

记住一点:need始终记录着当前滑动窗口下,我们还需要的元素数量,我们在改变i,j时,需同步维护need

值得注意的是,只要某个元素包含在滑动窗口中,我们就会在need中存储这个元素的数量,如果某个元素存储的是负数代表这个元素是多余的。比如当need等于{'A':-2,'C':1}时,表示当前滑动窗口中,我们有2A是多余的,同时还需要1C。这么做的目的就是为了步骤二中,排除不必要的元素,数量为负的就是不必要的元素,而数量为0表示刚刚好。

回到问题中来,那么如何判断滑动窗口包含了T的所有元素?结论就是当need中所有元素的数量都小于等于0时,表示当前滑动窗口不再需要任何元素。

优化

如果每次判断滑动窗口是否包含了T的所有元素,都去遍历need看是否所有元素数量都小于等于0,这个会耗费O(k)的时间复杂度,k代表字典长度,最坏情况下,k可能等于len(S)

其实这个是可以避免的,我们可以维护一个额外的变量needCnt来记录所需元素的总数量,当我们碰到一个所需元素c,不仅need[c]的数量减少1,同时needCnt也要减少1,这样我们通过needCnt就可以知道是否满足条件,而无需遍历字典了。

前面也提到过,need记录了遍历到的所有元素,而只有need[c]>0大于0时,代表c就是所需元素

 

图示

S="DOABECODEBANC"T="ABC"为例

初始状态:

 

步骤一:不断增加j使滑动窗口增大,直到窗口包含了T的所有元素,need中所有元素的数量都小于等于0,同时needCnt也是0

 

步骤二:不断增加i使滑动窗口缩小,直到碰到一个必须包含的元素A,此时记录长度更新结果

 

 

步骤三:让i再增加一个位置,开始寻找下一个满足条件的滑动窗口

 

class Solution:

    def minWindow(self, s: str, t: str) -> str:

        from collections import defaultdict

        lookup = defaultdict(int)

        for val in t:

            # lookup[val] = lookup.get(val, 0) + 1

            lookup[val] += 1

        n, m = len(s), len(t)

        left = 0

        min_len, res = n+1""

        for right in range(n):

            if lookup[s[right]] > 0:

                m -= 1

            lookup[s[right]] -= 1

            while m == 0:

                if min_len > right - left + 1:

                    min_len = right - left + 1

                    res = s[left:right+1]

                if lookup[s[left]] == 0:

                    m += 1

                lookup[s[left]] += 1

                left += 1

        return res

567. 字符串的排列(中等)

给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。

换句话说,第一个字符串的排列之一是第二个字符串的子串。

示例1:

输入: s1 = "ab" s2 = "eidbaooo"

输出: True

解释: s2 包含 s1 的排列之一 ("ba").

示例2:

输入: s1= "ab" s2 = "eidboaoo"

输出: False

 

注意:

输入的字符串只包含小写字母

两个字符串的长度都在 [1, 10,000] 之间

方法:滑动窗口+排序

class Solution:

    def checkInclusion(self, s1: str, s2: str) -> bool:

        n1, n2 = len(s1), len(s2)

        s1 = sorted(s1)

        for i in range(n2 - n1 + 1):

            temp = s2[i: i+n1]

            if sorted(temp) == s1:

                return True

        return False

30. 串联所有单词的子串(困难)

给定一个字符串 和一些长度相同的单词 words找出 中恰好可以由 words 中所有单词串联形成的子串的起始位置。

注意子串要与 words 中的单词完全匹配,中间不能有其他字符,但不需要考虑 words 中单词串联的顺序。

示例 1

输入:
  s = "barfoothefoobarman",
  words = ["foo","bar"]
输出:[0,9]
解释:
从索引 0  9 开始的子串分别是 "barfoo"  "foobar" 
输出的顺序不重要, [9,0] 也是有效答案。

示例 2

输入:
  s = "wordgoodgoodgoodbestword",
  words = ["word","good","best","word"]
输出:[]

方法1:滑动窗口+hash

因为单词长度固定的,我们可以计算出截取字符串的单词个数是否和 words 里相等,所以我们可以借用哈希表。

一个是哈希表是 words,一个哈希表是截取的字符串,比较两个哈希是否相等。

因为遍历和比较都是线性的,所以时间复杂度:O(n^2)

class Solution:

    def findSubstring(self, s: str, words: List[str]) -> List[int]:

        from collections import Counter

        if not s or not words:

            return []

        one_word = len(words[0])

        all_len = len(words)*one_word

        n = len(s)

        words = Counter(words)

        res = []

        for i in range(0, n-all_len+1):

            temp = s[i:i+all_len]

            c_temp = []

            for j in range(0, all_len, one_word):

                c_temp.append(temp[j:j+one_word])

            if Counter(c_temp) == words:

                res.append(i)

        return res

方法2:双指针

每次左右指针的移动,最主要是去除多余的元素,去除多余元素分为两种:

  1. 右指针向右移动时,元素个数超过了words中存在的元素个数,此时左指针向右移动直到去除了相应的一个元素(元素个数不再超过words为止)

b

a

r

t

o

o

t

o

o

b

a

r

b

a

r

left

 

 

 

 

 

 

 

 

right

 

 

 

 

 

此时包含了两个“too”,左指针一直向右移动直到去除了相应的“too”元素为止。

b

a

r

t

o

o

t

o

o

b

a

r

b

a

r

 

 

 

 

 

 

left

 

 

right

 

 

 

 

 

 

  1. 右指针向右移动时,发现了不存在words中的元素,此时右指针一直向右移动直到找到words存在的元素为止。

t

h

e

t

h

e

b

a

r

t

o

o

left

 

 

 

 

 

right

 

 

 

 

 

 

   右指针的右边遇到了words中不存在的字符串“the”,此时右指针不断地向右移动直到遇到了words中存在的“bar”为止

t

h

e

t

h

e

b

a

r

t

o

o

 

 

 

 

 

 

left right

 

 

 

 

 

 

class Solution:

    def findSubstring(self, s: str, words: List[str]) -> List[int]:

        from collections import Counter

        if not s or not words:

            return []

        one_word = len(words[0])

        word_num = len(words)

        n = len(s)

        if n < one_word:

            return []

        words = Counter(words)

        res = []

        for i in range(0, one_word):

            cur_count = 0

            left, right = i, i

            cur_Counter = Counter()

            while right + one_word <= n:

                temp = s[right:right+one_word]

                right += one_word

                if temp not in words:

                    left = right

                    cur_Counter.clear()

                    cur_count = 0

                else:

                    cur_Counter[temp] += 1

                    cur_count += 1

                    while cur_Counter[temp] > words[temp]:

                        left_temp = s[left:left+one_word]

                        left += one_word

                        cur_Counter[left_temp] -= 1

                        cur_count -= 1

                    if cur_count == word_num:

                        res.append(left)

        return res

 

632. 最小区间(困难)

你有 k 个升序排列的整数列表。找到一个最小区间,使得 k 个列表中的每个列表至少有一个数包含在其中。

我们定义如果 b-a < d-c 或者在 b-a == d-c 时 a < c,则区间 [a,b] 比 [c,d] 小。

示例:

输入:[[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]

输出:[20,24]

解释:

列表 1[4, 10, 15, 24, 26]24 在区间 [20,24] 中。

列表 2[0, 9, 12, 20]20 在区间 [20,24] 中。

列表 3[5, 18, 22, 30]22 在区间 [20,24] 中。

 

提示:

给定的列表可能包含重复元素,所以在这里升序表示 >=

1 <= k <= 3500

-105 <= 元素的值 <= 105

对于使用Java的用户,请注意传入类型已修改为List<List<Integer>>。重置代码模板后可以看到这项改动。

方法1:堆

给定 k个列表,需要找到最小区间,使得每个列表都至少有一个数在该区间中。该问题可以转化为,从 k个列表中各取一个数,使得这 k 个数中的最大值与最小值的差最小

 

假设这 k 个数中的最小值是第 i 个列表中的 x,对于任意 j!=i,设第 j 个列表中被选为 k 个数之一的数是 y,则为了找到最小区间,y 应该取第 j 个列表中大于等于 x 的最小的数。简单证明如下:假设 z 也是第 j 个列表中的数,且 z>y,则有 z−x>y−x,同时包含 x z 的区间一定不会小于同时包含 x y 的区间。因此,其余 k−1 个列表中应该取大于等于 x 的最小的数。

 

由于 k 个列表都是升序排列的,因此对每个列表维护一个指针,通过指针得到列表中的元素,指针右移之后指向的元素一定大于或等于之前的元素。

 

使用最小堆维护 k 个指针指向的元素中的最小值,同时维护堆中元素的最大值。初始时,k 个指针都指向下标 0,最大元素即为所有列表的下标 0 位置的元素中的最大值。每次从堆中取出最小值,根据最大值和最小值计算当前区间,如果当前区间小于最小区间则用当前区间更新最小区间,然后将对应列表的指针右移,将新元素加入堆中,并更新堆中元素的最大值。

 

如果一个列表的指针超出该列表的下标范围,则说明该列表中的所有元素都被遍历过,堆中不会再有该列表中的元素,因此退出循环。

 

class Solution:

    def smallestRange(self, nums: List[List[int]]) -> List[int]:

        rangeLeft, rangeRight = -10**910**9

        maxValue = max(row[0for row in nums)

        priorityQueue = [(vec[0], i, 0for i, vec in enumerate(nums)]

        heapq.heapify(priorityQueue)

 

        while True:

            minValue, row, col = heapq.heappop(priorityQueue)

            if maxValue - minValue < rangeRight - rangeLeft:

                rangeLeft, rangeRight = minValue, maxValue

            if col == len(nums[row]) - 1:

                break

            maxValue = max(maxValue, nums[row][col+1])

            heapq.heappush(priorityQueue, (nums[row][col+1], row, col+1))

            

        return [rangeLeft, rangeRight]

方法2

1143. 最长公共子序列(中等)

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,"ace" 是 "abcde" 的子序列,但 "aec" 不是 "abcde" 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

示例 1:

输入:text1 = "abcde", text2 = "ace"

输出:

解释:最长公共子序列是 "ace",它的长度为 3。

示例 2:

输入:text1 = "abc", text2 = "abc"

输出:3

解释:最长公共子序列是 "abc",它的长度为 3。

示例 3:

输入:text1 = "abc", text2 = "def"

输出:0

解释:两个字符串没有公共子序列,返回 0。

提示:

  • 1 <= text1.length <= 1000
  • 1 <= text2.length <= 1000
  • 输入的字符串只含有小写英文字符。

方法:DP

dp[i+1][j+1]:表示以str1[i]结尾的、str2[j]结尾的字符串str1str2的最长公共子序列。

class Solution:

    def longestCommonSubsequence(self, text1: str, text2: str) -> int:

        m, n = len(text1), len(text2)

        dp = [[0]*(n+1for _ in range(m+1)]

        for i in range(m):

            for j in range(n):

                if text1[i]==text2[j]:

                    dp[i+1][j+1] = dp[i][j] + 1

                else:

                    dp[i+1][j+1] = max(dp[i+1][j], dp[i][j+1])

        return dp[m][n]

718. 最长重复子数组(中等)

给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

示例:

输入:

A: [1,2,3,2,1]

B: [3,2,1,4,7]

输出:3

解释:

长度最长的公共子数组是 [3, 2, 1] 。

提示:

  • 1 <= len(A), len(B) <= 1000
  • 0 <= A[i], B[i] < 100

方法1:DP

令 dp[i+1][j+1] 表示 A[:i] 和 B[:j] 的最长公共前缀,那么答案即为所有 dp中的最大值。如果 A[i] == B[j],那么 dp[i+1][j+1] = dp[i][j] + 1,否则 dp[i][j] = 0。

 

时间复杂度: O(N×M)

空间复杂度: O(N×M)

N 表示数组 A 的长度,M 表示数组 B 的长度。

空间复杂度还可以再优化,利用滚动数组可以优化到 O(min(N,M))。

 

class Solution:

    def findLength(self, A: List[int], B: List[int]) -> int:

        m, n = len(A), len(B)

        dp = [[0]*(n+1for _ in range(m+1)]

        res = 0

        for i in range(m):

            for j in range(n):

                if A[i] == B[j]:

                    dp[i+1][j+1] = dp[i][j] + 1

                    res = max(res, dp[i+1][j+1])

        return res

方法2:滑动窗口

1  2  3  2  1

3  2  1  4  7

 

1  2  3  2  1

3  2  1  4  7

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值