十五周算法訓(xùn)練營(yíng)——滑動(dòng)窗口
// 滑動(dòng)窗口算法解題思路
1. 使用雙指針技巧,初始化left=right=0,把索引左閉右開區(qū)間[left, right)稱為一個(gè)窗口
2. 先不斷增加right指針,擴(kuò)大窗口
3. 當(dāng)結(jié)果不符合要求,進(jìn)行窗口收縮
4. 重復(fù)2、3步,直到終止條件
和為s的連續(xù)正數(shù)序列
輸入一個(gè)正整數(shù) target ,輸出所有和為 target 的連續(xù)正整數(shù)序列(至少含有兩個(gè)數(shù))。
序列內(nèi)的數(shù)字由小到大排列,不同序列按照首個(gè)數(shù)字從小到大排列。
示例 1:
輸入:target = 9 輸出:[[2,3,4],[4,5]]
// 該問題是一個(gè)滑動(dòng)窗口問題
// 滑動(dòng)窗口算法解題思路
// 1. 使用雙指針技巧,初始化left=right=0,把索引左閉右開區(qū)間[left, right)稱為一個(gè)窗口
// 2. 先不斷增加right指針,擴(kuò)大窗口
// 3. 當(dāng)結(jié)果不符合要求,進(jìn)行窗口收縮
// 4. 重復(fù)2、3步,直到終止條件
function findContinuousSequence(target) {
const result = [];
// 滑動(dòng)窗口
const window = [];
let sum = 0;
const middle = (target + 1) << 1;
let left = 1;
let right = 1;
for (let i = 1; i <= middle; i++) {
// 擴(kuò)充窗口
window.push(i);
sum += i;
right++;
// 判斷是否收縮窗口
while (sum > target) {
// 進(jìn)行窗口收縮
const temp = window.shift();
left++;
sum -= temp;
}
if (sum === target && window.length > 1) {
result.push([...window]);
}
}
return result;
}
console.log(findContinuousSequence(9));
最長(zhǎng)不含重復(fù)字符的子字符串
請(qǐng)從字符串中找出一個(gè)最長(zhǎng)的不包含重復(fù)字符的子字符串,計(jì)算該最長(zhǎng)子字符串的長(zhǎng)度。
示例 1:
輸入: "abcabcbb" 輸出: 3 解釋: 因?yàn)闊o(wú)重復(fù)字符的最長(zhǎng)子串是 "abc",所以其長(zhǎng)度為 3。
/**
* 最長(zhǎng)不含重復(fù)字符的子字符串
*/
// 可以通過滑動(dòng)窗口解決
function lengthOfLongestSubstring(s) {
// 滑動(dòng)窗口
const window = {};
// 左右指針
let left = 0;
let right = 0;
let result = 0;
for (let i = 0; i < s.length; i++) {
const c = s.charAt(i);
// 擴(kuò)充窗口
window[c] = window[c] ? window[c] + 1 : 1;
right++;
// 判斷是否收縮窗口
while (window[c] > 1) {
// 進(jìn)行窗口收縮
const leftC = s.charAt(left);
window[leftC]--;
left++;
}
result = Math.max(result, right - left);
}
return result;
}
const s = 'abcabcbb';
console.log(lengthOfLongestSubstring(s));
長(zhǎng)度最小的子數(shù)組
給定一個(gè)含有 n 個(gè)正整數(shù)的數(shù)組和一個(gè)正整數(shù) target 。
找出該數(shù)組中滿足其和 ≥ target 的長(zhǎng)度最小的 連續(xù)子數(shù)組 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其長(zhǎng)度。如果不存在符合條件的子數(shù)組,返回 0 。
示例 1:
輸入:target = 7, nums = [2,3,1,2,4,3]
輸出:2
解釋:子數(shù)組 [4,3] 是該條件下的長(zhǎng)度最小的子數(shù)組。
// 滑動(dòng)窗口
function minSubArrayLen(target, nums) {
let left = 0;
let right = 0;
let sum = 0;
let result = Infinity;
while (right < nums.length) {
// 更新狀態(tài)
sum += nums[right];
right++;
// 收縮窗口
while (sum >= target) {
result = Math.min(result, right - left);
const presentVal = nums[left];
// 更新狀態(tài)
sum -= presentVal;
left++;
}
}
return result === Infinity ? 0 : result;
}
無(wú)重復(fù)字符的最長(zhǎng)子串
一、題目
給定一個(gè)字符串 s ,請(qǐng)你找出其中不含有重復(fù)字符的 最長(zhǎng)子串 的長(zhǎng)度。
示例 1:
輸入: s = "abcabcbb" 輸出: 3 解釋: 因?yàn)闊o(wú)重復(fù)字符的最長(zhǎng)子串是 "abc",所以其長(zhǎng)度為 3。
二、題解
function lengthOfLongestSubstring(s) {
// 滑動(dòng)窗口
const window = {};
// 滑動(dòng)窗口的兩個(gè)指針
let left = 0;
let right = 0;
// 結(jié)果
let result = 0;
// 循環(huán)終止條件
while (right < s.length) {
const c = s[right];
// 進(jìn)行窗口內(nèi)數(shù)據(jù)的一系列更新
window[c] = window[c] ? window[c] + 1 : 1;
// 移動(dòng)窗口右側(cè)
right++;
// 判斷左側(cè)窗口是否要收縮
while (window[c] > 1) {
const d = s[left];
// 左側(cè)窗口收縮
left++;
// 進(jìn)行窗口內(nèi)的一系列更新
window[d]--;
}
// 更新答案
result = Math.max(result, right - left);
}
return result;
}
const s = 'abcabcbb';
console.log(lengthOfLongestSubstring(s));
字符串排列
一、題目
給你兩個(gè)字符串 s1 和 s2 ,寫一個(gè)函數(shù)來(lái)判斷 s2 是否包含 s1 的排列。如果是,返回 true ;否則,返回 false 。
換句話說(shuō),s1 的排列之一是 s2 的 子串 。
示例 1:
輸入:s1 = "ab" s2 = "eidbaooo" 輸出:true 解釋:s2 包含 s1 的排列之一 ("ba").
二、題解
/**
* 算法思路:
* 滑動(dòng)窗口
* s2包含s1的最小子串,然后最小子串長(zhǎng)度跟s1長(zhǎng)度相等
*/
function checkInclusion(s1, s2) {
// 需要湊齊的字符
const need = {};
for (let i = 0; i < s1.length; i++) {
need[s1[i]] = need[s1[i]] ? need[s1[i]] + 1 : 1;
}
// 滑動(dòng)窗口
const window = {};
// 滑動(dòng)窗口的兩端
let left = 0;
let right = 0;
// 表示窗口中滿足need部分的字符數(shù)
let valid = 0;
while (right < s2.length) {
const c = s2[right];
// 進(jìn)行窗口內(nèi)數(shù)據(jù)的一系列更新
if (need[c]) {
window[c] = window[c] ? window[c] + 1 : 1;
if (window[c] === need[c]) {
valid++;
}
}
// 移動(dòng)窗口右側(cè)
right++;
// 判斷左側(cè)窗口是否要收縮(當(dāng)s1字符和滑動(dòng)窗口字符大小相等,此時(shí)就要收縮敞口)
while (right - left >= s1.length) {
// 在這里判斷是否找到了合法的子串
if (valid === Object.keys(need).length) {
return true;
}
const d = s2[left];
left++;
// 進(jìn)行窗口內(nèi)數(shù)據(jù)的一系列更新
if (need[d] !== undefined) {
if (window[d] === need[d]) {
valid--;
}
window[d]--;
}
}
}
return false;
}
const s1 = 'ab';
const s2 = 'eidbaooo';
console.log(checkInclusion(s1, s2));
滑動(dòng)窗口的最大值
給你一個(gè)整數(shù)數(shù)組 nums,有一個(gè)大小為 k 的滑動(dòng)窗口從數(shù)組的最左側(cè)移動(dòng)到數(shù)組的最右側(cè)。你只可以看到在滑動(dòng)窗口內(nèi)的 k 個(gè)數(shù)字?;瑒?dòng)窗口每次只向右移動(dòng)一位。
返回 滑動(dòng)窗口中的最大值 。
示例 1:
輸入:nums = [1,3,-1,-3,5,3,6,7], k = 3 輸出:[3,3,5,5,6,7] 解釋: 滑動(dòng)窗口的位置最大值
[1 3 -1] -3 5 3 6 7 3 1 [3 -1 -3] 5 3 6 7 3 1 3 [-1 -3 5] 3 6 7 5 1 3 -1 [-3 5 3] 6 7 5 1 3 -1 -3 [5 3 6] 7 6 1 3 -1 -3 5 [3 6 7] 7
該問題用單調(diào)隊(duì)列解決(單調(diào)隊(duì)列可以解決滑動(dòng)窗口問題)。
// 首先建立一個(gè)單調(diào)隊(duì)列的類
class MonotonicQueue {
constructor() {
this.queue = [];
}
// 在隊(duì)尾添加元素n
// 該函數(shù)在加入元素時(shí),會(huì)將其前面比自己小的元素全部刪除掉
push(n) {
// 將前面小于自己的元素全部刪除掉
while (this.queue.length && n > this.queue[this.queue.length - 1]) {
this.queue.pop();
}
this.queue.push(n);
}
// 對(duì)頭元素如果是n,則刪除它
pop(n) {
if (this.queue.length > 0 && n === this.queue[0]) {
this.queue.shift();
}
}
// 返回當(dāng)前隊(duì)列中的最大值
// 因?yàn)閜ush元素時(shí)都會(huì)將比自己小的刪除掉,最終結(jié)果就是一個(gè)遞減的順序,則最大值就是隊(duì)列首部?jī)?nèi)容
max() {
return this.queue[0];
}
}
function maxSlidingWindow(nums, k) {
// 實(shí)例化一個(gè)單調(diào)隊(duì)列
const monotonicQueue = new MonotonicQueue();
const result = [];
for (let i = 0; i < nums.length; i++) {
// 先填滿整個(gè)滑動(dòng)窗口的k-1個(gè)元素
if (i < k - 1) {
monotonicQueue.push(nums[i]);
} else {
// 窗口向前滑動(dòng),添加新元素
monotonicQueue.push(nums[i]);
// 記錄當(dāng)前窗口的最大值
result.push(monotonicQueue.max());
// 移除隊(duì)列首部元素
monotonicQueue.pop(nums[i - k + 1]);
}
}
return result;
}