(0)前言算法
原本不打算在博客里面记录本身刷LeetCode的通过。作了几道题目之后发现能够AC却是不假,可是使用的方法在时间效率上平均只能战胜50%左右的用户。于是决定仍是记录一下,不应小瞧这些基础的题目,它们自有存在的价值。数组
(一)题意数据结构
题目连接:https://leetcode.com/problems/longest-substring-without-repeating-characters/ide
Given a string, find the length of the longest substring without repeating characters.优化
Examples:spa
Given "abcabcbb"
, the answer is "abc"
, which the length is 3.code
Given "bbbbb"
, the answer is "b"
, with the length of 1.blog
Given "pwwkew"
, the answer is "wke"
, with the length of 3. Note that the answer must be a substring, "pwke"
is a subsequence and not a substring.leetcode
————————————————————————————————————————————————————————————————————————————字符串
(二)题解
(1)普通解法-O(n2)
把全部的状况都枚举一遍,寻找最长不含重复字母的字串。借助一个bool数组,快速判断是否存在重复。代码以下:
1 class Solution { 2 public: 3 int lengthOfLongestSubstring(string s) { 4 int len = s.length(); 5 bool vis[200]; 6 int ans = 0,sublen; 7 for(int i = 0; i < len; i++) 8 { 9 memset(vis,0,sizeof vis); 10 sublen = 0; 11 for(int j = i; j < len; j++) 12 { 13 if(!vis[s[j]]) 14 { 15 vis[s[j]] = 1; 16 sublen++; 17 } 18 else 19 { 20 break; 21 } 22 23 } 24 if(sublen > ans) ans = sublen; 25 } 26 return ans; 27 } 28 };
(2)优化算法1-O(2n)
上面那个解法是最普通的想法。能够解决这个问题,可是效率不高,由于每一次j都是从i逐个日后移动,而i也是逐个移动的,这致使进行了不少次重复的判断。
而实际上在上一轮中已经作过判断的子串能够继续加以利用。因而引入了set的数据结构。这种方法叫作滑动窗口法
set是集合,包含的元素相互都是不重复的。
于是只要扫一遍字符串就能够解决问题了,可是i和j最坏状况下会重复扫描了同一个字符,因此时间复杂度是2n。
代码以下:
1 class Solution { 2 public: 3 int lengthOfLongestSubstring(string s) { 4 int len = s.length(); 5 set<char> sub; 6 int i = 0, j = 0,ans = 0; 7 //int sublen = 0; 8 while(i < len && j < len) 9 { 10 if(sub.find(s[j]) == sub.end()) 11 { 12 sub.insert(s[j]); 13 j ++; 14 if((j - i) > ans) 15 ans = j - i; 16 } 17 else 18 { 19 sub.erase(sub.find(s[i])); 20 i++; 21 } 22 } 23 return ans; 24 } 25 };
可是第一种实现用时36ms,第二种实现用时132ms。我怀疑是set的引入花费了时间。set要用O(logn)的时间查找、删除、增长元素。
因此改为了用hash的方法,使用bool类型的vis数组标记字符是否出现过.
1 class Solution { 2 public: 3 int lengthOfLongestSubstring(string s) { 4 int len = s.length(); 5 int i = 0, j = 0,ans = 0; 6 bool vis[300]; 7 memset(vis,0,sizeof vis); 8 while(i < len && j < len) 9 { 10 if(!vis[s[j]]) 11 { 12 vis[s[j]] = 1; 13 j++; 14 } 15 else 16 { 17 vis[s[i]] = 0; 18 if(j - i > ans) 19 ans = j - i; 20 i++; 21 } 22 } 23 if(j - i > ans) 24 ans = j - i; 25 return ans; 26 } 27 };
(3)优化算法2-O(n)
是对滑动窗口的一种优化,不只记录已经判断过的元素,还记录该元素对应的下标。
若是j’是与j位置相同的字符,那么下一次i只须要从j’+1开始判断便可,不须要重复[i+1,j']之间的这些判断。
代码以下:
1 class Solution { 2 public: 3 int lengthOfLongestSubstring(string s) { 4 cout<<endl; 5 int len = s.length(); 6 int i = 0, j = 0,ans = 0; 7 int vis[300]; 8 for(int k = 0; k < 300; k++) 9 vis[k] = -1; 10 while(i < len && j < len) 11 { 12 if(vis[s[j]] == -1 || vis[s[j]] < i) 13 { 14 } 15 else 16 { 17 if(j - i > ans) 18 ans = j - i; 19 i = vis[s[j]] + 1; 20 } 21 vis[s[j]] = j; 22 j++; 23 } 24 if(j - i > ans) 25 ans = j - i; 26 return ans; 27 } 28 };
三种方法是一个按部就班的过程,纯纯思考这个过程对我来讲仍是有些煎熬的,虽然道理是很显然的,借助图来讲明一下,
第一种方法
第二种方法
第三种方法