3.无重复字符的最长子串

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

  示例:输入: s = "abcabcbb"  

       输出: 3 

       解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3

  思路1:遍历字符串的每个字符,如果字符在哈希表中没有,就添加到哈希表中去,直到已经在哈希表中存在的即找到重复的了,记录最长的字符长度,清空哈希表,然后再从第2个字符开始向后遍历,以此类推直到遍历到最后一个字符结束,时间复杂度为O(n²)。

class Solution {
        public int lengthOfLongestSubstring(String s) {
            HashSet<Character> set = new HashSet<>();
            int maxLen = 0;
            for (int i = 0; i < s.length(); i++) {
                int j = i;
                for (; j < s.length(); j++) {
                    if (set.contains(s.charAt(j))) {
                        set.clear();
                        break;
                    }
                    set.add(s.charAt(j));
                }
                maxLen = Math.max(maxLen, j - i);
            }
            return maxLen;
        }
}

  思路2:在思路1的基础上优化,假设从i遍历到k时,k重复了,按照上面的方法是j要从i+1重新开始遍历,实际上是没有必要的,因为i+1到k-1之间是没有重复的,只需要从k接着向后遍历就可以了,因此j可以从k的位置继续向后,只需要将i向后移动一个即可,但要注意的是这种情况我们复用了之前存储在哈希表中的值,因此当i向后移动一个时要将前面的从哈希表中去除。时间复杂度为O(n)。

class Solution {
        public int lengthOfLongestSubstring(String s) {
            HashSet<Character> set = new HashSet<>();
            int maxLen = 0;
            int j = 0;
            for (int i = 0; i < s.length(); i++) {
                //当i向后移动一个,就将前面的字符从哈希表中移除
                if (i != 0) {
                    set.remove(s.charAt(i - 1));
                }
                for (; j < s.length(); j++) {
                    if (set.contains(s.charAt(j))) {
                        break;
                    }
                    set.add(s.charAt(j));
                }
                //可以用while替换上面的for循环
                /*while (j < s.length()) {
                    if (set.contains(s.charAt(j))) {
                        break;
                    }
                    set.add(s.charAt(j));
                    j++;
                }*/
                maxLen = Math.max(maxLen, j - i);
            }
            return maxLen;
        }
    }    

  思路三:在思路二的基础上,考虑用HashMap同时存储字符和它的下标,这样当遇到重复字符时,可以直接将i定位到这个重复字符的后面,而不是定位到i+1,但是同样的也要将前面的字符从哈希表中移除,因此还需要循环同样的次数去执行remove操作,但是整体的循环次数是减小的,省去了一些重复的判断过程。时间复杂度同样为O(n)。

    class Solution {
        public int lengthOfLongestSubstring(String s) {
            HashMap<Character, Integer> map = new HashMap<>();
            int maxLen = 0;
            int i = 0;
            int j = 0;
            //记录移动之前的i的位置
            int index;
            while (true) {
                while (j < s.length()) {
                    if (map.containsKey(s.charAt(j))) {
                        break;
                    }
                    map.put(s.charAt(j), j);
                    j++;
                }
                maxLen = Math.max(maxLen, j - i);
                if (j == s.length()) {
                    break;
                }
                index = i;
                i = map.get(s.charAt(j)) + 1;
                //将前面的字符从哈希表中移除
                for (int k = index; k < i; k++) {
                    map.remove(s.charAt(k));
                }
            }
            return maxLen;
        }
    }

  更进一步地,还可以直接用数组来代替哈希表,因为char类型的数据可以转换成整数,从题目中给的输入来看,一个长度为128的int或short数组应该可以满足,而数组的下标就代表char类型的数据,元素的值代表在字符串中的索引,这样可以获得更高的效率、占用更小的空间。

  总结:本题的最佳解决思路就是采用滑动窗口,思路二和三就是,把i当成左窗口边界,j当成右窗口边界,开始窗口长度为0,i、j都在下标为0的位置,此时j依次向右滑,当遇到重复字符的时候,就将i向右滑,只不过思路二是每次i向右滑一步,而思路三是i直接滑到重复字符的下一个,也就是让窗口中的内容满足要求。代码实现上,我觉得思路三更符合滑动串口的思路,而思路二实现起来则更简洁。

  【最佳】后来在题解中看到一种更巧妙的解法,不需要remove map中的字符。

  链接: 画解算法:3. 无重复字符的最长子串 - 无重复字符的最长子串 - 力扣(LeetCode) (leetcode-cn.com)

class Solution {
            public int lengthOfLongestSubstring(String s) {
                int len = s.length();
                int maxLen = 0;
                HashMap<Character, Integer> map = new HashMap<>();
                for (int left = 0, right = 0; right < len; right++) {
                    if (map.containsKey(s.charAt(right))) {
                        /*不能只是将left赋值为map.get(s.charAt(right)) + 1
                        因为如"abba"当left=2时,即在第2个b的位置时,right向后只像最后一个a时,
                        判断map.containsKey(s.charAt(right)会为true,而这个位置(0)其实是在当前的left=2前面
                        此时left=1,right=3,会得到错误的结果maxLen=right-left+1=3-1+1=3 
                        因此要将left始终赋值为更大的那一个*/
                        left = Math.max(left, map.get(s.charAt(right)) + 1);
                    }
                    map.put(s.charAt(right), right);
                    maxLen = Math.max(maxLen, right - left + 1);
                }
                return maxLen;
            }
        }              

 

posted @ 2020-12-23 15:26  ADvancedCZ  阅读(114)  评论(0编辑  收藏  举报