滑動視窗(Sliding Window)演算法介紹

Xu-DongHui發表於2019-02-26

前言

最近刷到leetCode裡面的一道演算法題,裡面有涉及到Sliding windowing演算法,因此寫一篇文章稍微總結一下


演算法題介紹

沒有重複字元的子字元的最大長度:給一個字串,獲得沒有重複字元的最長子字元的長度
例子:
輸入:"abcabcbb"
輸出:3
解釋:因為沒有重複字元的子字元是'abc',所以長度是3

解法1:暴力解法

    public class Solution {//時間複雜度高O(n3)
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        int ans = 0;
        //遍歷所有的子字串,記錄沒有重複字母的子字串的最大的長度
        //獲取子字串時,使用兩個標籤,分別代表子字串的開頭和結尾
        for (int i = 0; i < n; i++)
            for (int j = i + 1; j <= n; j++)
                //當子字串沒有重複字母時,ans記錄最大的長度
                if (allUnique(s, i, j)) ans = Math.max(ans, j - i);
        return ans;
    }
    //判斷該子字串是否有重複的字母
    public boolean allUnique(String s, int start, int end) {
        //HashSet實現了Set介面,它不允許集合中出現重複元素。
        Set<Character> set = new HashSet<>();
        for (int i = start; i < end; i++) {
            Character ch = s.charAt(i);
            if (set.contains(ch)) return false;
            set.add(ch);
        }
        return true;
    }
}   
複製程式碼

分析

時間複雜度:O(n3).

解法2:滑動視窗演算法

通過使用HashSet作為一個滑動視窗,檢查一個字元是否已經存在於現有的子字元中只需要O(1).
滑動視窗經常作為一個抽象的概念來處理陣列/字串問題。視窗代表著一組資料/字串元素,通過開頭和結尾的索引來定義視窗。

public class Solution {//時間複雜度O(2n)
  //滑動視窗演算法
    public int lengthOfLongestSubstring(String s) {
        int n = s.length();
        Set<Character> set = new HashSet<>();
        int ans = 0, i = 0, j = 0;
        while (i < n && j < n) {//視窗的左邊是i,右邊是j,下列演算法將視窗的左右移動,擷取出其中一段
            // try to extend the range [i, j]
            if (!set.contains(s.charAt(j))){//如果set中不存在該字母,就將j+1,相當於視窗右邊向右移動一格,左邊不動
                set.add(s.charAt(j++));
                ans = Math.max(ans, j - i);//記錄目前存在過的最大的子字元長度
            }
            else {//如果set中存在該字母,則將視窗左邊向右移動一格,右邊不動,直到該視窗中不存在重複的字元
                set.remove(s.charAt(i++));
            }
        }
        return ans;
    }
}
複製程式碼

分析

時間複雜度:O(2n)。在最差的情況下,每個字元將會被訪問兩次

解法3:優化的滑動視窗演算法

上面的滑動視窗演算法最多需要2n的步驟,但這其實是能被優化為只需要n步。我們可以使用HashMap定義字元到索引之間的對映,然後,當我們發現子字串中的重複字元時,可以直接跳過遍歷過的字元了。

public class Solution {//時間複雜度o(n)
    public int lengthOfLongestSubstring(String s) {
        int n = s.length(), ans = 0;
        //使用hashmap記錄遍歷過的字元的索引,當發現重複的字元時,可以將視窗的左邊直接跳到該重複字元的索引處
        Map<Character, Integer> map = new HashMap<>(); // current index of character
        // try to extend the range [i, j]
        for (int j = 0, i = 0; j < n; j++) {//j負責向右邊遍歷,i根據重複字元的情況進行調整
            if (map.containsKey(s.charAt(j))) {//當發現重複的字元時,將字元的索引與視窗的左邊進行對比,將視窗的左邊直接跳到該重複字元的索引處
                i = Math.max(map.get(s.charAt(j)), i);
            }
            //記錄子字串的最大的長度
            ans = Math.max(ans, j - i + 1);
            //map記錄第一次遍歷到key時的索引位置,j+1,保證i跳到不包含重複字母的位置
            map.put(s.charAt(j), j + 1);
        }
        return ans;
    }
}
複製程式碼

分析

時間複雜度:O(n)

滑動視窗演算法總結

  • 滑動視窗演算法可以用以解決陣列/字串的子元素問題
  • 滑動視窗演算法可以將巢狀的for迴圈問題,轉換為單迴圈問題,降低時間複雜度

相關文章