前端搞算法不再難,如何套路解題:滑動窗口類

前言

這不是一個給一道題目而後告訴你題解的系列,而是對於一系列題目進行分類,找出他們解題規律並得出大體框架代碼的文章。吃透解一系列題目的規律比會解單個題目有用的多,畢竟你總會遇到沒刷過的題。算法

正文

你們對於滑動窗口應該不陌生,在 TCP 協議中就有這個概念的出現,用於控制網絡流量,避免擁塞發生。數組

在算法中這個思想也是相似的,多用於解決在一段連續的區間中尋找知足條件的問題,好比說給定一個字符串,尋找出無重複字符的最長子串。該思路主要應用於數組及字符串的數據結構中。markdown

示例

截屏2020-11-04下午10.50.11

滑動窗口主要思路是維護一對指針,在必定條件內右移右指針擴大窗口大小直到窗口內的解不知足題意,此時咱們須要根據狀況移動左指針,重複移動左右指針的操做並在區間內求解,直到雙指針不能再移動。網絡

尋找出無重複字符的最長子串 題目爲例,根據上述的思路解題就會很方便:數據結構

var lengthOfLongestSubstring = function(s) {
    // 用於存儲指針移動過程當中的值
    let map = {}
    // 雙指針
    let left = 0
    let right = 0
    // 結果
    let count = 0
    // 指針移動終止條件
    while (right < s.length) {
        const char = s[right]
        // 根據題意咱們須要尋找不重複的最長子串
        // 當 char 出現時咱們須要移動左指針到重複字符的下一位
        if (char in map) {
            left = Math.max(left, map[char] + 1)
        }
        // 求解
        count = Math.max(count, right - left + 1)
        // 移動右指針並存下索引
        map[char] = right++
    }
    return count
};
複製代碼

此題爲高頻題,你們務必掌握框架

截屏2020-11-05下午10.11.08

框架

根據上題咱們能夠得出一個滑動窗口解題的大體框架的僞代碼,oop

let left = 0
let right = 0
while (right < size) {
    獲取當前索引數據
    right++
    數據更新等操做
    while (窗口須要縮小) {
        left++
        數據移除等操做
    }
}
複製代碼

框架中須要變化的幾點以下:ui

  • 右指針右移後數據的更新
  • 判斷窗口什麼時候須要縮小
  • 左指針右移後數據的更新
  • 根據題目求最優解

接下來咱們根據這個框架代碼來試着解決幾道題目。spa

實戰

209. 長度最小的子數組

解題思路:指針

  1. 移動右指針並將移動後的值累加存儲起來
  2. 當累加值大於 s 時移動左指針縮小窗口,此時須要更新累加值及咱們須要的解
var minSubArrayLen = function(s, nums) {
    // 定義雙指針
    let left = 0
    let right = 0
    // 求解須要用到的變量
    let length = Infinity
    let sum = 0
    // 指針移動終止條件
    while (right < nums.length) {
        // 獲取當前索引數據
        sum += nums[right]
        // 縮小窗口條件
        while (sum >= s) {
            // 求解
            length = Math.min(length, right - left + 1)
            // 縮小窗口
            sum -= nums[left++]
        }
        // 擴大窗口
        right++
    }
    return length === Infinity ? 0 : length
};
複製代碼

這道題目是 Leetcode 的第 209 題,答案能夠說除了小部分的微調以外,基本套用了框架代碼。後續的題目你們能夠繼續跟着這個思路解題,快速掌握經過滑動窗口來解題的套路。

出題頻率

438. 找到字符串中全部字母異位詞

解題思路:

  1. 經過哈希表存儲 p 中的字符出現次數
  2. 移動右指針判斷當前字符是否還符合條件
  3. 不符合條件時移動左指針縮小窗口,此時須要更新哈希表
  4. 當前字符不存在哈希表時說明雙指針能夠直接跳到下一位
var findAnagrams = function(s, p) {
    if (!s.length || !p.length || s.length < p.length) return []
    // 求解須要用到的變量
    const map = {}
    const result = []
    // 定義雙指針
    let left = 0, right = 0
    // 把字符串 p 中的字符經過 hash 存儲起來
    for (let i = 0; i < p.length; i++) {
        const char = p[i]
        if (!(char in map)) {
            map[char] = 0
        }
        map[char] += 1
    }
    // 指針移動終止條件
    while (right < s.length) {
        const char = s[right]
        // map 中存在字符就移動右指針
        if (map[char] > 0) {
            map[char] -= 1
            right++
        // 不然判斷左指針所指向的字符是否存在 map 中
        } else if (map[s[left]] >= 0) {
            map[s[left]] += 1
            left++
        // 不存在的話把左右指針所有挪到下一位
        } else {
            left = right += 1
        }
        // 存儲正確解
        if (right - left === p.length) {
            result.push(left)
        }
    }
    return result
};
複製代碼

出題頻率

76. 最小覆蓋子串

出題頻率

這道題目和以前的 「找到字符串中全部字母異位詞」思路很相似:

  1. 經過哈希表存儲 t 中的字符出現次數
  2. 移動右指針判斷當前字符是否還符合條件
  3. 不符合條件時移動左指針縮小窗口,此時須要更新哈希表
var minWindow = function(s, t) {
    // 定義雙指針
    let left = 0, right = 0
    // 求解須要用到的變量
    let length = Infinity
    let map = {}
    // 遇到 t 中存在的字符時更新 match,注意 t 中存在的字符可能在 s 中出現屢次
    // 所以並非每次都須要更新 match
    let match = 0
    // 記錄最短子串開始位置,不能用 left
    let start = 0
    // 把字符串 t 中的字符經過 hash 存儲起來
    for (let i = 0; i < t.length; i++) {
        const char = t[i]
        if (!(char in map)) {
            map[char] = 0
        }
        map[char] += 1
    }
    // 指針移動終止條件
    while (right < s.length) {
        const char = s[right]
        // 右指針移動時更新數據
        if (char in map) {
            map[char] -= 1
            if (map[char] >= 0) match += 1
        }
        // 縮小窗口條件
        while (match === t.length) {
            // 尋找到更佳解,保存數據
            if (length > right - left + 1) {
                length = right - left + 1
                start = left
            }
            // 移動左指針而且更新數據
            const char = s[left++]
            if (char in map) {
                if (map[char] === 0) match -= 1
                map[char] += 1
            }
        }
        // 移動右指針
        right++
    }
    return length === Infinity ? '' : s.substring(start, start + length)
};
複製代碼

總結

通過上面幾道題目的練習,你們應該能看出滑動窗口的思路多用於解決數組及字符串中子元素的問題。

相關文章
相關標籤/搜索