力扣练习——无重复字符的最长子串
问题描述
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
示例 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] 所耗费的时间总和为:
方法二:滑动窗口
(这里引用一个别人的图解,更形象)
假设原始字符串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'}
参考:
来源:CSDN
作者:weixin_45182000
链接:https://blog.csdn.net/weixin_45182000/article/details/103449628