[LEETCODE]#3 Longest Substring Without Repeating Characters 最长不重复的子串长度, Sliding Window, 滑动窗口

https://leetcode.com/problems/longest-substring-without-repeating-characters/

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

Example 1:

Input: "abcabcbb"
Output: 3 
Explanation: The answer is "abc", with the length of 3. 

Example 2:

Input: "bbbbb"
Output: 1
Explanation: The answer is "b", with the length of 1.

Example 3:

Input: "pwwkew"
Output: 3
Explanation: 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.

03-1.js 暴力方法会超时,不能通过。

/**
 * https://leetcode.com/problems/longest-substring-without-repeating-characters
 * brutal force
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    var n = s.length,
        ans = 0;
    /**
     * @param s String
     * @param start Number
     * @param end Number
     */
    var allUnique = function(s, start, end) {
        var set = new Set();
        for (var i = start; i < end; i++) {
            var ch = s.charAt(i);
            if (set.has(ch)) {
                return false;
            }
            set.add(ch);
        }
        return true;
    };
    for (var i = 0; i < n; i++) {
        for (var j = 1; j <= n; j++) {
            if (allUnique(s, i, j)) {
//                ans = Math.max(ans, j-i);
                if (j-i > ans) {
                    ans = j-i;
                    console.log(s.substr(i, ans));
                }
            }
        }
    }
    return ans;
};

Approach 2: Sliding Window

Algorithm

The naive approach is very straightforward. But it is too slow. So how can we optimize it?

In the naive approaches, we repeatedly check a substring to see if it has duplicate character. But it is unnecessary. If a substring s_{ij}sij​ from index ii to j - 1 is already checked to have no duplicate characters. We only need to check if s[j]s[j] is already in the substring s_{ij}sij​.

To check if a character is already in the substring, we can scan the substring, which leads to an O(n^2)O(n2) algorithm. But we can do better.

By using HashSet as a sliding window, checking if a character in the current can be done in O(1).

A sliding window is an abstract concept commonly used in array/string problems. A window is a range of elements in the array/string which usually defined by the start and end indices, i.e. [i, j)[i,j)(left-closed, right-open). A sliding window is a window "slides" its two boundaries to the certain direction. For example, if we slide [i, j) to the right by 1 element, then it becomes [i+1, j+1)(left-closed, right-open).

Back to our problem. We use HashSet to store the characters in current window [i, j)  (j = i initially). Then we slide the index j to the right. If it is not in the HashSet, we slide j further. Doing so until s[j] is already in the HashSet. At this point, we found the maximum size of substrings without duplicate characters start with index ii. If we do this for all ii, we get our answer.

* 03-2.js

/**
 * https://leetcode.com/problems/longest-substring-without-repeating-characters
 * Sliding Window
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    var set = new Set(), ans = 0;
    var i = 0, j = 0, n = s.length;
    while (i < n && j < n) {
        // try to extend the range [i, j)
        if (! set.has(s.charAt(j)) ) {
            set.add(s.charAt(j++));
            ans = Math.max(ans, j - i);
            // if (j - i > ans) {
            //     ans = j-i;
            //     console.log(s.substr(i, ans));
            // }
        } else {
            set.delete(s.charAt(i++));
        }
    }
    return ans;
};

Approach 3: Sliding Window Optimized

The above solution requires at most 2n steps. In fact, it could be optimized to require only n steps. Instead of using a set to tell if a character exists or not, we could define a mapping of the characters to its index. Then we can skip the characters immediately when we found a repeated character.

The reason is that if s[j] have a duplicate in the range [i, j)  with index j′, we don't need to increase ii little by little. We can skip all the elements in the range [i, j'] and let i to be j' + 1 directly.

03-3.js

/**
 * https://leetcode.com/problems/longest-substring-without-repeating-characters
 * Sliding Window optimized HashMap
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    // @map: current index of character
    var map = new Map(), ans = 0;
    var n = s.length;

    // try to extend the range [i, j]
    for (var j = 0, i = 0; j < n; j++) {
        if (map.has(s.charAt(j))) {
            i = Math.max( map.get(s.charAt(j)), i );
        }
        ans = Math.max(ans, j-i+1);
        map.set(s.charAt(j), j+1);
    }
    return ans;
};

03-4.js

/**
 * https://leetcode.com/problems/longest-substring-without-repeating-characters
 * Sliding Window optimized array(an integer array as direct access table)
 *
 * Commonly used tables are:
 *   int[26] for Letters 'a' - 'z' or 'A' - 'Z'
 *   int[128] for ASCII
 *   int[256] for Extended ASCII
 *
 * @param {string} s
 * @return {number}
 */
