《剑指offer》43&45、最长不含重复字符的字符串&第一个只出现一次的字符

最长不含重复字符的字符串

offer43的给出了一个字符串,要求其中最长不含重复字符的子字符串。
本例题目描述简单,但是方法多种多样,比较容易想到的是暴力方法和滑动窗口。就python而言,我们可以利用字典中不能有重复元素的特性来解决。此外,也能通过动态规划来解决。

暴力遍历

暴力遍历要求两次遍历,复杂度是 O ( n 2 ) O(n^2) O(n2),反正我是能接受的,天河二号也是能接受的。

# offer43-solution 1
def lengthOfLongestSubstring(self, s: str) -> int:
    max_count = 0 # 最长不重复子字符串的长度
    record = set() # 记录当前子串中以有哪些字符
    i, j = 0, 0 # 记录子串的起点和终点
    while i <= j and j < len(s):
        if s[j] not in record: # 如果子串没重复,长度+1
            record.add(s[j])
            j += 1
        else: # 如果子串重复了,重置record
            i = j = i+1
            record.clear() # 清空 
        if len(record) > max_count:
            max_count = len(record)
    return max_count

滑动窗口法

“滑动窗口”这个概念在计算机算法中非常常见。该算法可以把嵌套的循环转化为单循环从而降低时间复杂度,比较常见的领域的话,TCP协议的滑动窗口进行流量控制,或图像处理中的物体识别都会用到。
其原理大致分成如下几步:
1、初始化头尾指针 head,tail;
2、tail 右移,判断 tail 指向的元素是否在 [head:tail] 的窗口内;
3、如果窗口中没有该元素,则将该元素加入窗口,同时更新窗口长度最大值,tail 右移;否则将 head 指针右移,直到窗口中不包含该元素。
4、返回窗口长度的最大值。
代码不是我写的,见参考链接3,我一开始就没往滑动窗口的方向想。

# offer43-solution 2
class Solution:
   def lengthOfLongestSubstring(self, s: str) -> int:
       if len(s) <= 1:
           return len(s)
       head, tail = 0, 0
       maxLen = 1
       while tail+1 < len(s):
           tail += 1  # 往窗口内添加元素
           if s[tail] not in s[head: tail]:  # 窗口内没有重复的字符,实时更新窗口最大长度
               maxLen = max(maxLen, tail - head + 1)
           else:  # 窗口内有重复字符,移动head直至窗口内不再含重复的元素
               while s[tail] in s[head: tail]:
                   head += 1
       return maxLen

字典法

利用字典的不重复性,只需要一个start指针就可以完成了,这也是我第一想到的方法。python不仅仅语法糖多,好使的玩意儿也不少。

# offer43-solution 3
class Solution:
    def __init__(self):
        self.maxString = []
    def longestSubString(self, inputString):
        dic = {}
        dic = dic.fromkeys(inputString, 0)
        self.maxString.append(inputString[0])
        for i in range(len(inputString)):
            for j in range(i, len(inputString)):
                if dic[inputString[j]] != 0:
                    dic = dic.fromkeys(inputString, 0)
                    break
                else:
                    if j - i + 1 > len(self.maxString[0]):
                        self.maxString = []
                        self.maxString.append(inputString[i:j+1])
                    elif j - i + 1 == len(self.maxString[0]):
                        self.maxString.append(inputString[i:j+1])
                    dic[inputString[j]] += 1
 
sol = Solution()
sol.longestSubString(inputString)

动态规划

虽然我一开始没想到DP,但是半抄半搬地写了个字典(见参考链接4)出来以后,我突然觉得整个DP最头大的部分可以用字典解决,然后就奔着DP去思考了。

定义函数 f ( i ) f(i) f(i)为以第 i i i个字符为结尾的不包含重复字符的子字符串的最长长度,第i个字符和它上次出现在字符串中的位置的距离为 d d d
如果第 i i i个字以前没有出现过,那么 f ( i ) = f ( i − 1 ) + 1 f(i) = f(i-1)+1 f(i)=f(i1)+1,否则分两种情况:

1、 d ≤ f ( i − 1 ) d≤f(i-1) df(i1),此时第 i i i个字符上次出现在 f ( i − 1 ) f(i-1) f(i1)对应的最长子字符串之中即 f ( i ) = d f(i) = d f(i)=d
2、 d > f ( i − 1 ) d>f(i-1) df(i1),此时第 i i i个字符上次出现在 f ( i − 1 ) f(i-1) f(i1)对应的最长子字符串之前,即 f ( i ) = f ( i − 1 ) + 1 f(i) = f(i-1) + 1 f(i)=f(i1)+1
最头大的部分就是计算这个 d d d,但有了字典就好说了。

# offer43-solution 4
class Solution:
    def lengthOfLongestSubstring(self, s):
        start = 0
        maxLength = 0
        usedChar = {}
        for i in range(len(s)):
            if s[i] in usedChar and start <= usedChar[s[i]]:
                start = usedChar[s[i]] + 1
            else:
                maxLength = max(maxLength, i - start + 1)
            usedChar[s[i]] = i
        return maxLength

第一个只出现一次的字符

offer45要求:在一个字符串( 1 < = l e n g t h < = 10000 1<=length<=10000 1<=length<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置。

字典

毫无悬念字典冲冲冲,简单又快捷。

# offer45-solution 1
def FirstNotRepeatingChar(self, s):
    usedChar = {}
    for i in range(len(s)):
        if s[i] in usedChar:
            usedChar[s[i]] = -1
        else:
            usedChar[s[i]] = i
    p = usedChar.values()
    p = [i for i in p if i >= 0]
    return min(p)

非字典

我当初是写完就了事了,后来做笔记总结的时候,也看到了非字典的处理方法。首先需要明确,无论是否使用字典,都必须遍历字符串,统计每个字符出现的次数,然后再遍历一遍字符串,如果某个字符出现的次数是1,则返回这个字符的索引。
在不使用字典的情况下,参考链接5给出了换一个数据类型来存储字符串出现的次数的思路,我照搬代码,共同学习。

# offer45-solution 2
def FirstNotRepeatingChar(string):
    # 记录每个字符出现的次数
    lst = [0 for i in range(26)]
    for item in string:
        lst[ord(item) - 97] +=1
    for index, item in enumerate(string):
        if lst[ord(item) - 97] == 1:
            return index
    return -1

参考

滑动窗口算法基本原理与实践
Leetcode刷题总结之滑动窗口法(尺取法)
最长不含重复字符的子字符串(Python and C++解法)
最长不包含重复字符的子字符串【python】
第一个只出现一次的字符
Python字典及基本操作(超级详细)

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值