← 返回文章列表

leetcode-靈神跟練-滑動視窗與雙指標-不定長滑動視窗-越長越合法+求最短+最小

跟練不定長滑動視窗,討論合法條件與最短視窗的求解。

209. 長度最小的子陣列

題面

給定一個含有 n 個正整數的陣列和一個正整數 target 。

找出該陣列中滿足其總和大於等於 target 的長度最小的 子陣列 [numsl, numsl+1, …, numsr-1, numsr] ,並返回其長度。如果不存在符合條件的子陣列,返回 0 。

示例 1:

輸入:target = 7, nums = [2,3,1,2,4,3]

輸出:2

解釋:子陣列 [4,3] 是該條件下的長度最小的子陣列。

示例 2:

輸入:target = 4, nums = [1,4,4]

輸出:1

示例 3:

輸入:target = 11, nums = [1,1,1,1,1,1,1,1]

輸出:0

提示:

1 <= target <= 10^9

1 <= nums.length <= 10^5

1 <= nums[i] <= 10^4

思路說明

採用雙指標變長滑動視窗的思路

設定左指標,控制視窗大小,遍歷陣列的index作為右指標

設定變數統計視窗內的數字和

當數字和達到要求的時候

  • 採用while迴圈
  • 更新字陣列的最小值
  • 將左邊的值排除視窗,更新數字和,同時將左指標向右移動
  • 直到數字和不滿足要求結束迴圈

題解

// 209.长度最小的子数组
package main

import (
	"fmt"
	"math"
)

func minSubArrayLen(target int, nums []int) int {
	ans, left := math.MaxInt, 0
	cnt := 0

	for i, num := range nums {
		cnt += num
		for cnt >= target {
			ans = min(ans, i-left+1)
			cnt -= nums[left]
			left += 1
		}
	}

	if ans > len(nums) {
		return 0
	} else {
		return ans
	}
}

func main() {
	testCases := []struct {
		nums   []int
		target int
	}{
		{[]int{2, 3, 1, 2, 4, 3}, 7},
		{[]int{1, 4, 4}, 4},
		{[]int{1, 1, 1, 1, 1, 1, 1, 1}, 11},
	}

	for _, testCase := range testCases {
		fmt.Println(minSubArrayLen(testCase.target, testCase.nums))
	}
}

2904. 最短且字典序最小的美麗子字串

題面

給你一個二進位制字串 s 和一個正整數 k 。

如果 s 的某個子字串中 1 的個數恰好等於 k ,則稱這個子字串是一個 美麗子字串 。

令 len 等於 最短 美麗子字串的長度。

返回長度等於 len 且字典序 最小 的美麗子字串。如果 s 中不含美麗子字串,則返回一個 空 字串。

對於相同長度的兩個字串 a 和 b ,如果在 a 和 b 出現不同的第一個位置上,a 中該位置上的字元嚴格大於 b 中的對應字元,則認為字串 a 字典序 大於 字串 b 。

例如,“abcd” 的字典序大於 “abcc” ,因為兩個字串出現不同的第一個位置對應第四個字元,而 d 大於 c 。

示例 1:

輸入:s = “100011001”, k = 3

輸出:“11001”

解釋:示例中共有 7 個美麗子字串:

  1. 子字串 “100011001” 。
  2. 子字串 “100011001” 。
  3. 子字串 “100011001” 。
  4. 子字串 “100011001” 。
  5. 子字串 “100011001” 。
  6. 子字串 “100011001” 。
  7. 子字串 “100011001” 。

最短美麗子字串的長度是 5 。

長度為 5 且字典序最小的美麗子字串是子字串 “11001” 。

示例 2:

輸入:s = “1011”, k = 2

輸出:“11”

解釋:示例中共有 3 個美麗子字串:

  1. 子字串 “1011” 。
  2. 子字串 “1011” 。
  3. 子字串 “1011” 。

最短美麗子字串的長度是 2 。

長度為 2 且字典序最小的美麗子字串是子字串 “11” 。

示例 3:

輸入:s = “000”, k = 1

輸出:""

解釋:示例中不存在美麗子字串。

提示:

1 <= s.length <= 100

1 <= k <= s.length

思路說明

我們要做的是找到一個子字串,子字串中1的數量為看,如果有多個答案,返回字典序最小的那個

首先根據題目,當字串中1的數量小於k的時候,會返回空字串,直接返回

