刷题笔记 | 剑指offer-JZ48(最长不含重复字符的子字符串)

题目描述

请从字符串中找出一个最长的不包含重复字符的子字符串,计算该最长子字符串的长度。
数据范围:s.length≤40000

方法一:滑动窗口+哈希表

思路:

  • 使用滑动窗口,保证窗口内部元素都是不重复的,然后窗口右边界不断向右滑,如果窗口内出现重复字符,说明刚加入的元素与窗口内某元素相同了,只需收缩窗口左边界到重复元素的下一个字符处,就可使得窗口内元素重新保持不重复。
  • 使用哈希表来保证窗口内的元素不重复,key值为窗口内的元素,value值为其出现的次数,只要新加入窗口的元素出现次数不为1,即表示重复。

当新加入元素重复,窗口左边界右移,同时在哈希表中要其经过的元素频率减1,保证哈希表中的频率都是窗口内元素的频率。

// 直到left移动到重复的元素s[right]处,并将其频率减一,left++到其下一个元素,循环结束
while(mp[s[right] > 1]) 
    mp[s[left++]]--;

代码实现

int lengthOfLongestSubstring(string s) {
    unordered_map<char, int> mp;          //哈希表记录窗口内非重复的字符
    int res = 0;
    for(int left = 0, right = 0; right < s.length(); right++){  //left:左边界;right:右边界
        mp[s[right]]++;                   //窗口右移进入哈希表统计出现次数
        while(mp[s[right]] > 1)           //出现次数大于1,则窗口内有重复
            mp[s[left++]]--;              //窗口左移,同时减去该字符的出现次数
        res = max(res, right - left + 1); //维护子串长度最大值
    }
    return res;
}

方法二:动态规划+哈希表

思路:

  • 如果对于某个前面的字串,新加入一个字符,与之前的都不重复,那么最长子串就是在之前的基础上长度+1;如果与之前的重复了,那么长度就是当前位置减去它重复之前字符出现的位置。
  • 哈希表的key值记录出现过的字符,value值记录其出现的位置。

具体做法:

  • dp[i]表示以下标i为结尾的字符串最长不含重复子串的长度。
  • 遍历字符串,若哈希表没有出现过即不重复,dp[i] = dp[i-1] + 1。
  • 若哈希表出现过,则当前元素重复,考虑dp[i] = i - mp[s[i]]。但由于每次跟新重复字符后,其之前出现的字符依然在哈希表中用于判断是否重复,所以实际仍需考虑其前一个字符的基础,即dp[i] = min(dp[i-1] + 1, i - mp[s[i]]).
    • 之前重复的元素之前的字符(如果后面没出过即为更新过)其i - mp[s[i]] 一定大于 dp[i-1] + 1,对于更新后的子串其实不算做重复字符,dp[i]取的为dp[i-1] + 1;
    • 而在上次重复元素后更新过的字符(即出现在更新后子串中的),其i - mp[s[i]] 一定小于 dp[i-1] + 1, 那么子串重复,更新子串,dp[i]取 i - mp[s[i]];
假设:dp[i] = i - mp[s[i]]

    a b c c d a                      mp a b c
dp  1 2 3                               0 1 2
i   0 1 2 3 4 5

dp[3] = 3 - 2 = 1
mp[c] = 3  //更新mp

    a b c c d a                      mp a b c d
dp  1 2 3 1 2                           0 1 3 4
i   0 1 2 3 4 5

dp[5] = 5 - 0 = 5
mp[a] = 5

    a b c c d a                      mp a b c d 
dp  1 2 3 1 2 5                         5 1 3 4
i   0 1 2 3 4 5

然而最长不重复子串为  c d a 长度为3,明显与事实不符。
而考虑其前一个的基础,即dp[i] = min(dp[i-1] + 1, i - mp[s[i]])
dp[5] = min(2 + 1, 5 - 0) = 3 符合结果。

实现代码:

int lengthOfLongestSubstring(string s) {
    unordered_map<char,int> mp;
    int res = 1;  //子串长度最短为1
    vector<int> dp(s.length(),0); //dp[i]表示以下标i结尾的字符串最长不含重复子串的长度
    dp[0] = 1;       //第一个字符作为子串长度为1
    mp[s[0]] = 0;    //位置为0
    for(int i = 1; i < s.length(); ++i){
        if(mp.find(s[i]) == mp.end())
            dp[i] = dp[i-1] + 1;
        else
            dp[i] = min(dp[i-1] + 1,i - mp[s[i]]);
        mp[s[i]] = i;  //更新mp
        res = max(res,dp[i]);
    }
    return res;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值