剑指 Offer 48. 最长不含重复字符的子字符串_字符串

思路

方法一:暴力法

对于字符串s,用f[i]表示以s[i]开头的无重复字符的字符串最大长度,

逐一求出f[0], f[1], ... ,f[s.length()-1] ,最后返回这些值中的最大值即可。

1 class Solution {
2 public:
3 int lengthOfLongestSubstring(string s) {
4 if(s.empty())
5 return 0;
6 //f[i]表示以字符s[i]开头的最长无重复字符串
7 vector<int> f(s.size(), 0);
8 unordered_map<char, bool> vis;
9
10 for(int i = 0; i < s.length(); ++i) {
11 vis[s[i]] = true;
12 f[i] = 1;
13 for(int j = i+1; j < s.length(); ++j) {
14 if(!vis[s[j]]) {
15 vis[s[j]] = true;
16 f[i]++;
17 } else {
18 for(int k = i; k <= j; ++k) {
19 if(vis[s[k]])
20 vis[s[k]] = false;
21 }
22 break;
23 }
24 }
25 }
26
27 int maxlen = -1;
28 for(int i = 0; i < f.size(); ++i) {
29 if(f[i] > maxlen)
30 maxlen = f[i];
31 }
32
33 return maxlen;
34 }
35 };

复杂度分析

时间复杂度:O(n2)

空间复杂度:O(n)

空间优化:由于最后只需要求f[]数组中的最大值,所以可以只用一个表示最大值的变量来代替f[]数组,这样可以减少n个大小的空间,但空间复杂度还是O(n),因为unordered_map仍然需要O(n)的空间。

 

以下方法都参考题解:​​面试题48. 最长不含重复字符的子字符串(动态规划 / 双指针 + 哈希表,清晰图解)​

 

方法二:动态规划

剑指 Offer 48. 最长不含重复字符的子字符串_字符串_02

剑指 Offer 48. 最长不含重复字符的子字符串_动态规划_03

 

方法2.1 动态规划 + 哈希表

剑指 Offer 48. 最长不含重复字符的子字符串_动态规划_04

1 class Solution {
2 public:
3 int lengthOfLongestSubstring(string s) {
4 if(s.empty())
5 return 0;
6 unordered_map<char, int> dic;
7 vector<int> dp(s.size());
8 dp[0] = 1;
9 dic[s[0]] = 0;
10
11 int i = -1;
12 for(int j = 1; j < s.size(); ++j) {
13 //获取索引位置i
14 if(dic.count(s[j]) == 1)
15 i = dic[s[j]];
16 else
17 i = -1;
18
19 dic[s[j]] = j; //更新哈希表
20
21 if(dp[j-1] < j-i) {
22 dp[j] = dp[j-1] + 1;
23 } else {
24 dp[j] = j-i;
25 }
26 }
27
28 //返回dp数组中的最大值
29 int res = dp[0];
30 for(int j = 1; j < dp.size(); ++j) {
31 if(dp[j] > res)
32 res = dp[j];
33 }
34
35 return res;
36 }
37 };

 剑指 Offer 48. 最长不含重复字符的子字符串_数组_05

 空间优化后的代码如下:

1 class Solution {
2 public:
3 int lengthOfLongestSubstring(string s) {
4 unordered_map<char, int> dic;
5 int res = 0;
6
7 int i = -1;
8 int tmp = 0;
9 for(int j = 0; j < s.size(); ++j) {
10 if(dic.count(s[j]) == 1)
11 i = dic[s[j]];
12 else
13 i = -1;
14
15 dic[s[j]] = j;
16
17 if(tmp < j-i) {
18 tmp = tmp + 1;
19 } else {
20 tmp = j-i;
21 }
22
23 res = max(res, tmp);
24 }
25
26 return res;
27 }
28 };

 剑指 Offer 48. 最长不含重复字符的子字符串_动态规划_06

 

方法2.2 动态规划 + 线性遍历

 剑指 Offer 48. 最长不含重复字符的子字符串_数组_07

1 class Solution {
2 public:
3 int lengthOfLongestSubstring(string s) {
4 int res = 0;
5 int tmp = 0;
6
7 for(int j = 0; j < s.size(); ++j) {
8 int i = j-1;
9 while(i >= 0 && s[i] != s[j]) {
10 --i; //线性查找
11 }
12
13 if(tmp < j-i) {
14 tmp = tmp + 1;
15 } else {
16 tmp = j-i;
17 }
18
19 res = max(res, tmp);
20 }
21
22 return res;
23 }
24 };

 

方法三:双指针 + 哈希表 (滑动窗口)

剑指 Offer 48. 最长不含重复字符的子字符串_数组_08

1 class Solution {
2 public:
3 int lengthOfLongestSubstring(string s) {
4 unordered_map<char, int> dic;
5 int i = -1, res = 0;
6 for(int j = 0; j < s.length(); j++) {
7 if(dic.count(s[j]) == 1)
8 i = max(i, dic[s[j]]); // 更新左指针 i
9 dic[s[j]] = j; // 更新哈希表记录
10 res = max(res, j - i); // 更新结果
11 }
12
13 return res;
14 }
15 };