字符串相关的题目在面试中出现的频率是特别高的,主要原因有以下几点:
-
日常开发过程中字符串是我们打交道相对比较多的一种数据类型,相关题目描述也比较简单易懂,基本不会出现面试官描述完题目候选人不理解题目本身意思的情况。
-
字符串可玩性高,排序、递归、回溯、二分查找、动态规划等基本都能基于字符串来出题,很适合拿来考察候选人的代码能力。
所以刷题的过程中,字符串相关的题目应该着重去关注。
1. 题目描述
给定一个字符串 s ,请你找出其中不含有重复字符的最长子串的长度。
输入:一个字符串s。
输出:s无重复字符最长子串的长度。
举例:s = "abcabcbb", 那么其无重复字符的最长子串为"abc", 所以返回值是3。
2. 思路解析
基本上有点编程基础的同学都能想到一种暴力的算法,两个for循环找出所有的子串,针对每个子串去判断是否有重复字符,这样的时间复杂度太高了,很显然这并不是面试官想要的答案。
下面我来介绍一种基于“滑动窗口”的方式来解此题。所谓滑动窗口,其实就是由两个索引维护一个区间,满足一定条件时会动态去调整这个区间。非常像计算机网络中的tcp协议栈里所用到的滑动窗口算法。这里的两个索引决定了滑动窗口的起点和终点,需要始终保持滑动窗口中的元素是不重复的,这就变成了找到包含非重复元素的最长的一个窗口。
a. i=0, j=0, res=0, u_set={};
b. 字符s[i]不在u_set中那么就把s[i]加入到u_set中,并记录当前窗口的长度,索引i继续向后移动,直到s[i]已经在u_set中,u_set开始删除s[j]并移动索引j,直到s[i]不在u_set中,这时把s[i]加入到u_set中并更新res,res=max(res, cur_window);
c. 向后移动索引i,并重复b中的步骤,直到索引i=s.length。
看到这里有些同学可能还是不太理解,文字描述比较抽象,我特意根据一个简单的例子画了个流程图来帮助理解。理解能力比较强的同学也可以一起感受下我的画功。
比如给定s="abcac",其大致流程如图所示:
从上图中可以看出不含重复字符的窗口[0, 2]、[1, 3]和[3, 4]长度分别为3、3、2,即s无重复字符最长子串的长度为3。
3. C++代码实现
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_set<char> u_set;
int res = 0;
int len = s.length();
int cur_window = 0; //当前窗口大小
for (int i = 0, j = 0; i < len; ++i) {
while (j < i && u_set.count(s[i])) {
//s[i]已经在u_set中,窗口区间[j,i]中存在重复的元素
//移动j直到窗口区间[j,i]中不存在重复元素
u_set.erase(s[j]);
++j;
--cur_window;
}
u_set.insert(s[i]);
++cur_window;
res = max(res, cur_window);
}
return res;
}
};
4.复杂度分析
时间复杂度:不要看到代码用了一个for嵌套一个while就以为时间复杂度是O(n^2),i,j都是不会回头的两个索引,各自最多只遍历一遍字符串,unordered_set的查找效率一般是O(1),最坏的情况是O(n),这种情况一般是数据量巨大,存在很多哈希冲突,基本上退化成了链表。由于我们字符集大小也就几百个,不会出现这种最坏的情况。故总的时间复杂度为O(n), n为字符串长度。
空间复杂度: 用了三个int型变量,一个set,set的最大元素个数为字符集的个数,所以空间复杂度为3*O(1)+O(m)=O(m), m为字符集的大小。