5/2 LeetCode每日一题 3. 无重复字符的最长子串

给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。

示例 1: 输入: “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”,所以其长度为 3。 示例 2:
输入: “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “b”,所以其长度为 1。 示例 3: 输入:
“pwwkew” 输出: 3 解释: 因为无重复字符的最长子串是 “wke”,所以其长度为 3。 请注意,你的答案必须是 子串
的长度,“pwke” 是一个子序列,不是子串。

QRcode

每天同步在公众号 算法学习笔记 中更新,欢迎各位关注!
分析:

这是一道非常经典的双指针法中的滑动窗口问题。

解法1:暴力法

首先很容易想到暴力法:设置首尾两个指针 i , j :

for (i in s)
  for (j in s[i:])
      for (k in s[i:j])
        for (l in s[k:j])
           if (s[k] == s[l])
             len=1;
           else
              len++;
           res = max(len,res);

时间复杂度。。。爆炸了;空间复杂度 O(1)

那么稍微聪明点的做法就是把有无重复元素判断封装起来,用桶或者哈希表之类的方式实现。

for (i in s)
  for (j in s[i:])
      if (s[i:j]中无重复元素)
        res = max (res,j-i+1)

但是时间复杂度还是O(n²)

对于这类线性结构的问题,我们应该尽可能想办法把O(n²)的时间复杂度降为O(nlogn)甚至更低。

解法2:滑动窗口+哈希桶/哈希表

在上述解法中,计算机做了大量重复而无用的计算和判断工作。比如:在判断s[i+1,j]中是否存在重复字符和s[i,j]是否存在重复字符这二者的工作中,这种算法二者之间没有联系起相互之间的关系。

以[ i , j ]递推到[ i , j+1 ]为例:我们大可不必先用O(n)时间先证明一次[ i , j ]中有无重复元素,再用O(n)时间证明一次[ i , j+1 ]有无重复,这样是浪费算力。

如果[ i , j ]中无重复元素的话,我们可以将[ i , j ]范围内所有的字符值记录在某个容器中,再判断s[j+1]是否在容器中即可。

如果[ i , j ]中有重复元素的话,把s[i]对应的键值减少1,再让i++,判断容器中所有的字符是否唯一。若不唯一,重复该操作,直到唯一为止。
这就是滑动窗口(Sliding Windows)的核心思想,它也可以理解为 DP的一种形式。LeetCode上有相当多的题目都是这种套路。用伪码来描述就是

l = 0, r = 1 ,s[l] s[r]都丢入容器
for (;r < s.len;r++)
  while (s[r+1]在容器中)
      容器中的s[l]--
      l++
   r++
   将s[r]丢入容器
   res = max(res,r-l+1)

这里的算法利用了前期计算的结果作为基础,复杂度大大下降,成为线性的O(n),完全可以被接受了。
具体代码实现

class Solution {
public:
    inline int max(int a, int b) { return a > b ? a : b; }
    int lengthOfLongestSubstring(string s) {
        int left = 0, right = 0;
        int len = s.length() - 1;
        if (len == -1)  return 0;
        int res = 1;
        if (len == 0) return 1;//预处理一哈
        bool bucket[128] = { false };//CHAR_MAX是127
        bucket[s[0]] = 1;
        while (right < len)
        {
            while (bucket[s[right + 1]])
            {
                bucket[s[left++]] = 0;//清空桶
            }
            right++;
            bucket[s[right]] = 1;//把右边的值丢入桶
            res = max(res, right - left + 1);
        }
        return res;
    }
};

解法3:哈希表记录前缀值

还可以有别的写法吗?我不想嵌套内层的while循环。

当然可以
利用一个hashmap来存放每个字母最新出现的位置。若hashMap中显示已访问该值,则更新左值。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        vector<int> m(128, -1);
        int res = 0, left = -1;
        for(int i=0;i<s.length();i++){
            left = max(m[s[i]], left);//若已经访问过这个点就更新左值
            m[s[i]] = i;
            res = max(res, i - left);
        }
        return res;
    }
};

时间复杂度同样是O(n)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值