• 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;
                }
            }              
  • 相关阅读:
    Sql server 2005 restore failed
    使用Windows Live Writer发布到cnblogs
    IE7 Tab problem
    转: 编码,charset,乱码,unicode,utf8与net简单释义(续)
    移动12.1号动感地带寻宝答案
    转: 各种 lightbox 实现
    Cannot connect windows 2003 server remotely by mstsc
    boost asio 网络编程案例简单改写
    读书笔记之《程序员的自我修养链接、装载与库》
    基于OpenSSL简单实现Shamir基于身份的数字签名算法
  • 原文地址:https://www.cnblogs.com/advancedcz/p/14150784.html
Copyright © 2020-2023  润新知