滑動窗口(Sliding Window)問題常常使用快慢指針(slow, fast pointer)
[0, slow) 的區域爲滑動窗口已經探索過的區域
[slow, fast]的區域爲滑動窗口正在探索的區域
(fast, end of array)爲待探索的區域api
Sliding Window的問題主要分爲:
fixed size sliding window 和 dynamic size sliding window數組
fixed size sliding window: 當快指針增長的時候慢指針必須增長
non-fixed size sliding window: 快指針增長,慢指針不必定變化數據結構
使用滑動窗口能夠線性時間解決問題並且能夠減小空間消耗app
Fixed Length Sliding Window:
1.Strstr:
Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.
Input: haystack = "hello", needle = "ll"
Output: 2
要求找到短字符串在的起始位置在長字符串中的位置
因此只須要保持一個fixed sliding window的長度爲短字符串的長度而後掃長字符串來尋找起始位置ide
class Solution{ public int strStr(String long, String short) { //sanity check if(long == null || short == null) return -1; int i = 0; int j = needle.length(); while(i <= haystack.length() - needle.length() && j <= haystack.length()) { if(haystack.substring(i, j).equals(needle)) { return i; } i++; j++; } return -1; } }
2.Repeated DNA Sequennce
All DNA is composed of a series of nucleotides abbreviated as A, C, G, and T, for example: "ACGAATTCCG". When studying DNA, it is sometimes useful to identify repeated sequences within the DNA.
Write a function to find all the 10-letter-long sequences (substrings) that occur more than once in a DNA molecule.
Given s = "AAAAACCCCCAAAAACCCCCCAAAAAGGGTTT",
Return:
["AAAAACCCCC", "CCCCCAAAAA"]
這道題給一個鹼基序列,要求咱們返回在given的鹼基序列中重複的鹼基序列
因此這道題咱們能夠用一個定長的滑動窗口,每次去match在given的鹼基序列中任意的position從而返回所用出現過的重複的鹼基序列,能夠用一個HashSet的數據結構來判斷是否已經檢查過已經出現的序列指針
class Solution{ public List<String> repeatedDNASequence(String s) { HashSet<String> window = new HashSet<String>(); HashSet<String> repeated = new HashSet<String>(); for(int i = 0; i < s.length() - 9; i++) { if(!window.add(s.substring(i, i + 10))) { repeated.add(s.substring(i, i + 10)); } } return new ArrayList<String>(repeated); } }
Non-fixed Size Sliding-Window
3.find all anagrams of shortString in longString
Given a string s and a non-empty string p, find all the start indices of p's anagrams in s.Strings consists of lowercase English letters only and the length of both strings s and p will not be larger than 20,100.The order of output does not matter.
Example 1:
Input:s: "cbaebabacd" p: "abc"
Output:[0, 6]
Explanation:
The substring with start index = 0 is "cba", which is an anagram of "abc".
The substring with start index = 6 is "bac", which is an anagram of "abc".
Example 2:
Input: s: "abab" p: "ab"
Output: [0, 1, 2]
Explanation:
The substring with start index = 0 is "ab", which is an anagram of "ab".
The substring with start index = 1 is "ba", which is an anagram of "ab".
The substring with start index = 2 is "ab", which is an anagram of "ab".
這道題是尋找input長字符串中全部出現子串的起始字母在長字符串中的位置
由於咱們須要找到長字符串中全部match子串的字符串而且返回須要match的字串中第一個字母在長字符串中的位置,因此須要用一個動態的滑動窗口慢指針在match的子字符串的第一個字母在長字符串中的位置,快指針在最後一個match的字母在長字符串中的位置, 而後須要一個hashmap來記錄每一個字母出現的頻率,利用length來teminatecode
class Solution{ public List<Integer> findAnagrams(String s, String p) { //sanity check List<Integer> res = new ArrayList<Integer>(); //count the frequency of each appeared character Map<Character, Integer> map = new HashMap<Character, Integer>(); for(char c : p.toCharArray()) { map.put(c, map.getOrDefault(0, c) + 1); } int fast = 0; int slow = 0; int count = map.size();//記錄全部出現過字符的頻率 //update fast pointer while(fast < s.length()) { char c = s.charAt(fast); if(map.containsKey(s.charAt(fast)) { map.put(c, map.get(fast) - 1); if(map.get(c) == 0) count--; } fast++; //update slow pointer while(count == 0) { char temp = s.charAt(slow); if(map.containsKey(temp)) { map.put(temp, map.get(temp) + 1)); if(map.get(temp) > 0) count++; } //store res; if(fast - slow == p.length()) { res.add(slow); } slow++; } } return res; } }
4.Maximum Value of size K subarray
Given an array nums, there is a sliding window of size k which is moving from the very left of the array to the very right. You can only see the k numbers in the window. Each time the sliding window moves right by one position.
這題要求找到given數組中任意定長的滑動窗口中數的最大值,所以須要考慮一個數據結構能夠在移動的滑動窗口中找到最大值,所以有幾種想法:
1.在定長的滑動窗口裏維持一個最大堆,所以咱們能夠用constant時間去找到最大值,可是考慮到每次heapify的時間須要O(logn),因此找到k個最大值須要花費O(klogn)的時間
2.仍是一樣在定長的滑動窗口裏維持一個treeset,可是考慮到每次在treeset中添加或者刪除元素須要花費O(logn)的時間,因此是否存在一個數據結構能夠在線性時間內獲得定長滑動窗口裏的最大值?
3.於是,想到了雙端隊列(Deque),能夠維持一個遞增的雙端隊列
EX:[|1, 4|, 5, 3, 9], k = 3
咱們先將k-1個元素放入隊列:|2|
而後從第k個元素開始,一次加入新元素並刪除舊元素,而且保持滑動窗口的size不變
[|1, 4, 5|, 3, 9], Deque: 5, Output: [5];
[1, |4, 5, 3|, 9], Deque: 5, 5, Output: [5, 5];
[1, 4, |5, 3, 9|], Deque: 8, Output: [5, 5, 8];
由於對於每一個數組中的元素只掃描一次,因此整體時間在deque操做中也近似於線性,因此總運行時間:O(n)(amortized), 空間複雜度:O(1)隊列
class slidingWindowMax{ public void inQueue(Deque<Integer> deque, int k) { while(!deque.isEmpty() && deque.peekLast() < k) { deque.pollLast(); } deque.offerLast(num); } public void outQueue(Deque<Integer> deque, int k) { if(deque.peekFirst() == k) { deque.pollFirst(); } } public int[] maxSlidingWindow(int[] nums, int k) { List<Integer> ans = new ArrayList<Integer>(); Deque<Integer> deque = new ArrayDeque<Integer>(); if(nums == null || nums.length == 0) { return new int[]{}; } for(int i = 0; i < k - 1; i++) { inQueue(deque, nums[i]); } for(int i = k - 1; i < nums.length; i++) { inQueue(deque, nums[i]); res.add(deque.peekFirst()); outQueue(deque, nums[i - k + 1]);//delete old element } int[] res = new int[ans.size()]; int h = 0; for(int num : res) { res[h++] = num; } return res; } }