然後初始化結果ans為輸入字串s,因為最差的結果就是s,所以設定為s

之後設定cnt表示視窗內1的數量,left表示視窗的左邊界,用迴圈的index表示右邊界

每次移動右邊界之後

  • 判斷視窗內1的數量是不是達到要求
  • 如果達到了,開始移動左邊界,直到把最左邊的1排出
  • 這個過程中,左邊可能有很多0,排出之後會減小子字串的長度,所以不斷更新子字串以及左邊界,這是還要注意子字串長度相同選最小字典序的情況
  • 這個過程透過while迴圈實現

題解

// 2904.最短且字典序最小的美丽子字符串
package main

import (
	"fmt"
	"strings"
)

func shortestBeautifulSubstring(s string, k int) string {
	if strings.Count(s, "1") < k {
		return ""
	}
	ans := s
	cnt, left := 0, 0

	for i, ch := range s {
		if ch == '1' {
			cnt += 1
		}

		for cnt >= k {
			tmp := s[left : i+1]
			if s[left] == '1' {
				cnt -= 1
			}
			left += 1
			if len(tmp) == len(ans) {
				if tmp < ans {
					ans = tmp
				}
			}
			if len(tmp) < len(ans) {
				ans = tmp
			}
		}
	}

	return ans
}

func main() {
	testCases := []struct {
		s string
		k int
	}{
		{"100011001", 3},
		{"1011", 2},
		{"000", 1},
	}

	for _, testCase := range testCases {
		fmt.Println(shortestBeautifulSubstring(testCase.s, testCase.k))
	}
}

1234. 替換子串得到平衡字串

題面

有一個只含有 ‘Q’, ‘W’, ‘E’, ‘R’ 四種字元,且長度為 n 的字串。

假如在該字串中,這四個字元都恰好出現 n/4 次,那麼它就是一個「平衡字串」。

給你一個這樣的字串 s,請透過「替換一個子串」的方式,使原字串 s 變成一個「平衡字串」。

你可以用和「待替換子串」長度相同的 任何 其他字串來完成替換。

請返回待替換子串的最小可能長度。

如果原字串自身就是一個平衡字串,則返回 0。

示例 1:

輸入:s = “QWER”

輸出:0

解釋:s 已經是平衡的了。

示例 2:

輸入:s = “QQWE”

輸出:1

解釋:我們需要把一個 ‘Q’ 替換成 ‘R’,這樣得到的 “RQWE” (或 “QRWE”) 是平衡的。

示例 3:

輸入:s = “QQQW”

輸出:2

解釋:我們可以把前面的 “QQ” 替換成 “ER”。

示例 4:

輸入:s = “QQQQ”

輸出:3

解釋:我們可以替換後 3 個 ‘Q’,使 s = “QWER”。

提示:

1 <= s.length <= 10^5

s.length 是 4 的倍數

s 中只含有 ‘Q’, ‘W’, ‘E’, ‘R’ 四種字元

思路說明

構建一個最小視窗,讓視窗外的每種字元數量儘量接近n,這個最小視窗就是需要替換的字串

透過雜湊表統計每種字元的數量

題解

// 1234.替换子串得到平衡字符串
package main

import "fmt"

func balancedString(s string) int {
	// 构建哈希表,统计各自的数量
	// 构建一个最小窗口,让窗口外的每种字符数量尽量接近n,这个最小窗口就是需要替换的字符串
	mp := make(map[byte]int, 4)
	n := len(s) / 4

	for i := 0; i < len(s); i++ {
		mp[s[i]] += 1
	}

	// 已经平衡,返回0
	if mp['Q'] == n && mp['W'] == n && mp['E'] == n && mp['R'] == n {
		return 0
	}

	ans, left := len(s), 0

	for i := 0; i < len(s); i++ {
		mp[s[i]] -= 1 // 放进窗口
		for mp['Q'] <= n && mp['W'] <= n && mp['E'] <= n && mp['R'] <= n {
			// 满足条件,记录并收缩
			ans = min(ans, i-left+1)
			mp[s[left]] += 1
			left += 1
		}
	}

	return ans
}

func main() {
	testCases := []string{
		"QWER",
		"QQWE",
		"QQQW",
		"QQQQ",
	}

	for _, testCase := range testCases {
		fmt.Println(balancedString(testCase))
	}
}

2875. 無限陣列的最短子陣列

