最长不同字串
给定一个字符串,找出该字符串中最长的,无重复字符的子串。
字符串s中包含字母,数字,空格和符号。
这道题我想到的有两种比较好的解法:1、滑动窗口法 2、hashmap优化法
1、滑动窗口法
滑动窗口解法的思想是通过双指针作为界限划定一个窗口,窗口内的内容是满足条件的内容,本题条件是无重复字符的字串,所以窗口内装的是无重复子串。
设定的左指针为start,右指针为end,当满足以下条件时,左右指针分别移动:
1、每次都优先检查右指针开始向左的子串中最大的无重复字符子串,也就是说窗口不断向右扩大
2、在窗口每向右扩大一次,左指针以右指针为最大界限为移动前提,去判断当前左指针是否和右指针相同,如果相同则往当前相同的左指针下标右移动一格,下次左指针就以这里为起点向右检索。
为什么左指针和右指针相同时需要移动?
因为左指针和右指针相同时,说明当前左指针下标的左边任意字符和右指针形成的子串都是不满足 “不包含相同字符子串” 条件的,为了避免重复,左指针 start 直接跳到当前左指针b的右边一格
详细代码如下:
class Solution { public: int lengthOfLongestSubstring(string s) { if(s.length()==0||s.length()==1) return s.length(); int max(0),start,end; for(end=0,start=0;end<s.length();end++) { for(int b=start;b<end;b++) if(s[b]==s[end]) { start=b+1; break; } max=end-start+1>max?end-start+1:max; } return max; } };
时间复杂度为O(n2),空间复杂度为1。
因为当字符串整个为不重复字符串时,可以看到上述这个算法要循环n2,start每次都要从0开始遍历。
2、hashmap优化滑动窗口法
我们可以看到题目中给定的是字母,数字,字符和空格,这些字符集是有限的,如果我们拿一段有限空间来存储已经出现过的字符,那么每次查找时,也不用再从左指针开始往右去查找是否有字母重复,而只需要从集合中取出对应值的下标就可以知道滑动窗口内的是否有重复字符了。
思路是:依旧是左右指针,但是只有右指针往右遍历,左指针不用循环移动,只负责告知窗口左界限位置在哪。
1、遍历时总要更新当前字符在hash集合中的下标位置,这样能保证hash集合中一直有最新的滑动窗口中各字符的位置信息。
2、如果遍历的当前字符出现在hash集合中,则更新左指针的位置为hash集合中那个出现过的字符的位置的右一个。
时间复杂度为O(n),空间复杂度为O(1),因为字符集是有限的,不超过200个。
详细代码如下:
1 class pack{ 2 public: 3 int data; 4 bool isfou; 5 pack(bool isfou,int data):isfou(isfou),data(data){} 6 }; 7 class Solution { 8 public: 9 pack check(char x) 10 { 11 int data=0; 12 bool isfou=true; 13 if(x>='a'&&x<='z') 14 data=x-'a'; 15 else if(x>='A'&&x<='Z') 16 data=x-'A'+26; 17 else if(x>='0'&&x<='9') 18 data=x-'0'+52; 19 else if(x==' ') 20 data=62; 21 else 22 { 23 data=(int)x-0; 24 isfou=false; 25 } 26 pack ex(isfou,data); 27 return ex; 28 } 29 int lengthOfLongestSubstring(string s) { 30 if(s.length()==0||s.length()==1) 31 return s.length(); 32 int max(0),flag1[63],flag2[150],index=0,a; 33 memset(flag1,0,sizeof(flag1)); 34 memset(flag2,0,sizeof(flag2)); 35 pack ex=check(s[0]); 36 ex.isfou?flag1[ex.data]=1:flag2[ex.data]=1; 37 for(a=1;a<s.length();a++) 38 { 39 ex=check(s[a]); 40 if((ex.isfou&&flag1[ex.data]>=index)||(!ex.isfou&&flag2[ex.data]>=index)) 41 { 42 index=ex.isfou?flag1[ex.data]:flag2[ex.data]; 43 } 44 ex.isfou?flag1[ex.data]=a+1:flag2[ex.data]=a+1; 45 max=a-index+1>max?a-index+1:max; 46 } 47 max=a-index>max?a-index:max; 48 return max; 49 } 50 };
有些厉害的同学可能已经发现,为什么我不直接根据ASCII码值来划分字母在hash集合中的位置,反而要划分两个数组来判断是字符还是数组、字母、空格。那是因为我不知道ASCII集从多少到多少啊哈哈哈哈哈!所以我干脆一不做二不休,自己划分26个小写字母、26个大写字母、10个数字和一个空格的散列值了,反正值并不是很大。
还有些同学可能好奇为什么我不直接用map集合或者set集合做这道题,因为map集合和set集合都是自带排序功能的,他们是基于某种二叉树而建成的集合,在插入过程中相比于我自建hash集合要费时的多,leetcode官方的代码虽然很简洁,但是你直接提交它的代码发现相当耗时就是这个原因。

浙公网安备 33010602011771号