算法实战(三)无重复字符的最长子串

谁说胖子不能爱 提交于 2019-11-28 01:52:28

一.前言

  今天开始第三题,这个题目有点绕,我一开始都看错了两次题目,最后面才弄清楚到底是要算什么。我自己先是想了一下思路,用的方法虽然和网上大部分用的不太一样,但是核心思想是一样的(我想到的也是优化的滑动窗口,但是我使用的时StringBulider来存储,没有去使用map,list等,所以耗时更长),下面我们就一起来看看题目。

二.题目

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

  示例1:输入: "abcabcbb"

       输出: 3

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

  示例2:输入: "bbbbb"

       输出: 1

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

  示例3:输入: "pwwkew"

       输出: 3

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

   请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。

三.解题思路

  首先要明白这个题的意思,就是要我们求字符串的不重复的连续子串,首先必须是连续的,不能断开,其次子串中不能有一个字母是重复的。弄清楚了题目的意思后,让我们来看看解法。

  1)暴力法:暴力法总是最容易让人明白的方法,首先我们拿到字符串的所有子串,这个通过两次循环可以实现拿到所有字串的区间,这里我们采用[i,j)(左闭右开)的区间然后我们实现一个方法,这个方法用来判断传入的字符串在一段区间内是否存在重复,这个可以借助set来实习。假设这段区间内的字串没有重复字符,那么我们就要去对比更新最大字串的值。代码如下:

 1 public class Solution {
 2     public int lengthOfLongestSubstring(String s) {
 3         int sum = 0;
 4         int length =  s.length();
 5         for(int i = 0; i < length; i++){
 6             for(int j = i + 1; j <= length; j++){
 7                 sum = uniqueStr(s, i , j) && sum < j - i ? sum = j - i : sum; 
 8             }
 9         }
10         return sum;
11     }
12     
13     public boolean uniqueStr(String s, int i, int j){
14         Set<Character> set = new HashSet();
15         for(int n = i; n < j; n++){
16             if(set.contains(s.charAt(n))){
17                 return false;
18             }
19             set.add(s.charAt(n));
20         }
21         return true;
22     }
23 }

  2)2.1 滑动窗口法:这里我们要引入一个概念---窗口,窗口其实就是类似于上面提到的左闭右开的一个区间。那么为什么叫做滑动窗口呢?大家可以看到,上面的暴力法,我们做了很多无用的计算,在区间[i,j)中,假如我们判断是存在重复字符,那么以i开头,并且结尾大于j的所有区间都是存在重复字符的,这个时候我们就没必要进行判断了,可以直接进行i+1的操作,所以大家会发现,要么j+1,要么i+1,整个区间就像是在滑动一样,所以就要滑动窗口。代码如下:

 1 public class Solution {
 2     public int lengthOfLongestSubstring(String s) {
 3         int sum = 0;
 4         int length =  s.length();
 5         int begin = 0;
 6         int end = 0;
 7         while(begin < length && end < length){
 8             if(uniqueStr(s, begin, ++end)){
 9                 sum = sum < end - begin ? end - begin : sum;
10             }else{
11                 begin++;
12             }
13             
14         }
15         return sum;
16     }
17     
18     public boolean uniqueStr(String s, int i, int j){
19         Set<Character> set = new HashSet();
20         for(int n = i; n < j; n++){
21             if(set.contains(s.charAt(n))){
22                 return false;
23             }
24             set.add(s.charAt(n));
25         }
26         return true;
27     }
28 }

    2.2 上面的滑动窗口法,虽然已经可以通过测试了,但是在计算一个字符串是否重复,要消耗O(n^2)的时间复杂度,于是我们可以使用一个全局的set来替代这个滑动窗口,让检查是否重复变成O(1)的操作。代码如下:

 1 public class Solution {
 2     public int lengthOfLongestSubstring(String s) {
 3         int sum = 0;
 4         int length =  s.length();
 5         int begin = 0;
 6         int end = 0;
 7         Set<Character> set = new HashSet();
 8         while(begin < length && end < length){
 9             if(!set.contains(s.charAt(end))){
10                 set.add(s.charAt(end++));
11                 sum = sum < end - begin ? end - begin : sum;
12             }else{
13                 set.remove(s.charAt(begin++));
14             }
15         }
16         return sum;
17     }
18 }

    2.3 优化版滑动窗口:其实有人可能已经发现了,还可以继续优化,当我们在【i,j)区间是无重复字符区间的时间,第J个字符,在这个区间是重复的,假设重复的位置是K(i <= k < j),那么其实我们可以直接将窗口跳至【k+1,j),这时我们不当当需要记录字符串,还需要记录其对应的位置,所以我们需要使用一个map来保存,代码如下:

 1 public class Solution {
 2     public int lengthOfLongestSubstring(String s) {
 3         int n = s.length(), ans = 0;
 4         Map<Character, Integer> map = new HashMap<>(); // current index of character
 5         // try to extend the range [i, j]
 6         for (int j = 0, i = 0; j < n; j++) {
 7             if (map.containsKey(s.charAt(j))) {
 8                 i = Math.max(map.get(s.charAt(j)), i);
 9             }
10             ans = Math.max(ans, j - i + 1);
11             map.put(s.charAt(j), j + 1);
12         }
13         return ans;
14     }
15 }

    2.4 因为map在滑动窗口左边界移动时,不好删除左边界之前的元素,所以这个窗口需要我们从逻辑上理解,为了更直观的简直,我们还可以使用LinkedList来替代map,代码如下:

 1 class Solution 
 2 {
 3     public int lengthOfLongestSubstring(String s)
 4     {
 5         int sum = 0;//记录最长子串长度
 6         LinkedList<Character> temp = new LinkedList<>();
 7 
 8         for (int i = 0; i < s.length(); i++ )
 9         {
10             Character curCh =  s.charAt(i);
11             if (!temp.contains(curCh)){
12                 temp.add(curCh);
13                 sum = sum < temp.size() ? temp.size() : sum;
14             }
15             else//如果新增字符与原子串中字符有重复的,删除原子串中重复字符及在它之前的字符,与新增字符组成新的子串
16             {
17                 int index = temp.indexOf(curCh);
18                 for (int j = 0;j <= index;j++ ){
19                     temp.remove();
20                 }
21                 temp.add(curCh);
22             }
23         }
24         return sum;
25     }
26 }

    熬夜写完了这篇文章,上面的方法可能还不是最优的,如果大家有什么疑问或者有更好的方法,欢迎留言讨论,谢谢!

  

  

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!