題面

給你一個下標從 0 開始的陣列 nums 和一個整數 target 。

下標從 0 開始的陣列 infinite_nums 是透過無限地將 nums 的元素追加到自己之後生成的。

請你從 infinite_nums 中找出滿足 元素和 等於 target 的 最短 子陣列,並返回該子陣列的長度。如果不存在滿足條件的子陣列,返回 -1 。

示例 1:

輸入:nums = [1,2,3], target = 5

輸出:2

解釋:在這個例子中 infinite_nums = [1,2,3,1,2,3,1,2,…] 。

區間 [1,2] 內的子陣列的元素和等於 target = 5 ,且長度 length = 2 。

可以證明,當元素和等於目標值 target = 5 時,2 是子陣列的最短長度。

示例 2:

輸入:nums = [1,1,1,2,3], target = 4

輸出:2

解釋:在這個例子中 infinite_nums = [1,1,1,2,3,1,1,1,2,3,1,1,…].

區間 [4,5] 內的子陣列的元素和等於 target = 4 ,且長度 length = 2 。

可以證明,當元素和等於目標值 target = 4 時,2 是子陣列的最短長度。

示例 3:

輸入:nums = [2,4,6,8], target = 3

輸出:-1

解釋:在這個例子中 infinite_nums = [2,4,6,8,2,4,6,8,…] 。

可以證明,不存在元素和等於目標值 target = 3 的子陣列。

提示:

1 <= nums.length <= 10^5

1 <= nums[i] <= 10^5

1 <= target <= 10^9

思路說明

使用的nuns的數量是不不定的,可能是1-n個,但是可以觀察到,如果使用n個,中間n-2個是完整的,左邊nums和右邊nums,分別取右邊和左邊連續的資料

設 nums的資料和事total

target = total*m + res

res = nums*2[i:j]

我們要做的就是找到res的長度

res = target & total

其他的按照固定步驟做即可

題解

// 2875.无限数组的最短子数组
package main

import (
	"fmt"
	"math"
)

func minSizeSubarray(nums []int, target int) int {
	total := 0
	for _, x := range nums {
		total += x
	}

	ans := math.MaxInt
	left, sum, n := 0, 0, len(nums)
	for right := 0; right < n*2; right++ {
		sum += nums[right%n]
		for sum > target%total {
			sum -= nums[left%n]
			left++
		}
		if sum == target%total {
			ans = min(ans, right-left+1)
		}
	}
	if ans == math.MaxInt {
		return -1
	}
	return ans + target/total*n
}

func main() {
	testCases := []struct {
		nums   []int
		target int
	}{
		{[]int{1, 2, 3}, 5},
		{[]int{1, 1, 1, 2, 3}, 4},
		{[]int{2, 4, 6, 8}, 3},
	}

	for _, testCase := range testCases {
		fmt.Println(minSizeSubarray(testCase.nums, testCase.target))
	}
}

76. 最小覆蓋子串

題面

給你一個字串 s 、一個字串 t 。返回 s 中涵蓋 t 所有字元的最小子串。如果 s 中不存在涵蓋 t 所有字元的子串,則返回空字串 "" 。

注意:

對於 t 中重複字元,我們尋找的子字串中該字元數量必須不少於 t 中該字元數量。

如果 s 中存在這樣的子串,我們保證它是唯一的答案。

示例 1:

輸入:s = “ADOBECODEBANC”, t = “ABC”

輸出:“BANC”

解釋:最小覆蓋子串 “BANC” 包含來自字串 t 的 ‘A’、‘B’ 和 ‘C’。

示例 2:

輸入:s = “a”, t = “a”

輸出:“a”

解釋:整個字串 s 是最小覆蓋子串。

示例 3:

輸入: s = “a”, t = “aa”

輸出: ""

解釋: t 中兩個字元 ‘a’ 均應包含在 s 的子串中,

因此沒有符合條件的子字串,返回空字串。

提示:

m == s.length

n == t.length

1 <= m, n <= 10^5

s 和 t 由英文字母組成

思路說明

先說一下題目的要求,找到s的子串,子串包含t中所有字元(種類和數量)

首先,我們要知道t中有哪些字元

然後,在s中找到子串,子串中包含t中所有種類字元,這個子串儘可能小

所以使用雜湊表統計t中所有字元種類和數量

