算法题解题记录——Longest Substring Without Repeating Characters(leetCode#3-medium)

本文由作者三汪首发于。
历史解题记录已同步更新github.

算法题解题记录——Longest Substring Without Repeating Characters(leetCode#3-medium)_第1张图片
改进版solution提交结果.png

题目

Problem Description:
Given a string, find the length of the longest substring without repeating characters.

Examples:

  • Given "abcabcbb", the answer is "abc", which the length is 3.
  • Given "bbbbb", the answer is "b", with the length of 1.
  • Given "pwwkew", the answer is "wke", with the length of 3.
    Note that the answer must be a substring, "pwke" is a subsequence and not a substring.

题目分析

题意是让我们返回给定字符串不含重复字符的最大子字符串的长度。

代码和思路

我会对自己的原始提交版本做个记录。
没耐心看的同学可以跳过原始版本直接去看后面的B和C,
分别是我对当前Java solution最优解的分析和我自己的改进版本。

A.原始版本

看到这个题目的时候,我是把它当成字符串处理题来做的。
没注意看题目要求返回的是符合要求的最大子字符串长度。
因此第一个版本做了很多字符处理,Runtime也很感人,用了90ms,排位在12.61%的位置。

1.0版本代码如下:
    private int lengthOfLongestSubstring_1_0(String str){
        if (str==null || str.equals("")) {
            return 0;
        }
        String[] subs = new String[str.length()];
        int subsIndex = 0;
        String[] chars = str.split("");
        StringBuffer sb = new StringBuffer(chars[0]);
        for (int i = 1; i < chars.length; i++) {
            if (!sb.toString().contains(chars[i])) {
                sb.append(chars[i]);
            }else if(sb.indexOf(chars[i]) != sb.length()-1){
                subs[subsIndex++] = sb.toString();
                sb.delete(0,sb.indexOf(chars[i])+1);
                sb.append(chars[i]);
            }else{
                subs[subsIndex++] = sb.toString();
                sb.delete(0, sb.length());
                sb.append(chars[i]);
            }
        }
        subs[subsIndex++] = sb.toString();
        int maxLength = subs[0].length();
        for (int i = 1; i < subs.length; i++) {
            if (subs[i] == null) {
                return maxLength;
            }
            if (subs[i].length() > maxLength) {
                maxLength = subs[i].length();
            }
        }
        return maxLength;
    }
思路分析:

这段代码的思路是先把给定字符串分解成单个字符数组,再依次写入StringBuffer。

当出现重复字符时,根据重复字符在字符串中的位置分两种情况处置。
情况一、重复字符不是当前字符串的最后一个字符时:
将当前StringBuffer中拼好的字符串写入String[]数组,删除从字符串开端到重复字符位置的所有字符,从重复字符的后一个字符为字符串的开端,继续拼接字符串。
情况二、重复字符是当前字符串的最后一个字符时:
将当前StringBuffer中拼好的字符串写入String[]数组,清空StringBuffer,重新开始新的字符串拼接。

最后遍历String[]数组,获取长度最大的字符串的长度并返回。

不足之处:

在这个版本的代码中我有一个很大的弊病是我忘了String可以字节调用.toCharArray()方法获取char[]数组,而是用了.split("")来把给定字符串分割成一个个由单个字符组成的字符串。

B.改进版本

改进版本不再使用字符串拼接的方法来实现。
而是使用一个快指针(Runner)和一个慢指针(Walker)来遍历由给定字符串分解的char[]数组,直接获取最大子字符串长度。
这种方式Runtime只有36ms,排位在99.94%的位置。

值得一提的是,这个改进版本是在看了一会儿会分析的当前Java最优解以后结合自己思考而来的。
我通过写自己的改进版来理解最优解的代码思路。这是一个小心得,你也可以试试。

1.1版本代码如下:
    private int lengthOfLongestSubstring_1_1(String str){
        //It's my own habit to check if the input is empty.
        if (str == null) {
            return 0;
        }
        char[] chars = str.toCharArray();
        if (chars.length<2) {
            return chars.length;
        }
        int maxLength = 0,spliter = 0;
        for (int i = 1; i < chars.length; i++) {
            for (int j = spliter ; j < i ; j++) {
                if (chars[i]==chars[j]) {
                    int tempLength = i-spliter;
                    maxLength = maxLength > tempLength ? maxLength : tempLength;
                    spliter = j+1;
                    break;
                }
            }
        }
        maxLength = maxLength > chars.length-spliter ? maxLength : chars.length-spliter;
        return maxLength;
    }
思路分析:
  1. 当给定字符串长度小于2时,直接返回它的长度。
  2. 使用快指针和慢指针遍历给定字符串分解而来的char[]数组
    2.1 使用spliter来记录重复字符的下一个字符的位置(重复字符和它前面的字符对新子字符串没有意义),初始值为0。
    2.2 使用maxLength来记录每次遍历时子字符串的最大长度。当有新的最大子字符串产生(遇到重复字符或结束遍历)时,判断其长度是否大于maxLength。若是,则更新maxLength。
  3. 我不知道我真的解释清楚了没,虽然我觉得我解释清楚了。如果正在看的你哪里不理解,欢迎留言讨论:)

C.截至此刻LeetCode上用时最少的Java Solution

代码如下:
    /**
     * 
     * 

* Description:The best solution on LeetCode by this time.
* Runtime:35ms
*

* @version 0.1 2017年12月8日 * @param s * @return * int */ private int lengthOfLongestSubstring_best(String s) { char[] chars = s.toCharArray(); if(2 > chars.length){ return chars.length; } int max = 0; int split_at = 0; int cur_len = 1; for(int i=1;i max) max = cur_len; } return max; }
思路分析:
  1. 主体思路同改进版本思路,下面来讲一些细节方面的不同。
  2. 使用cur_len字段来记录当前子字符串长度,初始值为1。
    使用split_at字段来记录上一次碰到的重复字符的下一个字符的位置,初始值为0。
    使用max字段记录每次遍历时子字符串的最大长度。
  3. 当出现重复字符时,break跳出快指针循环(即内层for循环,快指针为j)。
    结束内层for循环以后,判断快指针与慢指针(慢指针为i)是否相等。
    当出现重复字符时,快指针与慢指针是不会相等的。因为会break跳出循环。
    3.1 若快指针与慢指针相等,则当前子字符串没有遇到重复字符。cur_len自增。
    3.2 若快指针小于慢指针,则不含重复字符的当前子字符串长度为cur_len=i-j,更新split_at字段。
    3.3 快指针不可能大于慢指针。因为内层for循环条件是j
  4. 每次结束外层循环时,判断cur_len是否大于max。若是,则更新max。
  5. 我不知道我真的解释清楚了没,虽然我觉得我解释清楚了。如果正在看的你哪里不理解,欢迎留言讨论:)

以上。
希望我的文章对你能有所帮助。
我不能保证文中所有说法的百分百正确,
但我能保证它们都是我的理解和感悟以及拒绝直接复制黏贴(确实需要引用的部分我会附上源地址)。
有什么意见、见解或疑惑,欢迎留言讨论。

你可能感兴趣的:(算法题解题记录——Longest Substring Without Repeating Characters(leetCode#3-medium))