高频算法面试题(十二)- 最长不含重复字符的子串

127 阅读2分钟

「这是我参与11月更文挑战的第 6 天,活动详情查看:2021最后一次更文挑战

刷算法题,从来不是为了记题,而是练习把实际的问题抽象成具体的数据结构或算法模型,然后利用对应的数据结构或算法模型来进行解题。个人觉得,带着这种思维刷题,不仅能解决面试问题,也能更多的学会在日常工作中思考,如何将实际的场景抽象成相应的算法模型,从而提高代码的质量和性能

最长不含重复字符的子串

题目来源LeetCode-剑指 Offer 48. 最长不含重复字符的子字符串

题目描述

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。

示例

示例 1

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

示例 2

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

示例 3

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

提示:

  • s.length <= 40000

解题

解法一:滑动窗口

思路

首先我们应该知道,滑动窗口适合求解数组/字符串内区间的最值问题,通常是有两个边界(左边界left和有边界right),窗口的大小就是right-left。当看到本题的时候,应该大致能想到用滑动窗口来解题

  • 定义左边界left和右边界right,并且定义一个窗口,这个窗口中记录窗口内出现过的字符(这样做的目的是为了能快速知道,右边界遍历到的字符,是否在窗口内出现过)
  • 在right向右滑动的过程中,判断right所在的位置的字符,是否出现在left的右边(left可以理解成最长不含重复子串的开始位置,如果出现在left的左边,就可以不关心)
  • 如果出现了,则让左边界移动到该重复字符的下一个位置即可
  • 期间取窗口的最大值

1.png

代码

func SlideWindow(s string) int {
	n := len(s)
	if n == 0 {
		return 0
	}
	if n == 1 {
		return 1
	}

	left, right, window, maxLength := 0, 0, make(map[byte]int), 1
	for right < n {
		var rightChar = s[right]
		index, ok := window[s[right]] //是否出现过
		if ok && index > left {
			left = index
		}

		if right - left + 1 > maxLength {
			maxLength = right - left + 1
		}

		window[rightChar] = right + 1
		right++
	}

	return maxLength
}

解法二:普通思维解法

思路

这个问题用暴力解法很简单,导致它复杂度比较高的原因就是,每遍历一个字符,需要判断在之前是否已经出现过。如果能记录下来之前出现过的字符,就会简单很多

  1. 定义一个map,用来记录出现过的字符(它的下标是字符串的每一个字符,值是字符串中每个字符对应的下标)
  2. start用来保存最长不含重复子串的起始下标;maxLength记录最长不含重复子串的长度
  3. 当遍历字符串中的某一个字符时,判断它是否已经出现,并且出现的位置在start之前,则更新start为该字符的下一个位置
  4. 然用当前遍历到的字符的下标i-start+1就是0~i这个子串的最长不含重复子串的长度

2.png

代码

func lengthOfLongestSubstring(s string) int {
	lastOccured := make(map[byte]int) //用来记录已经遍历过的字符(下标是s的值,它的值是s的下标)
	maxLength := 0
	start :=0 //记录最长不含重复子串的起始位置

	for i, ch := range []byte(s) {
		lastId, ok := lastOccured[ch]
		if ok && lastId >= start { //如果该字符出现过,并且在start的后边出现的,更新start的位置
			start = lastId + 1
		}
		if i - start + 1 > maxLength {
			maxLength = i - start + 1
		}

		lastOccured[ch] = i
	}

	return maxLength
}