力扣练习——无重复字符的最长子串

问题描述

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

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


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


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


来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路和提示

个人思路

这个问题很自然的想到的列表的方法。但是在如何从列表中识别重复元素的位置这个问题上,没有想到使用指针,所以采用了最傻的办法。即从第一个字符开始依次往后数,发现重复的则中断计数,并记下长度,然后从第二个字符开始重复上述工作。用一个列表记录每个不重复字符子串的长度,最后弹出最长的数。

官方题解

方法一:暴力法

(该方法在题目更新后由于时间限制,已经不能使用了)

思路
逐个检查所有的子字符串,看它是否不含有重复的字符。


算法
假设我们有一个函数 boolean allUnique(String substring) ,如果子字符串中的字符都是唯一的,它会返回 true,否则会返回 false。 我们可以遍历给定字符串 s 的所有可能的子字符串并调用函数 allUnique。 如果事实证明返回值为 true,那么我们将会更新无重复字符子串的最大长度的答案。


现在让我们填补缺少的部分:

为了枚举给定字符串的所有子字符串,我们需要枚举它们开始和结束的索引。假设开始和结束的索引分别为 ii 和 jj。那么我们有 0 \leq i \lt j \leq n0≤i<j≤n(这里的结束索引 jj 是按惯例排除的)。因此,使用 ii 从 0 到 n - 1n−1 以及 jj 从 i+1i+1 到 nn 这两个嵌套的循环,我们可以枚举出 s 的所有子字符串。
要检查一个字符串是否有重复字符,我们可以使用集合。我们遍历字符串中的所有字符,并将它们逐个放入 set 中。在放置一个字符之前,我们检查该集合是否已经包含它。如果包含,我们会返回 false。循环结束后,我们返回 true。


作者:LeetCode
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/wu-zhong-fu-zi-fu-de-zui-chang-zi-chuan-by-leetcod/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