然後在s中找子串,如果子串中字元在雜湊表中,就減去,如果雜湊表全部變成0,表示子串中已經包含t中全部字元,然後縮小視窗,直到不滿足要求

題解

// 76.最小覆盖子串
package main

import (
	"fmt"
	"math"
)

func minWindow(s string, t string) string {
	// 先统计一下t中各个字符的数量,统计一下字符的种类
	mp := make(map[byte]int)

	for i := 0; i < len(t); i++ {
		mp[t[i]] += 1
	}
	left := 0
	cnt := len(mp) // 字符的种类
	ans := ""
	tmp := math.MaxInt

	for i := 0; i < len(s); i++ {
		if _, ok := mp[s[i]]; ok { // 字符有效
			mp[s[i]] -= 1
			if mp[s[i]] == 0 { // 符合要求
				cnt -= 1
			}
		}

		for cnt == 0 { // 子串中已经满足要求,缩小子串范围
			if tmp > i-left+1 {
				tmp = i - left + 1
				ans = s[left : i+1]
			}
			if _, ok := mp[s[left]]; ok { // 字符有效
				mp[s[left]] += 1
				if mp[s[left]] == 1 { // 符合要求
					cnt += 1
				}
			}
			left += 1
		}
	}

	return ans
}

func main() {
	testCases := []struct {
		s string
		t string
	}{
		{"ADOBECODEBANC", "ABC"},
		{"a", "a"},
		{"a", "aa"},
	}

	for _, testCase := range testCases {
		fmt.Println(minWindow(testCase.s, testCase.t))
	}
}

632. 最小區間

題面

你有 k 個 非遞減排列 的整數列表。找到一個 最小 區間,使得 k 個列表中的每個列表至少有一個數包含在其中。

我們定義如果 b-a < d-c 或者在 b-a == d-c 時 a < c,則區間 [a,b] 比 [c,d] 小。

示例 1:

輸入:nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]

輸出:[20,24]

解釋:

列表 1:[4, 10, 15, 24, 26],24 在區間 [20,24] 中。

列表 2:[0, 9, 12, 20],20 在區間 [20,24] 中。

列表 3:[5, 18, 22, 30],22 在區間 [20,24] 中。

示例 2:

輸入:nums = [[1,2,3],[1,2,3],[1,2,3]]

輸出:[1,1]

提示:

nums.length == k

1 <= k <= 3500

1 <= nums[i].length <= 50

-105 <= nums[i][j] <= 10^5

nums[i] 按非遞減順序排列

思路說明

構建pair,包含數字和位置,然後排序,構建一個滑動視窗,視窗包含所有index並且視窗最小

題解

// 632.最小区间
package main

import (
	"fmt"
	"slices"
)

func smallestRange(nums [][]int) []int {
	type pair struct{ x, i int } // 数字和他们所在的位置
	pairs := []pair{}

	for i, arr := range nums {
		for _, num := range arr {
			pairs = append(pairs, pair{num, i})
		}
	}

	slices.SortFunc(pairs, func(a, b pair) int { return a.x - b.x })

	ansL, ansR := pairs[0].x, pairs[len(pairs)-1].x
	mp := make(map[int]int)
	left := 0

	for _, p := range pairs {
		r, i := p.x, p.i
		mp[i] += 1

		for len(mp) == len(nums) { // 每个列表都至少包含一个数
			l, i := pairs[left].x, pairs[left].i
			if r-l < ansR-ansL {
				ansL, ansR = l, r
			}
			mp[i] -= 1
			if mp[i] == 0 {
				delete(mp, i)
			}
			left += 1
		}
	}
	return []int{ansL, ansR}
}

func main() {
	testCases := [][][]int{
		[][]int{
			[]int{4, 10, 15, 24, 26},
			[]int{0, 9, 12, 20},
			[]int{5, 18, 22, 30},
		},
		[][]int{
			[]int{1, 2, 3},
			[]int{1, 2, 3},
			[]int{1, 2, 3},
		},
	}

	for _, testCase := range testCases {
		fmt.Println(smallestRange(testCase))
	}
}
關於這篇文章的日期

保留舊站原始文章標註的日期;實際開始寫作的時間可能更早。

原始日期: 2025-11-16 17:00:16 +0800

同一系列 · 滑動視窗與雙指標

上一篇leetcode-靈神跟練-滑動視窗與雙指標-定長滑動視窗-基礎 ←