[LeetCode] 3. Longest Substring Without Repeating Characters 最长无重复字符的子串

1.暴力法:

     本题让求给定字符串的最长的无重复字符的子串,首先想到暴力解法,穷举出字符串的所有子串,并判断每个子串是否是不重复子串,具体使用hashset或set判是否有重复字符;暴力法效率很差,时间O(n^3),空间O(n);参考代码如下:

 1 class Solution {
 2 public:
 3     int lengthOfLongestSubstring(string s){
 4         int res = 0;
 5         const int size = s.size();
 6         if(s.empty()) return 0;
 7         if(size<=1) return size;
 8         for(int i = 0;i<size;++i)
 9            for(int j = i;j<size;++j)
10            {
11                 int sub_seq = j-i+1;
12                 if(isNoreapeatSubstring(i,j,s)){
13                     if(sub_seq>=res)
14                        res = sub_seq;
15                 }
16            }
17         return res;
18     }
19 
20     bool isNoreapeatSubstring(int l,int h,string &s)
21     {
22         set<char> charSet;
23         for(int i =l;i<=h;i++)
24         {
25             if(charSet.find(s[i])==charSet.end())
26              {
27                  charSet.insert(s[i]);
28              }
29              else{
30                  return false;
31              }
32         }    
33         return true;
34     }
35 };

2.动态规划     

    稍加思考,很容易发现本题是一个具有最优子结构的最优解问题,所以可以用动态规划的方法;

   2.1 定义状态

     dp[i]: 字符串s中,以字符s[i]结尾的最长的不重复子串的长度;

   2.2  转态转移 (方程)

     当s[i]和以s[i-1]结尾的最长不重复子串中所有的字符都不相同时,dp[i] =  dp[i-1]+1;否则,找以s[i-1]结尾的最长不重复子串中和s[i]重复的最后的字符位置index,则

dp[i]=i-1-index+1; 动态规划的时间O(n^2),空间O(n^2);

参考代码如下:

 1 class Solution {
 2 public:
 3     int lengthOfLongestSubstring(string s) {
 4         const int size = s.size();
 5         if(s.empty()) return 0;
 6         if(size<=1) return size;
 7         int dp[size];//dp[i]表示以s[i]结尾的不重复子串
 8         memset(dp,0,size);
 9         int res = 1;
10         dp[0]=1;//dp初始值
11         for(int i = 1;i<size;++i)
12         {
13             bool reapeat = false;
14             int index = 0;
15             for(int j = i-1;j>= i-1-dp[i-1]+1;j--)
16             {
17                  if(s[j]==s[i])
18                  {
19                      reapeat = true;
20                      index = j ;//最近的一次重复
21                      break;
22                  }       
23             }
24             if(reapeat)//重复
25             {
26                 dp[i]=i-1-index+1;
27             }
28             else //不重复
29             {
30                 dp[i]=dp[i-1]+1;
31             }
32             if(dp[i]>=res)
33                 res=dp[i];
34         }
35         return res;
36     }
37 };

3.滑动窗口+hash表 法

   可以在遍历字符串 的过程中维护一个滑动窗口,使得滑动窗口内的子串表示字符串的当前最长不重复子串,具体地,使用hashmap  m记录字符串中字符和其最后一次出现的位置之间的映射;定义变量left,res,left表示滑动窗口的左边界的前一个位置,初始化为-1,res表示滑动窗口的长度最大值;

  遍历到一个字符s[i]时,如果字符在hashmap中,且最后一次的位置在滑动窗口内(i>left),将滑动窗口的边界右移,left = m[s[i]];然后更新hash表,

 使用滑动窗口+hash表的方法可以实现最好的效率,因为只需遍历一遍,所以时间O(n),因为字符的个数是一定的(最多128个)所有空间O(1),

   

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
       int res = 0,left = -1,str_len=s.size();//
       unordered_map<int,int> m;//映射字符中字符和其最后一次出现的位置
       for(int i = 0;i< str_len;++i)
       {
           if(m.count(s[i])&&m[s[i]]>left){
               left = m[s[i]];
           }
            m[s[i]] = i;//更新字符最后一次出现的位置
            res = max(res,i-left);
       }
       return res;
    }
};

    下面这种写法是上面解法的精简模式,这里我们可以建立一个 256 位大小的整型数组来代替 HashMap,这样做的原因是 ASCII 表共能表示 256 个字符,但是由于键盘只能表示 128 个字符,所以用 128 也行,然后全部初始化为 -1,这样的好处是不用像之前的 HashMap 一样要查找当前字符是否存在映射对了,对于每一个遍历到的字符,直接用其在数组中的值来更新 left,因为默认是 -1,而 left 初始化也是 -1,所以并不会产生错误,这样就省了 if 判断的步骤,其余思路都一样:

   

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        vector<int> m(128, -1);
        int res = 0, left = -1;
        for (int i = 0; i < s.size(); ++i) {
            left = max(left, m[s[i]]);
            m[s[i]] = i;
            res = max(res, i - left);
        }
        return res;
    }
};
posted @ 2020-01-07 11:42  谁在写西加加  阅读(144)  评论(0编辑  收藏  举报