由于我不太能理解这两段话,代码又是用java写的,所以是半看半猜。个人的理解是列出字符串s从长度1到n-1的所有子串,然后记录True的情况。
其时间复杂度:O(n3

要验证索引范围在 [i, j)[i,j) 内的字符是否都是唯一的,我们需要检查该范围中的所有字符。 因此,它将花费 O(j - i)O(j−i) 的时间。
对于给定的 i,对于所有 j ∈[i+1,n] 所耗费的时间总和为: ∑ i + 1 n O ( j − i ) \displaystyle\sum_{i+1}^n O(j-i) i+1nO(ji)

方法二:滑动窗口

(这里引用一个别人的图解,更形象)

假设原始字符串S如下
在这里插入图片描述
从左侧开始遍历S,以i标记窗口左侧,j标记窗口右侧,初始时,i=0,j=0,即开头a所在的位置,此时,窗口大小为1
然后,将j右移,逐步扩大窗口,依次经过b、c、d,此时,窗口内均无重复字符,继续右移j
在这里插入图片描述
当j移动到d后面的a所在位置时,对应字符a在窗口中已存在,此时,窗口大小为5,去除当前重复的一位,窗口大小为4。此时窗口内的字符串abcd为
在这里插入图片描述
找到窗口中已存在的该字符所在位置,并将i移动到该位置下一位
在这里插入图片描述
此时为第二个窗口
在这里插入图片描述
继续重复之前的操作,直到j移动到字符串最后一位停止。


作者:superychen
链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/hua-dong-chuang-kou-tu-wen-jiang-jie-by-superychen/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

代码及结果

个人代码

class Solution(object):
    def lengthOfLongestSubstring(self, s):
            """
            :type s: str
            :rtype: int
            """
            total_ls=[]
            sub_ls=[]
            if len(set(s)) == len(s):
                return len(s)
            else:
                for i in range(len(s)):
                    ss=s[i:]
                    for j in ss:
                        if j not in sub_ls:
                            sub_ls.append(j)
                        else:
                            total_ls.append(len(sub_ls))
                            sub_ls=[]
                            break
                return max(total_ls)

他山之石

执行用时 :1904 ms, 在所有 python 提交中击败了 5.04% 的用户
内存消耗 :13.2 MB, 在所有 python 提交中击败了 5.07% 的用户

推测一下时间复杂度应该是O(n!)(再次存疑)

1.直观的滑动窗口法

class Solution(object):
def lengthOfLongestSubstring(self, s: str) -> int:
        # 字符串为空则返回零
        if not s:
            return 0

        window = []     # 滑动窗口数组
        max_length = 0  # 最长串长度

        # 遍历字符串
        for c in s:
            # 如果字符不在滑动窗口中,则直接扩展窗口
            if c not in window:
                # 使用当前字符扩展窗口
                window.append(c)
            # 如果字符在滑动窗口中,则
            # 1. 从窗口中移除重复字符及之前的字符串部分
            # 2. 再扩展窗口
            else:
                # 从窗口中移除重复字符及之前的字符串部分,新字符串即为无重复字符的字符串
                window[:] = window[window.index(c) + 1:]
                # 扩展窗口
                window.append(c)

            # 更新最大长度
            max_length = max(len(window), max_length)

        return max_length if max_length != 0 else len(s)

#作者:imckl
#链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/python-hua-dong-chuang-kou-xun-xu-jian-jin-de-3ge-/
#来源:力扣(LeetCode)
#著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

2.滑动窗口优化 ( 双指针法 )

class Solution(object):
    def lengthOfLongestSubstring(self, s: str) -> int:
        # 字符串为空则返回零
        if not s:
            return 0

        max_length = 0      # 滑动窗口数组
        left, right = 0, 0  # 双指针

        for i, c in enumerate(s):
            # 如果字符不在滑动窗口中,则直接扩展窗口
            if c not in s[left:right]:
                # 右指针右移一位
                right += 1
            # 如果字符在滑动窗口中,则
            # 1. 从窗口中移除重复字符及之前的字符串部分
            # 2. 再扩展窗口
            else:
                # 在滑动窗口范围内中找出对应的首个字符的索引X,对应的新的左指针位置为X + 1
                # 左指针右移 索引X增一 位
                left += s[left:right].index(c) + 1
                # 右指针右移一位
                right += 1

            # 更新最大长度
            max_length = max(right - left, max_length)

        # 如果最大长度不为零,返回最大长度
        # 如果最大长度仍为零,则说明遍历整个字符串都没有发现重复字符,最大长度即为字符串本身的长度
        return max_length if max_length != 0 else len(s)

#作者:imckl
#链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/python-hua-dong-chuang-kou-xun-xu-jian-jin-de-3ge-/
#来源:力扣(LeetCode)
#著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

3.滑动窗口优化(使用Hash)

class Solution(object):

    def lengthOfLongestSubstring(self, s: str) -> int:
        # 可抛弃字符串的索引尾值 - 字符串索引值,该索引值以及之前的字符都属于重复字符串中的一部分,不再在计算中涉及
        ignore_str_index_end = -1
        dic = {}        # 任意字符最后出现在索引的位置 - {字符: 字符索引值}
        max_length = 0  # 最长字符串长度

        for i, c in enumerate(s):
            # 如果字典中已经存在字符c,则字符c重复
            # 如果字符索引值大于ignore_str_index_end,则字符c在需处理的范围内(补充说明请参考备注一)
            if c in dic and dic[c] > ignore_str_index_end:
                # 先更新可抛弃字符串的索引尾值为字符c上一次的索引值
                ignore_str_index_end = dic[c]
                # 再更新字符c的索引值
                dic[c] = i
            # 否则,
            else:
                # 更新字符最近的索引位置
                dic[c] = i
                # 更新最大长度
                max_length = max(i - ignore_str_index_end, max_length)

        return max_length

#作者:imckl
#链接:https://leetcode-cn.com/problems/longest-substring-without-repeating-characters/solution/python-hua-dong-chuang-kou-xun-xu-jian-jin-de-3ge-/
#来源:力扣(LeetCode)
#著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

这三种方法时间复杂度都是O(N)(我不确定)

总结(一些概念)

在看题解的时候还看到有人用enumerate函数的,但是感觉双指针的方法更好,在此就不作比较了。

第1,2项是为理解第3项中hash map的例子而总结的

1.python中二进制十进制转变

(1). 其他进制转换成十进制
可以用python的int函数。如果只给一个值的话,则是将这个值变为整数。但它的另一个功能是将其他进制转换成十进制:int(‘number’,mod),其中numer是其他进制的数字,需要带‘ ’,mod是需要转变的进制。

(2). 十进制转换成其他进制
转换为二进制为:bin(dec)
转换为八进制为:, oct(dec)
转换为十六进制为:hex(dec)
**备注:**转换后成字符串的形式,其中前两位是进制的标志

2.python中 ^ 的功能

^ 按位异或运算符:当两对应的二进位相异时,结果为1
例子:
下表中变量 a 为 60,b 为 13,二进制格式如下:
a = 0011 1100
b = 0000 1101
a^b = 0011 0001

^按位异或运算符:当两对应的二进位相异时,结果为1(a ^ b) 输出结果 49 ,二进制解释: 0011 0001

3.hash map

(1). 简介
哈希(hash)也翻译作散列。Hash算法,是将一个不定长的输入,通过散列函数变换成一个定长的输出,即散列值。

(2). 在python 中实现
在Python中,字典是通过散列表或说哈希表实现的。字典也被称为关联数组,还称为哈希数组等。也就是说,字典也是一个数组,但数组的索引是键经过哈希函数处理后得到的散列值。哈希函数的目的是使键均匀地分布在数组中,并且可以在内存中以O(1)的时间复杂度进行寻址,从而实现快速查找和修改。

如:

# coding:utf-8
# 自定义哈希函数
 
def my_hash(x):
    return (x % 7) ^ 2
 
print(my_hash(1)) # 输出结果:3
print(my_hash(2)) # 输出结果:0
print(my_hash(3)) # 输出结果:1
print(my_hash(4)) # 输出结果:6

#版权声明:本文为CSDN博主「_Yucen」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
#原文链接:https://blog.csdn.net/qq_14997473/article/details/81085186

(3). 冲突
对不同的关键字可能得到同一散列地址,即k1≠k2,而f(k1)=f(k2),这种现象称为冲突(英语:Collision)。具有相同函数值的关键字对该散列函数来说称做同义词。

如:

c = {'a': 1, 'b': 2, 'b': '3'} 
print(c)

Out[6]: {'a': 1, 'b': '3'}

参考:

  1. Python字典底层实现原理
  2. Hash算法(含python实现)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值