python每日一题【剑指 Offer 48. 最长不含重复字符的子字符串】

本文探讨了剑指Offer48题中寻找最长不含重复字符子字符串的问题,介绍了三种逐步优化的方法,包括初始的列表存储法、改进的空间优化法及最终的哈希表法,并详细分析了每种方法的时间与空间复杂度。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

day18-2022.11.12

题目信息来源
作者:Krahets
链接:https://leetcode.cn/leetbook/read/illustration-of-algorithm
来源:力扣(LeetCode)

剑指 Offer 48. 最长不含重复字符的子字符串

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

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

输入: "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

题解一:个人做题过程

这个方法是之前的连续子数组最大和,还有列表栈最大值的方法结合而来。空间复杂度比较高为2N,即O(N)O(N)O(N),时间复杂度也为O(N2)O(N^2)O(N2)。设置 sub-s 保存最长子串,dp[i]dp[i]dp[i] 保存前 i 个字符的最大连续子数字

如果下一个字符不在subs里,dp[i]=dp[i−1]+1dp[i]=dp[i-1]+1dp[i]=dp[i1]+1,subs append(这里如果用字符串的话就是+,但是+就是重新创建了一个字符串,效率不高,所以换成了列表)。

如果下一个字符在subs里,就要找到重复字符的位置,把它和他之前的字符从subs里面pop出来,dp[i]=len(subs)dp[i] = len(subs)dp[i]=len(subs)

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if not s:return 0
        subs = [s[0]]
        dp = [0]*len(s)
        dp[0] = 1
        for i in range(1, len(s)):
            if s[i] not in subs:
                subs.append(s[i])
                dp[i] = dp[i-1] + 1
            else:
                while subs[0]!=s[i]:
                    subs.pop(0)
                subs.pop(0)
                subs.append(s[i])
                dp[i] = len(subs)
        return max(dp)

查看了解析,考虑到我这里的空间复杂度却有可优化之处,便有下面简化版本:

链接:https://leetcode.cn/leetbook/read/illustration-of-algorithm/5dz9di/

空间复杂度降低:
由于返回值是取 dpdpdp 列表最大值,因此可借助变量 tmp存储 dp[j]dp[j]dp[j],变量 maxlenmaxlenmaxlen 每轮更新最大值即可。
此优化可节省 dpdpdp 列表使用的 O(N)O(N)O(N) 大小的额外空间。

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if not s:return 0
        subs = [s[0]]
        max_len = 1
        tmp = 1
        for i in range(1, len(s)):
            if s[i] not in subs:
                subs.append(s[i])
                tmp = tmp + 1
            else:
                while subs[0]!=s[i]:
                    subs.pop(0)
                subs.pop(0)
                subs.append(s[i])
                tmp = len(subs)
            max_len = max(max_len, tmp)
        return max_len

那还可不可以继续优化呢?题解方式在问题分析上其实和我是一样的,如果没有重复,就+1,有重复就从重复的那个字母的地方开始重新计算dp。我的方式是通过保存计算subs的长度来计算dp的。看到解析,似乎可以考虑有另外一个下标来存储重复字母的前一个序号。

但是还有问题:重复的字母可能在subs队列的中间,所以不能通过记录像窗口的端首端尾一样,记录首尾的指针。并且,不可能只有某一个字母重复,所以可以考虑,记录每个字母出现的位置。遍历查看是否有这个字符,有的话,更新字母的位置,同时更新dp值。这种一一对应的存储关系,可以用字典。而这样的存储,是和 s 的长度无关的,所以空间复杂度是 O(1)O(1)O(1)。继续优化一下代码,就可以得到官方题解里的 动态规划+哈希表法

下面是哈希表的第一版,其实有一个bug,就是如果重复的字母已经不在字串里了,还是会被算了,所有还需要判断一下字串的区间

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if not s:return 0
        # 存储所有出现字母的字典
        mydict = {}
        # 初始化
        max_len = 1
        tmp = 1
        mydict[s[0]] = 0
        for i in range(1, len(s)):
            value = mydict.get(s[i])
            if value==None:
                tmp = tmp + 1
            else:
                tmp = i - value
            # 更新新的序号
            mydict[s[i]] = i
            max_len = max(max_len, tmp)
        return max_len

修改一下为

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if not s:return 0
        # 存储所有出现字母的字典
        mydict = {}
        # 初始化
        max_len = 1
        tmp = 1
        mydict[s[0]] = 0
        for i in range(1, len(s)):
            value = mydict.get(s[i])
            if value==None:
                tmp = tmp + 1
            elif tmp>=i-value:
                tmp = i - value
            else:
                tmp = tmp + 1
            # 更新新的序号
            mydict[s[i]] = i
            max_len = max(max_len, tmp)
        return max_len

value部分的不同条件还可以简化一下:

            if value!=None and tmp>=i-value:
                tmp = i - value
            else:
                tmp = tmp + 1

也可以是

            value = mydict.get(s[i], -1)
            if tmp>=i-value:
                tmp = i - value
            else:
                tmp = tmp + 1
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

piukaty

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值