【华为机试】3. 无重复字符的最长子串
文章目录
- 3. 无重复字符的最长子串
- 📋 题目描述
- 🎯 示例
- 示例1
- 示例2
- 示例3
- 🔍 解题思路
- 核心思想:滑动窗口
- 算法流程
- 详细步骤
- 🚀 算法实现
- 方法1:暴力法(时间复杂度O(n³))
- 方法2:滑动窗口法(时间复杂度O(n))
- 方法3:优化的滑动窗口(使用数组)
- 📊 算法分析
- 时间复杂度对比
- 滑动窗口过程可视化
- 🎯 关键技巧
- 1. 滑动窗口的本质
- 2. 哈希表的作用
- 3. 边界条件处理
- 🔧 实际应用
- 1. 字符串处理
- 2. 网络协议
- 3. 算法竞赛
- 📈 性能优化
- 1. 空间优化
- 2. 时间优化
- 3. 内存优化
- 🧪 测试用例
- 基础测试
- 边界测试
- 💡 扩展思考
- 1. 变种问题
- 2. 优化方向
- 3. 实际应用扩展
- 🎯 总结
- 完整题解代码
3. 无重复字符的最长子串
📋 题目描述
给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
🎯 示例
示例1
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。
示例2
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
示例3
输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
🔍 解题思路
核心思想:滑动窗口
这是一个经典的滑动窗口问题。我们需要维护一个窗口,使得窗口内的字符都是唯一的,并且尽可能地扩大这个窗口。
算法流程
详细步骤
- 初始化:设置左指针
left = 0
,右指针right = 0
,哈希表记录字符位置 - 扩展窗口:右指针向右移动,将字符加入窗口
- 处理重复:如果遇到重复字符,移动左指针到重复字符的下一位
- 更新结果:每次移动后更新最大长度
- 继续直到结束:重复步骤2-4直到右指针到达字符串末尾
🚀 算法实现
方法1:暴力法(时间复杂度O(n³))
func lengthOfLongestSubstringBruteForce(s string) int {// 枚举所有子串,检查是否包含重复字符// 时间复杂度:O(n³)// 空间复杂度:O(min(m,n))
}
方法2:滑动窗口法(时间复杂度O(n))
func lengthOfLongestSubstring(s string) int {charMap := make(map[byte]int)left := 0maxLength := 0for right := 0; right < len(s); right++ {if pos, exists := charMap[s[right]]; exists && pos >= left {left = pos + 1}charMap[s[right]] = rightmaxLength = max(maxLength, right-left+1)}return maxLength
}
方法3:优化的滑动窗口(使用数组)
func lengthOfLongestSubstringOptimized(s string) int {// 使用数组代替哈希表,提高性能// 适用于ASCII字符集
}
📊 算法分析
时间复杂度对比
方法 | 时间复杂度 | 空间复杂度 | 特点 |
---|---|---|---|
暴力法 | O(n³) | O(min(m,n)) | 简单直观,效率低 |
滑动窗口 | O(n) | O(min(m,n)) | 高效,推荐使用 |
优化滑动窗口 | O(n) | O(m) | 最优性能,固定空间 |
滑动窗口过程可视化
以字符串 “abcabcbb” 为例:
步骤 左指针 右指针 当前窗口 长度 最大长度
1 0 0 a 1 1
2 0 1 ab 2 2
3 0 2 abc 3 3
4 1 3 bca 3 3
5 2 4 cab 3 3
6 3 5 abc 3 3
7 4 6 bcb 3 3
8 6 7 b 1 3
🎯 关键技巧
1. 滑动窗口的本质
- 扩展:右指针不断向右移动扩大窗口
- 收缩:当遇到重复字符时,左指针跳跃到合适位置
2. 哈希表的作用
- 快速检查字符是否在当前窗口中
- 记录字符最后出现的位置
- 支持O(1)时间的查找和更新
3. 边界条件处理
- 空字符串:返回0
- 单字符:返回1
- 全相同字符:返回1
- 全不同字符:返回字符串长度
🔧 实际应用
1. 字符串处理
- 文本编辑器中的重复检测
- 密码强度验证
- 数据去重
2. 网络协议
- TCP滑动窗口协议
- HTTP/2流控制
- 缓存策略
3. 算法竞赛
- 子数组/子串问题的通用解法
- 双指针技巧的典型应用
- 哈希表优化的经典案例
📈 性能优化
1. 空间优化
// 使用数组代替哈希表(仅适用于ASCII)
lastIndex := make([]int, 128)
2. 时间优化
// 避免重复计算
maxLength = max(maxLength, right-left+1)
3. 内存优化
// 预分配合适大小的哈希表
charMap := make(map[byte]int, len(s))
🧪 测试用例
基础测试
testCases := []struct {input stringexpected int
}{{"abcabcbb", 3},{"bbbbb", 1},{"pwwkew", 3},{"", 0},{"au", 2},{"dvdf", 3},
}
边界测试
extremeCases := []struct {input stringexpected int
}{{"a", 1}, // 单字符{"abcdefghijklmnopqrstuvwxyz", 26}, // 全不同{"aaaaaaaaaa", 1}, // 全相同{" ", 1}, // 空格字符{"!@#$%^&*()", 10}, // 特殊字符
}
💡 扩展思考
1. 变种问题
- 最多包含k个不同字符的最长子串
- 最长回文子串
- 最长公共子串
2. 优化方向
- 并行处理长字符串
- 支持Unicode字符集
- 内存映射大文件处理
3. 实际应用扩展
- 日志分析中的模式识别
- 基因序列分析
- 数据压缩算法
🎯 总结
无重复字符的最长子串问题是滑动窗口技巧的经典应用:
- 核心思想:维护一个动态窗口,保证窗口内字符唯一
- 关键技巧:使用哈希表快速检测重复字符
- 时间复杂度:从O(n³)优化到O(n)
- 空间复杂度:O(min(m,n)),m为字符集大小
- 实际应用:字符串处理、网络协议、算法竞赛
通过这道题,我们学会了滑动窗口的基本思想和实现技巧,为解决更复杂的字符串问题打下了坚实基础。
完整题解代码
package mainimport ("fmt""time"
)// 方法1:暴力法 - 时间复杂度O(n³)
func lengthOfLongestSubstringBruteForce(s string) int {n := len(s)if n == 0 {return 0}maxLength := 1// 枚举所有子串for i := 0; i < n; i++ {for j := i + 1; j <= n; j++ {if isUnique(s[i:j]) {if j-i > maxLength {maxLength = j - i}}}}return maxLength
}// 检查字符串是否包含重复字符
func isUnique(s string) bool {charSet := make(map[byte]bool)for i := 0; i < len(s); i++ {if charSet[s[i]] {return false}charSet[s[i]] = true}return true
}// 方法2:滑动窗口法 - 时间复杂度O(n)
func lengthOfLongestSubstring(s string) int {if len(s) == 0 {return 0}charMap := make(map[byte]int)left := 0maxLength := 0for right := 0; right < len(s); right++ {// 如果当前字符在窗口中已存在if pos, exists := charMap[s[right]]; exists && pos >= left {left = pos + 1}charMap[s[right]] = right// 更新最大长度newLength := right - left + 1if newLength > maxLength {maxLength = newLength}}return maxLength
}// 方法3:优化的滑动窗口法(使用数组代替哈希表)
func lengthOfLongestSubstringOptimized(s string) int {if len(s) == 0 {return 0}// 使用数组存储字符最后出现的位置lastIndex := make([]int, 128) // ASCII字符集for i := range lastIndex {lastIndex[i] = -1}left := 0maxLength := 0for right := 0; right < len(s); right++ {char := s[right]// 如果当前字符在窗口中已存在if lastIndex[char] >= left {left = lastIndex[char] + 1}lastIndex[char] = right// 更新最大长度if right-left+1 > maxLength {maxLength = right - left + 1}}return maxLength
}// 方法4:滑动窗口法(带详细过程展示)
func lengthOfLongestSubstringWithTrace(s string) int {if len(s) == 0 {return 0}fmt.Printf("输入字符串: %s\n", s)fmt.Println("滑动窗口过程:")fmt.Println("步骤\t左指针\t右指针\t当前窗口\t\t长度\t最大长度")charMap := make(map[byte]int)left := 0maxLength := 0step := 0for right := 0; right < len(s); right++ {step++// 如果当前字符在窗口中已存在if pos, exists := charMap[s[right]]; exists && pos >= left {left = pos + 1}charMap[s[right]] = right// 更新最大长度currentLength := right - left + 1if currentLength > maxLength {maxLength = currentLength}// 打印当前状态window := s[left : right+1]fmt.Printf("%d\t%d\t%d\t%s\t\t%d\t%d\n",step, left, right, window, currentLength, maxLength)}return maxLength
}// 性能测试函数
func performanceTest() {testCases := []string{"abcabcbb","bbbbb","pwwkew","","abcdefghijklmnopqrstuvwxyz","abcabcabcabcabc","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",}fmt.Println("\n=== 性能测试 ===")fmt.Println("测试用例\t\t\t暴力法耗时\t滑动窗口耗时\t优化版耗时")for _, testCase := range testCases {// 暴力法测试start := time.Now()result1 := lengthOfLongestSubstringBruteForce(testCase)time1 := time.Since(start)// 滑动窗口法测试start = time.Now()result2 := lengthOfLongestSubstring(testCase)time2 := time.Since(start)// 优化版测试start = time.Now()result3 := lengthOfLongestSubstringOptimized(testCase)time3 := time.Since(start)// 验证结果一致性if result1 != result2 || result2 != result3 {fmt.Printf("错误:结果不一致!%d, %d, %d\n", result1, result2, result3)}displayCase := testCaseif len(displayCase) > 15 {displayCase = displayCase[:15] + "..."}fmt.Printf("%-20s\t%v\t\t%v\t\t%v\n",displayCase, time1, time2, time3)}
}func main() {// 基本测试用例testCases := []string{"abcabcbb","bbbbb","pwwkew","","au","dvdf",}fmt.Println("=== 基本测试 ===")for _, testCase := range testCases {result := lengthOfLongestSubstring(testCase)fmt.Printf("输入: \"%s\" -> 输出: %d\n", testCase, result)}fmt.Println("\n=== 详细过程展示 ===")lengthOfLongestSubstringWithTrace("abcabcbb")// 性能测试performanceTest()fmt.Println("\n=== 算法复杂度分析 ===")fmt.Println("暴力法:")fmt.Println(" 时间复杂度:O(n³)")fmt.Println(" 空间复杂度:O(min(m,n)) - m为字符集大小")fmt.Println("\n滑动窗口法:")fmt.Println(" 时间复杂度:O(n)")fmt.Println(" 空间复杂度:O(min(m,n)) - m为字符集大小")fmt.Println("\n优化版滑动窗口:")fmt.Println(" 时间复杂度:O(n)")fmt.Println(" 空间复杂度:O(m) - 固定大小数组")
}