Java LeetCode每日一题-从易到难带你领略算法的魅力(四):无重复字符的最长子串

LeetCode每日一题 专栏收录该内容
21 篇文章 2 订阅

1.题目要求

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

2.题目示例

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

3.提示

  1. 0 <= s.length <= 5 * 104
  2. s 由英文字母、数字、符号和空格组成

4.解题

4.1 解题思路

  1. 这道题的题目很短很容易理解,所以在解题思路上我们从示例下手,首先在示例中选取一个例子(示例三),然后自己模拟程序跑的情况
    “pwwkew”
    起始位置(加粗)—最长不重复字符串
    pwwkew—pw
    pwwkew—w
    pwwkew—wke
    pwwkew—kew
    pwwkew—ew
    pwwkew—w
  2. 通过上面的思路我们可以知道,在起始位置不断递增的情况下,结束的位置也是在不断递增的,所以我们在程序汇中可以使用两个指针来代表我们思路中的起始和结束位置
  3. 一般想到这里,我们就可以去想是不是可以用“滑动窗口”来解决问题,使用for循环来指定起始指针位置,在for循环中不断的移动右指针,直到出现重复字符或者到达字符串末尾,每一次for循环代表起始位置+1,也代表着新的一轮循环的开始,在每一次循环之后我们记录下这个子串的长度
  4. for循环结束后我们就得到了最长的子串的长度
  5. 解决了字符串遍历的问题后,就是解决重复子串的问题。一般涉及到去重的话,我们会考虑到用哈希表来解决问题
  6. 在起始指针(S)向右移动(+1)的时候,我们就删除哈希表中起始指针(S)当前位置的字符,在结尾指针向右移动的时候,我们往哈希表中添加结尾指针当前位置的字符

4.2 业务代码

class Solution {
    public int lengthOfLongestSubstring(String s) {
        Set<Character> hs = new HashSet<Character>();
        int n = s.length();
        // -1 的意思是还未开始移动
        int rk = -1, ans = 0;
        for (int i = 0; i < n; ++i) {
            if (i != 0) {
                hs.remove(s.charAt(i - 1));
            }
            //判断哈希表中有无重复以及有无越界
            while (rk + 1 < n && !hs.contains(s.charAt(rk + 1))) {
                hs.add(s.charAt(rk + 1));
                ++rk;
            }
            ans = Math.max(ans, rk - i + 1);
        }
        return ans;
    }
}

4.3 运行结果

结果

5.优化

5.1 优化思路

  1. 这里我们使用HashMap来进行优化,可以通过键值对的方式直接记录各个元素和对应下标,就可以一次性把起始指针放到重复元素的下一个鸽子,就不用像刚刚那样挨着移动
  2. 如"abba",当 j = 2 即指向第二个b的时候,发现元素重复,i 更新为重复的b的下一个格子即 i = map.get(s.charAt(2)) + 1 = 2
  3. 当 j = 3 即指向第二个a的时候,发现元素重复,i 更新为重复的a的下一个格子即 i = map.get(s.charAt(3)) + 1 = 1,这就不对了,新的左边界在当前滑动窗口的左边、外面(此时就不应该更新左边界),所以应取重复元素的下一个格子下标和 i 这两者之间的最大值

5.2 优化业务代码

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        Map<Character, Integer> map = new HashMap<>();
        int i = 0, j = 0;
        int max = 0;
        while(j < n) {
            if (map.containsKey(s.charAt(j))) {
                //直接将起始指针右移到重复字符的后一格,如果重复元素在边界i左边则i不变
                i = Math.max(map.get(s.charAt(j)) + 1, i);
            }
            map.put(s.charAt(j), j++); // 新元素会覆盖重复元素
            max = Math.max(max, j - i);
        }
        return max;
    }
}

5.3 优化结果

结果

6.总结

  • 优化出来结果还是比较明显的,2ms、0.3MB的差距还是比较满意的。如果大家有更好的想法可以直接在下面留言哟。
  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

打赏
文章很值,打赏犒劳作者一下
相关推荐
©️2020 CSDN 皮肤主题: 鲸 设计师:meimeiellie 返回首页

打赏

地球村公民

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值