leetcode 3 - 无重复字符的最长子串

题目

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

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

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

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

提示:

  • 0 <= s.length <= 5 \* 104
  • s 由英文字母、数字、符号和空格组成

分析思路

  1. 首先,题目要求是最长子串,符合滑动窗口算法求解特征;
  2. 题目求最长子串长度,实际就是求符合条件的窗口最大长度,所以是一个可变窗口,即属于滑动窗口中的动态窗口类型;
  3. 对于动态窗口类型,可以通过哈希集来进行唯一性检查,在哈希集中存在,即符合条件的可以保留在窗口中,否则剔除键;
  4. 在给定数组 s 长度内滑动窗口:
    • 计数方式:
      • 扩展右边界:在第一层循环中更新,如果右边界当前的字符键不在哈希集中则新建,键初始值为 1,如果键存在则更新字符计数,如果右边界当前字符计数超过 1,说明存在重复字符,需要收缩左边界;
      • 收缩左边界:收缩可以通过一个内循环来实现,每次收缩时,减少移出位置的字符计数,如果减少计数后该字符计数归 0,则剔除该字符对应键,直到右边界当前字符计数不大于 1;
    • 索引方式:
      • 扩展右边界:通过 for 对 s 进行遍历,如果右边界当前字符在哈希集中不存在,则新建字符键,初始值为字符当前索引,如果字符已存在,说明存在重复字符,则收缩左边界;
      • 收缩左边界:获取重复字符旧索引下一位,并和当前左边界索引进行比较,如果重复字符旧索引大于左边界索引(说明重复字符在当前窗口内),则将旧索引下一位作赋值给左边界,同时更新哈希集中的重复字符索引为新索引(即右边界字符当前索引);
  5. 每次移动后,进行当前窗口长度和最大窗口长度比较,如果当前窗口长度大于最大窗口长度,则将当前窗口长度值赋值给最大窗口长度,然后继续计算右边界;
  6. 返回最大窗口长度最终结果。

go 实现示例

  • 解法 1
func lengthOfLongestSubstring(s string) int {

	// 获取字符串s长度
	n := len(s)

	// 边界情况处理:如果
	if n == 0 {
		return 0
	}

	// 定义哈希字符集,用来进行字符唯一性检查
	char := make(map[byte]int)

	// 定义滑动窗口的左右边界,初始值均为0,表示从头开始滑动
	left, right := 0, 0

	// 定义最长无重复子串长度,初始值为0
	maxLength := 0

	// 定义每次移动窗口时的子串长度,初始值为0
	currentLength := 0

	// 开始滑动窗口
	for right < n {

		// 如果右边界新增元素在检查字符集char中存在,表示存在重复字符,则字符计数+1,不存在则新增字符,初始计数为1
		if _, exist := char[s[right]]; exist {

			// 增加重复字符计数
			char[s[right]]++

			// 如果字符计数大于1,说明存在重复字符,减少左边界字符计数
			for char[s[right]] > 1 {
				char[s[left]]--

				// 如果左边界字符计数归0,则从字符集中剔除
				if char[s[left]] == 0 {
					delete(char, s[left])
				}

				// 收缩左边界
				left++
			}
		} else {
			char[s[right]] = 1
		}

		// 每次移动,都更新当前子串长度,并与最大子串长度进行比较,如果大于最大子串长度,则更新
		currentLength = right - left + 1
		if currentLength > maxLength {
			maxLength = currentLength
		}

		// 移动右边界
		right++
	}

	// 返回最长子串长度
	return maxLength
}
  • 解法 2
func lengthOfLongestSubstring(s string) int {

	// 获取字符串s长度
	n := len(s)

	// 如果字符串长度为0,则直接返回0
	if n == 0 {
		return 0
	}

	// 定义哈希集,用于检查字符唯一性
	char := make(map[byte]int)

	// 定义子串最长长度,初始值为0
	maxLength := 0

	// 定义滑动窗口左边界,初始值为0,表示从索引0开始
	left := 0

	// 开始进行窗口滑动,对右边界新增字符进行检查
	for right := 0; right < n; right++ {

		// 如果检查字符在char存在,表示当前字符为重复字符,则收缩左边界,如果上一个重复字符在当前窗口内,即将左边界位置更新为上一个重复字符的下一位
		if lastIndex, exist := char[s[right]]; exist {
			if lastIndex >= left {
				left = lastIndex + 1
			}
		}

		// 更新窗口字符集已有字符最新位置
		char[s[right]] = right

		// 如果当前窗口长度大于最大窗口长度,则更新最大窗口长度
		if right-left+1 > maxLength {
			maxLength = right - left + 1
		}
	}

	// 返回最终计算结果
	return maxLength
}
posted @ 2025-09-01 14:39  505donkey  阅读(23)  评论(0)    收藏  举报