var lengthOfLongestSubstring = function(s) {
    var n = s.length, ans = 0;
    // current index of character
    var index = new Array(128);
    for (var i = 0; i < index.length; i++) {
        index[i] = 0;
    }
    // try to extend
    i = 0;
    for (var j = 0; j < n; j++) {
        i = Math.max(index[s.charCodeAt(j)], i);
        ans = Math.max(ans, j - i + 1);
        index[ s.charCodeAt(j) ] = j + 1;
    }
    return ans;
};

* test:

// var s = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!\\\"#$%&'()*+,-./:;<=>?@[\\\\]^_`{|}~ abcdefghijklmnopqrstuvwxyzABCD";
var s = "jhhthogonzpheevzetkvygpvbdhcaisjpbfwslmflbopgmqmfcjdkzbmckqeskpjluhditltvzkdlap";
console.log(lengthOfLongestSubstring(s));

* run:

node 03-4.js

答案详解:

solution: https://leetcode.com/problems/longest-substring-without-repeating-characters/solution/

// Sliding Window in PHP

* Solution.php

<?php

class Solution {
    public static function lengthOfLongestSubstring(string $s) : int {
        $set = new Set();
        $ans = 0;
        $i = $j = 0;
        $n = strlen($s);
        while ($i < $n && $j < $n) {
            // try to extend the range[i, j)
            if ( ! $set->has($s[$j]) ) {
                $set->add( $s[$j++] );
                $ans = max($ans, $j-$i);
            } else {
                $set->delete($s[$i++]);
            }
        }
        return $ans;
    }
}

* Set.php

<?php

class Set {
    /** @var array<string> */
    private $m = [];
    /** @var int */
    public $size = 0;

    public function __construct($size = 0) {
        $this->size = $size;
    }

    public function add($e) : self {
        if (isset($this->m[$e])) {
            return $this;
        }
        $this->m[ $e ] = 0;
        $this->size++;
        return $this;
    }

    public function delete($e) : bool {
        if (isset($this->m[ $e ])) {
            unset($this->m[$e]);
            $this->size--;
            return true;
        }
        return false;
    }

    public function has($e) : bool {
        return isset($this->m[$e]);
    }

    public function __toString() {
        $t = sprintf("Set(%d) {", $this->size);
        $sep = ", ";
        if ($this->size < 1) {
            $t .= "}";
            return $t;
        }
        $stringify = function($s) {
            if (is_string($s)) {
                return "'".addslashes($s)."'";
            }
        };
        // $keys = array_keys($this->m);
        $keys = [];
        array_walk($this->m, function($value, $key) use (&$keys) {
            $keys[] = $key;
        });
        $t .= $stringify($keys[0]);
        for ($i = 1, $n = count($keys); $i < $n; $i++) {
            $t .= $sep . $stringify($keys[$i]);
        }
        $t .= "}";
        return $t;
    }
}

* index.php

<?php
/**
 * Created by PhpStorm.
 * User: Mch
 * Date: 2018-12-09
 * Time: 22:21
 */
function __autoload($className) {
    include $className.'.php';
}

$set = new Set();

$s = "jhhthogonzph";
for ($i = 0, $n = strlen($s); $i < $n; $i++) {
    $set->add($s[$i]);
}
echo $set.PHP_EOL;
$set->delete('z');
echo $set.PHP_EOL;

$s = "jhhthogonzpheevzetkvygpvbdhcaisjpbfwslmflbopgmqmfcjdkzbmckqeskpjluhditltvzkdlap";
echo Solution::lengthOfLongestSubstring($s).PHP_EOL; // 12

$s = "bbbbbbb"; // "b" 1
echo Solution::lengthOfLongestSubstring($s).PHP_EOL;

$s = "abcabcbb"; // "abc" 3
echo Solution::lengthOfLongestSubstring($s).PHP_EOL;

$s = "pwwkew";  // "wke" 3
echo Solution::lengthOfLongestSubstring($s).PHP_EOL;

* test:

$ php index.php

Set(8) {'j', 'h', 't', 'o', 'g', 'n', 'z', 'p'}

Set(7) {'j', 'h', 't', 'o', 'g', 'n', 'p'}

12

1

3

3

猜你喜欢

转载自blog.csdn.net/fareast_mzh/article/details/84929558