LeetCode(3) Longest Substring Without Repeating Characters
2013-11-23 04:25
337 查看
LeetCode的第3题,给定一个字符串,找到其中的一个最长的字串,使得这个子串不包含重复的字符。
Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.
1 时间复杂度O(N²)的算法
在MAC的Xcode下的运行结果
![](http://img.blog.csdn.net/20131123041124015)
小结:
(1) 注意边界条件的检查,当输入串为空时候应该找到的字串也为空,此时的返回的字串长度应该为0。第一次提交的时候,犯了这个错误。
(2) 采取逐步右移的方式逐一检查,使用hash表来记录每个字符出现的位置,时间复杂度为O(N²),空间复杂度为O(N)。
(3) 程序中的q表示p的前一位,基本上不起作用,写着是为了debug方便。
2 时间复杂度为O(N)的算法
使用i和j两个指针进行搜索,i代表候选的最长子串的开头,j代表候选的最长子串的结尾。
先假设i不动,那么在理想的情况下,我们希望可以一直右移j,直到j到达原字符串的结尾,此时j-i就构成了一个候选的最长子串。每次都维护一个max_length,就可以选出最长子串了。
实际情况是,不一定可以一直右移j,如果字符j已经重复出现过(假设在位置k),就需要停止右移了。记录当前的候选子串并和max_length做比较。接下来为下一次搜寻做准备。
在下一次搜寻中,i应该更新到k+1。这句话的意思是,用这个例子来理解,abcdef是个不重复的子串,abcdefc中(为了方便记录为abc1defc2),c1和c2重复了。那么下一次搜寻,应该跨过出现重复的地方进行,否则找出来的候选串依然有重复字符,且长度还不如上次的搜索。所以下一次搜索,直接从c1的下一个字符d开始进行,也就是说,下一次搜寻中,i应该更新到k+1。
LeetCode给出的参考答案非常漂亮
这个解答的时间复杂度是O(N)。虽然有两个while的嵌套,但是时间复杂度依然是O(N),为什么呢?
因为i和j都只把这个string从开始到结束遍历了一遍。
可以这样想,外层while在改变j的值,j最多从0改变到n(n为字符串的长度),内层while在改变i的值,同样的,i最多从0改变到n(n为字符串的长度)。所以加起来,时间复杂度为O(2*N),也就是O(N)。
还可以这样想,内层循环不一定要进行的,仅仅当j遇到了重复字符后需要更新i的值时,才会进行内存循环,而且i加起来走过的步数最多为n(n为字符串的长度)。
这段代码还有很有意思的一点,就是别忘了在循环体之外,还要写上,maxLen = max(maxLen, n-i)。这是为什么呢? 因为可能最后一次检查的时候,j知道走到字符串末尾都没有遇到重复字符。而while循环体中找到的最长不重复子串只是在j遇到重复字符时才进行的。
另外,下面这个算法我自己测试没有通过
这个解法是动态规划+hash的结合体,主要参考了《找工作知识储备(2)---数组字符串那些经典算法:最大子序列和,最长递增子序列,最长公共子串,最长公共子序列,字符串编辑距离,最长不重复子串,最长回文子串》这篇文章。
update: 2014-12-23 写出来居然Time Limit Exceeded了,O(N²), 看来Leetcode要求变严格了,正解还得用上面的O(N)的办法,用空间换时间。
update: 2014 - 12- 23
又看了一遍leetcode的官网上的解法,就是上面的O(N)的时间复杂度的算法,因为最后计算maxLength 的时候还要考虑剩余的尾巴的部分,很容易忘记,所以我把代码改了一下,这样看上去会if和else这两部分的操作更加整齐一些。
参考资料
1 Longest Substring Without Repeating Characters
2
《找工作知识储备(2)---数组字符串那些经典算法:最大子序列和,最长递增子序列,最长公共子串,最长公共子序列,字符串编辑距离,最长不重复子串,最长回文子串》
Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.
1 时间复杂度O(N²)的算法
// // Solution.h // LeetCodeOJ_003_LongestUniqueSubstr // // Created by feliciafay on 11/21/13. // Copyright (c) 2013 feliciafay. All rights reserved. // #ifndef LeetCodeOJ_003_LongestUniqueSubstr_Solution_h #define LeetCodeOJ_003_LongestUniqueSubstr_Solution_h #include <iostream> #include <string> #include <map> using std::string; class Solution { public: int lengthOfLongestSubstring(string s) { // IMPORTANT: Please reset any member data you declared, as // the same Solution instance will be reused for each test case. unsigned long limit =s.length(); if(limit==0) return 0; int start = 0; int p = 0; int q = 0; //q对程序其实没作用,主要用来debug的时候观察。q与p的区别是,p表示当前进行探测的字符,q表示p的前面一位字符。 unsigned long max_length = 1; unsigned long cur_length = 1; string sub1; std::map<char,int> char_int_map; std::map<char,int>::iterator it = char_int_map.begin(); while(p<limit) { it = char_int_map.find(s[p]); if(it == char_int_map.end()) { q = p; // std::cout<<"1-1 q: "<<q<<std::endl; char_int_map.insert(std::pair<char, int>(s[p],p)); p++; }else{ cur_length = q-start+1; if(cur_length >= max_length) { max_length = cur_length; sub1 = s.substr(start,max_length); // std::cout<<"2-1 substring1: "<<sub1<<std::endl; } start = it->second+1; p = start; q = start; char_int_map.clear(); char_int_map[(char)s[start]] = start; //更新map // std::cout<<"2-2 q: "<<q<<std::endl; p++; } } string tail_segment = s.substr(start,limit-start+1); unsigned long tail_length = tail_segment.length(); if(tail_length >= max_length) { sub1 = tail_segment; max_length = tail_length; } // std::cout<<"largest sub string is "<<sub1<<std::endl; // std::cout<<"largest sub string length is "<<max_length<<std::endl; return (int)max_length; } }; #endif
在MAC的Xcode下的运行结果
小结:
(1) 注意边界条件的检查,当输入串为空时候应该找到的字串也为空,此时的返回的字串长度应该为0。第一次提交的时候,犯了这个错误。
(2) 采取逐步右移的方式逐一检查,使用hash表来记录每个字符出现的位置,时间复杂度为O(N²),空间复杂度为O(N)。
(3) 程序中的q表示p的前一位,基本上不起作用,写着是为了debug方便。
2 时间复杂度为O(N)的算法
使用i和j两个指针进行搜索,i代表候选的最长子串的开头,j代表候选的最长子串的结尾。
先假设i不动,那么在理想的情况下,我们希望可以一直右移j,直到j到达原字符串的结尾,此时j-i就构成了一个候选的最长子串。每次都维护一个max_length,就可以选出最长子串了。
实际情况是,不一定可以一直右移j,如果字符j已经重复出现过(假设在位置k),就需要停止右移了。记录当前的候选子串并和max_length做比较。接下来为下一次搜寻做准备。
在下一次搜寻中,i应该更新到k+1。这句话的意思是,用这个例子来理解,abcdef是个不重复的子串,abcdefc中(为了方便记录为abc1defc2),c1和c2重复了。那么下一次搜寻,应该跨过出现重复的地方进行,否则找出来的候选串依然有重复字符,且长度还不如上次的搜索。所以下一次搜索,直接从c1的下一个字符d开始进行,也就是说,下一次搜寻中,i应该更新到k+1。
LeetCode给出的参考答案非常漂亮
int lengthOfLongestSubstring(string s) { int n = s.length(); int i = 0, j = 0; int maxLen = 0; bool exist[256] = { false }; while (j < n) { if (exist[s[j]]) { maxLen = max(maxLen, j-i); while (s[i] != s[j]) { exist[s[i]] = false; i++; } i++; j++; } else { exist[s[j]] = true; j++; } } maxLen = max(maxLen, n-i); return maxLen; }
这个解答的时间复杂度是O(N)。虽然有两个while的嵌套,但是时间复杂度依然是O(N),为什么呢?
因为i和j都只把这个string从开始到结束遍历了一遍。
可以这样想,外层while在改变j的值,j最多从0改变到n(n为字符串的长度),内层while在改变i的值,同样的,i最多从0改变到n(n为字符串的长度)。所以加起来,时间复杂度为O(2*N),也就是O(N)。
还可以这样想,内层循环不一定要进行的,仅仅当j遇到了重复字符后需要更新i的值时,才会进行内存循环,而且i加起来走过的步数最多为n(n为字符串的长度)。
这段代码还有很有意思的一点,就是别忘了在循环体之外,还要写上,maxLen = max(maxLen, n-i)。这是为什么呢? 因为可能最后一次检查的时候,j知道走到字符串末尾都没有遇到重复字符。而while循环体中找到的最长不重复子串只是在j遇到重复字符时才进行的。
另外,下面这个算法我自己测试没有通过
这个解法是动态规划+hash的结合体,主要参考了《找工作知识储备(2)---数组字符串那些经典算法:最大子序列和,最长递增子序列,最长公共子串,最长公共子序列,字符串编辑距离,最长不重复子串,最长回文子串》这篇文章。
update: 2014-12-23 写出来居然Time Limit Exceeded了,O(N²), 看来Leetcode要求变严格了,正解还得用上面的O(N)的办法,用空间换时间。
//TLE class Solution { public: int lengthOfLongestSubstring(string s) { int i = 0; //每次搜索候选子串的起始位置 int j = 0; //每次搜索候选子串的结束位置 std::unordered_map<char, int> map; int length = 0; int max_length = 0; while (i < s.length() && j < s.length()) { map.clear(); j = i; while (j < s.length() && map.find(s[j]) == map.end()) { map[s[j]] = j; ++j; } length = j - i; //std::cout<<"sub str = "<<s.substr(i, length)<<std::endl; max_length = length > max_length ? length : max_length; i = map[s[j]] +1; // 新的起始位置是从重复的两个字符中的第一个字符的后面开始的。 // i = j 一开始错写为了这句,这样写的话,新的起始位置将是从重复的两个字符中的第二个字符的后面开始,所以会错过对重复字符中间的部分的检查。 } return max_length; } };
update: 2014 - 12- 23
又看了一遍leetcode的官网上的解法,就是上面的O(N)的时间复杂度的算法,因为最后计算maxLength 的时候还要考虑剩余的尾巴的部分,很容易忘记,所以我把代码改了一下,这样看上去会if和else这两部分的操作更加整齐一些。
//72ms i和j都只遍历s一次,所以时间复杂度为O(N) class Solution { public: int lengthOfLongestSubstring(string s) { int n = s.length(); int i = 0, j = 0; //i是候选字符串的起点, j是候选字符串的终点。 int max_length = 0; int cur_length = 0; bool exist[256] = { false }; while (j < n) { if (!exist[s[j]]) { exist[s[j]] = true; //遍历过,记录为true j++; } else { while(s[i]!= s[j]) { exist[s[i]] = false; //新候选字串从第一个重复字符(当s[i] == s[j]时候的i)的后一位开始算,之前的i不算,等效于没有被扫描到,所以设为false. i++; } i++; j++; } cur_length = j - i; max_length = max_length > cur_length ? max_length : cur_length; } return max_length; } };
参考资料
1 Longest Substring Without Repeating Characters
2
《找工作知识储备(2)---数组字符串那些经典算法:最大子序列和,最长递增子序列,最长公共子串,最长公共子序列,字符串编辑距离,最长不重复子串,最长回文子串》
相关文章推荐
- [leetcode] Longest Substring Without Repeating Characters
- Leetcode :3. Longest Substring Without Repeating Characters
- 3-Longest Substring Without Repeating Characters @LeetCode
- leetcode:Longest Substring Without Repeating Characters
- LeetCode|Longest Substring Without Repeating Characters
- leetcode学习笔记:Longest Substring Without Repeating Characters
- leetcode 3 -- Longest Substring Without Repeating Characters
- LeetCode03:Longest Substring Without Repeating Characters
- 【leetcode】Longest Substring Without Repeating Characters
- leetcode3:Longest Substring Without Repeating Characters
- [Leetcode-3] Longest Substring Without Repeating Characters
- LeetCode - Longest Substring Without Repeating Characters
- Leetcode: Longest Substring Without Repeating Characters
- Leetcode Longest Substring Without Repeating Characters解题报告
- leetcode:Longest Substring Without Repeating Characters
- Longest Substring Without Repeating Characters - Leetcode
- Leetcode 3 Longest Substring Without Repeating Characters
- LeetCode 3-Longest Substring Without Repeating Characters
- [LeetCode]-algorithms-Longest Substring Without Repeating Characters
- [leetcode] Longest Substring Without Repeating Characters