二、leetcode刷题之【滑动窗口】

[TOC]

Leetcode刷题

3. 无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串 的长度。
输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

输入: s = "pwwkew"
输出: 3
解释: 因为无重复字符的最长子串是 "wke",所以其长度为 3。
     请注意,你的答案必须是 子串 的长度,"pwke" 是一个子序列,不是子串。
//  ======================== 最长区间模板刷题 这种才是最符合的一种解法============
func lengthOfLongestSubstring(s string) int {
    res := 0
    window := make(map[byte]int, 0)
    left, right := 0, 0 // 左右移动指针
    for ; right < len(s); right++ {
        ch := s[right]
        window[ch]++

        for window[ch] > 1 {
            window[s[left]]--
            // 这个if要不要无所谓
            if window[s[left]] == 0 {
                delete(window, s[left])
            }
            left++
        }

        res = max(res, right-left+1)
    }
    return res
}
// ==========  解法  也不太容易理解  ===============
// 左指针代表枚举子串的起始位置
// 右指针表示不包干重复字符串的最长子串的结束位置
func lengthOfLongestSubstring(s string) int {
    // 哈希集合,记录每个字符是否出现过
    m := map[byte]int{}
    n := len(s)
    // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
    right, ans := -1, 0
    for left := 0; left < n; left++ {
        if left != 0 {
            // 左指针向右移动一格,移除一个字符
            delete(m, s[left-1])
        }
        for right+1 < n && m[s[right+1]] == 0 { // 这里意思就是一直挪动右指针,到一个没有重复的时候停下来。
            m[s[right+1]]++
            right++
        }
        // 第 left 到 right 个字符是一个极长的无重复字符子串
        ans = max(ans, right-left+1)
    }
    return ans
}

//  解法,此解法有点高级,可能不好理解
func lengthOfLongestSubstring(s string) int {
    len_s := len(s)
    if len_s == 0 {
        return 0
    }
    tempMap := make(map[byte]int)
    j := 0
    res := 0
    for i := 0; i < len_s; i++ {
        if _, ok := tempMap[s[i]]; ok { // 判断这个key在不在这个map中
            j = max(j, tempMap[s[i]]+1) // 这一步非常关键,为了针对 abba 这种场景。
        }
        tempMap[s[i]] = i
        res = max(res, i-j+1)

    }
    return res
}

159. 至多包含两个不同字符的最长子串 【会员/中等/滑动窗口】

给定一个字符串 s ,找出 至多 包含两个不同字符的最长子串 t ,并返回该子串的长度。
输入: "eceba"
输出: 3
解释: t 是 "ece",长度为3。

输入: "ccaabbb"
输出: 5
解释: t 是 "aabbb",长度为5。
// ====================  解法二  窗口移动,个人推荐解法二,删除map中多余的,维持map 大小=======================
func lengthOfLongestSubstringTwoDistinct(s string) int {
    if len(s) <= 2 {
        return len(s)
    }
    left, right := 0, 0 // 左右移动指针
    maxLen := 0
    tempMap := make(map[byte]int)
    for ; right < len(s); right++ { // 也就是右指针一直在往右边移动,左边指针只有在字符种类大于二的时候才移动

        tempMap[s[right]]++

        for len(tempMap) > 2 {
            tempMap[s[left]]--
            if tempMap[s[left]] == 0 {
                delete(tempMap, s[left])
            }
            left++
        }
        maxLen = maxNum(maxLen, right-left+1)

    }
    return maxLen

}

340. 至多包含 K 个不同字符的最长子串【会员/中等/滑动窗口】

// ========  解法二  窗口移动,个人推荐解法二,删除map中多余的,维持map 大小=======================
func lengthOfLongestSubstringKDistinct(s string, k int) int {
    if len(s) <= k {
        return len(s)
    }

    left, right := 0, 0 // 左右移动指针
    maxLen := 0
    tempMap := make(map[byte]int)
    for ; right < len(s); right++ { // 也就是右指针一直在往右边移动,左边指针只有在字符种类大于二的时候才移动

        tempMap[s[right]]++

        for len(tempMap) > k {
            tempMap[s[left]]--
            if tempMap[s[left]] == 0 {
                delete(tempMap, s[left])
            }
            left++
        }
        maxLen = maxNum(maxLen, right-left+1)

    }
    return maxLen

}

395. 至少有 K 个重复字符的最长子串【中等】


//============= 滑动窗口(直接md)  =================
/*
所以解题思路为:
求字符串包含的字符个数t
依次假设最长字串包含字符数量为1, 2, 3..., t,并求得包含该数量字符的字串最大长度(使用滑动窗口)
在2的结果中找到一个最大长度,即为结果

这里用滑动窗口模板,先将右边界右移一格,然后右移左边界直到符合条件。
我们假设本次的子字符串包含字符数量为t(如前所说),维护如下变量:

滑动窗口左右边界l, r
滑动窗口中每个字符出现次数int[26] cnt
滑动窗口内不同字符总数total
计数器less表示当前出现次数小于k(且不为0)的字符数量。

首先右移一格滑动窗口右边界r
当某个字符的出现次数从 0 增加到 1 时,将 less 加一;
当某个字符的出现次数从 k−1 增加到 k 时,将 less 减一。

然后需要左移左边界l,直到滑动窗口内字符个数total小于等于t(这样得到的子字符串才符合之前的假设)
当某个字符出现次数从 k 减少到 k - 1 时,将 less 加一
当某个字符出现次数从 1 减少到 0 时,将 less 减一。

最后通过less的值判断该滑动窗口内字串是否都出现了至少 k 次(less = 0)
*/

func main() {
    s := "aaabb"
    k := 3
    fmt.Println(longestSubstring(s, k))
}
func longestSubstring(s string, k int) (ans int) {
    if k == 1 {
        return len(s)
    }

    cnt := map[byte]int{}
    for i := 0; i < len(s); i++ { //计算有多少个字符种类
        cnt[s[i]]++
    }

    fmt.Println(cnt)
    for t := 1; t <= len(cnt); t++ { //遍历字符种类数量
        window := map[byte]int{}
        left, right := 0, 0
        lessK := 0 // 代表当前出现次数小于 k 的字符的数量。
        for ; right < len(s); right++ {
            window[s[right]]++
            if 1 == window[s[right]] { // 当某个字符的出现次数从 0 增加到 1 时,将 less 加一;
                lessK++
            }
            if k == window[s[right]] { // 当某个字符的出现次数从 k−1 增加到 k 时,将 less 减一。
                lessK--
            }

            for len(window) > t { // 说好了窗口内只有t个字符的,如果窗口>t个字符,左边界就开始移动
                window[s[left]]--
                if window[s[left]] == k-1 { //窗口左边界右移,当某个字符出现次数从 k 减少到 k - 1 时,将 less 加一
                    lessK++
                }
                if window[s[left]] == 0 { // 当某个字符出现次数从 1 减少到 0 时,将 less 减一。
                    delete(window, s[left])
                    lessK--
                }
                left++
            }

            if 0 == lessK { //窗口内字符的数量都>=k
                ans = max(ans, right-left+1)
            }
        }
    }
    return ans
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

1100. 长度为 K 的无重复字符子串【会员/中等】

给你一个字符串 S,找出所有长度为 K 且不含重复字符的子串,请你返回全部满足要求的子串的 数目。

输入:S = "havefunonleetcode", K = 5
输出:6
解释:这里有 6 个满足题意的子串,分别是:'havef','avefu','vefun','efuno','etcod','tcode'。

输入:S = "home", K = 5
输出:0
解释:注意:K 可能会大于 S 的长度。在这种情况下,就无法找到任何长度为 K 的子串。

// ============== 解法一  推荐这种解法,一定要会 =====
func numKLenSubstrNoRepeats(s string, k int) int {
    len_s := len(s)
    if len_s < k {
        return 0
    }

    count := 0
    left, right := 0, 0
    tempMap := make(map[byte]int)
    for ; right < len_s; right++ {
        tempMap[s[right]]++
        for tempMap[s[right]] > 1 { // 这里保证无重复,所以>1
            tempMap[s[left]]--
            if tempMap[s[left]] == 0 {
                delete(tempMap, s[left])
            }
            left++
        }

        if len(tempMap) == k { // 这里做了第二层过滤,比如K=5,abcdef六个的时候,左指针也是要右移的
            count++
            delete(tempMap, s[left])
            left++
        }
    }
    return count
}

487. 最大连续1的个数 II【会员/中等】

给定一个二进制数组 nums ,如果最多可以翻转一个 0 ,则返回数组中连续 1 的最大个数。
输入:nums = [1,0,1,1,0]
输出:4
解释:翻转第一个 0 可以得到最长的连续 1。 当翻转以后,最大连续 1 的个数为 4。

输入:nums = [1,0,1,1,0,1]
输出:4

// ======== 直接 md ======
func findMaxConsecutiveOnes(nums []int) int {
    left, right := 0, 0
    res := 0
    count := 0

    for ; right < len(nums); right++ {
        if nums[right] == 0 {
            count++
        }

        for count > 1 {
            if nums[left] == 0 {
                count--
            }
            left++
        }
        res = max(res, right-left+1)
    }
    return res
}


====================解法二,不是很好理解====================
func findMaxConsecutiveOnes(nums []int) int {
    left, right := 0, 0
    zero_pos := -1
    res := 0
    for right = 0; right < len(nums); right++ {
        if nums[right] == 0 {
            if zero_pos != -1 {     //  这一块很关键
                left = zero_pos + 1
            }
            zero_pos = right
        }
        res = max(res, right-left+1)
    }
    return res
}

1004. 最大连续1的个数 III【中等】

给定一个二进制数组 nums 和一个整数 k,如果可以翻转最多 k 个 0 ,则返回 数组中连续 1 的最大个数 。

输入:nums = [1,1,1,0,0,0,1,1,1,1,0], K = 2
输出:6
解释:[1,1,1,0,0,1,1,1,1,1,1]
粗体数字从 0 翻转到 1,最长的子数组长度为 6。
// =============== 解法二  滑动窗口内最多有 K 个 00,求滑动窗口最大的长度,这种做法最好,是需要牢记的 =============
func longestOnes(A []int, K int) int {
    left := 0
    res := 0
    count := 0

    for right := 0; right < len(A); right++ {
        if A[right] == 0 {
            count++
        }
        for count > K {
            if A[left] == 0 { //逐渐移动左指针
                count--
            }
            left++
        }
        res = max(res, right-left+1)
    }
    return res
}

209. 长度最小的子数组【中等】

给定一个含有 n 个正整数的数组和一个正整数 target 。找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
// 我们用 2 个指针,一个指向数组开始的位置,一个指向数组最后的位置,并维护区间内的和 sum 大于等于 ss 同时数组长度最小。
func minSubArrayLen(target int, nums []int) int {

    len_num := len(nums)
    if len_num == 0 {
        return 0
    }

    minValue := math.MaxInt32

    sum := 0
    left, right := 0, 0
    for ; right < len_num; right++ {
        sum += nums[right]
        for sum >= target {
            minValue = min(minValue, right-left+1)
            sum -= nums[left]
            left++
        }
    }
    if minValue == math.MaxInt32 { // 这里比较关键,1, 1, 1, 1, 1, 1, 1  就是所有和加一起,都小于target
        return 0
    }
    return minValue

}

1151. 最少交换次数来组合所有的 1 【会员/中等】

给出一个二进制数组 data,你需要通过交换位置,将数组中 任何位置 上的 1 组合到一起,并返回所有可能中所需 最少的交换次数。

输入: data = [1,0,1,0,1]
输出: 1
解释: 
有三种可能的方法可以把所有的 1 组合在一起:
[1,1,1,0,0],交换 1 次;
[0,1,1,1,0],交换 2 次;
[0,0,1,1,1],交换 1 次。
所以最少的交换次数为 1。

输入:data = [0,0,0,1,0]
输出:0
解释: 
由于数组中只有一个 1,所以不需要交换。

输入:data = [1,0,1,0,1,0,0,1,1,0,1]
输出:3
解释:
交换 3 次,一种可行的只用 3 次交换的解决方案是 [0,0,0,0,0,1,1,1,1,1,1]。

输入: data = [1,0,1,0,1,0,1,1,1,0,1,0,0,1,1,1,0,0,1,1,1,0,1,0,1,1,0,0,0,1,1,1,1,0,0,1]
输出: 8

/*
算法思路:
固定窗口的模板
计算原数组 data 中 1 的个数 totalOne。
维护一个长度为 totalOne 的窗口,计算窗口中 1 的个数。先遍历求出第一个窗口 1 的个数,并保存好这个数字,记为 countOne。
向右移动窗口,继续计算 1 的个数。假设当前下标为 i,那么需要加上当前的数字,再减去移出窗口的数字,移出窗口的下标为 i - totalOne。
所以新的窗口 1 的个数为 countOne += data[i] - data[i-totalOne]。
求 countOne 的最大值,和 totalOne 相减就是我们要求的结果。
一句话:枚举窗口的起点,判断每个窗口内1的数量,选取1最多的一个窗口,就是交换次数最少的位置。
*/
func minSwaps(data []int) int {
    totalOneNum := 0
    for i := range data {
        totalOneNum += data[i] // 这里方式比if data[i]==1要清爽多了
    }

    tempCountOne := 0
    for i := 0; i < totalOneNum; i++ {
        tempCountOne += data[i]
    }
    maxCountOne := tempCountOne
    for i := totalOneNum; i < len(data); i++ {
        // 算法中的精华,向右移动窗口,继续计算 1 的个数。假设当前下标为 i,那么需要加上当前的数字,再减去移出窗口的数字,
        // 移出窗口的下标为 i - totalOne。所以新的窗口 1 的个数为data[i] - data[i-totalOneNum]
        tempCountOne += data[i] - data[i-totalOneNum]
        maxCountOne = max(maxCountOne, tempCountOne)
    }
    return totalOneNum - maxCountOne
}

1208. 尽可能使字符串相等【中等】

给你两个长度相同的字符串,s 和 t。

将 s 中的第 i 个字符变到 t 中的第 i 个字符需要 |s[i] - t[i]| 的开销(开销可能为 0),也就是两个字符的 ASCII 码值的差的绝对值。

用于变更字符串的最大预算是 maxCost。在转化字符串时,总开销应当小于等于该预算,这也意味着字符串的转化可能是不完全的。

如果你可以将 s 的子字符串转化为它在 t 中对应的子字符串,则返回可以转化的最大长度。

如果 s 中没有子字符串可以转化成 t 中对应的子字符串,则返回 0。

输入:s = "abcd", t = "bcdf", maxCost = 3
输出:3
解释:s 中的 "abc" 可以变为 "bcd"。开销为 3,所以最大长度为 3。

424. 替换后的最长重复字符 (中等/滑动窗口)

给你一个字符串 s 和一个整数 k 。你可以选择字符串中的任一字符,并将其更改为任何其他大写英文字符。该操作最多可执行 k 次。

在执行上述操作后,返回包含相同字母的最长子字符串的长度。

输入:s = "ABAB", k = 2
输出:4
解释:用两个'A'替换为两个'B',反之亦然。

输入:s = "AABABBA", k = 1
输出:4
解释:
将中间的一个'A'替换为'B',字符串变为 "AABBBBA"。
子串 "BBBB" 有最长重复字母, 答案为 4。
=================== 方法一(// 其实就是 1004. 最大连续1的个数 III 的变种 ) ================
func characterReplacement(s string, k int) int {
    // tempMap 用来统计 s 里一共有多少字母
    tempMap := make(map[byte]int)
    for i := 0; i < len(s); i++ {
        tempMap[s[i]]++
    }

    result := 0
    for ch := range tempMap { // 这里j遍历顺序不一定的,遍历 s 中所有字符
        left, right := 0, 0
        res := 0
        count := 0
        for ; right < len(s); right++ {
            if strings.Compare(string(s[right]), string(ch)) != 0 { // 当字符为A时,要统计不为A的数量,超过就要剔出窗口
                count++
            }
            for count > k {
                if strings.Compare(string(s[left]), string(ch)) != 0 {
                    count--
                }
                left++
            }
            res = max(res, right-left+1)
        }
        result = max(result, res)
    }
    return result
}

=================== 方法二(还没深入了解的一种方法) ================
func characterReplacement(s string, k int) int {
    left,right, n,maxNumber:=0,0,len(s),0
    res :=0
    valueAndFrequent :=map[byte]int{}
    for ;right < n ;right++{
        valueAndFrequent[s[right]]++;
        maxNumber=max(maxNumber,valueAndFrequent[s[right]])
        if(maxNumber+k < right-left+1){
            valueAndFrequent[s[left]]--
            left++
        }
        res = max(res,right-left+1)
    }
    return res
}

76. 最小覆盖子串(困难/滑动窗口)

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。
 
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

输入:s = "a", t = "a"
输出:"a"

输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。

// https://leetcode.cn/problems/minimum-window-substring/solution/zui-xiao-fu-gai-zi-chuan-by-leetcode-solution/ 看这里的图解释
// https://leetcode.cn/problems/minimum-window-substring/solution/golangshuang-zhi-zhen-dan-ha-xi-biao-by-8cn0r/

func minWindow(s string, t string) string {
    tempT := make(map[byte]int)
    for i := range t {
        tempT[t[i]]++
    }
    res := ""
    left, right := 0, 0
    minLen := math.MaxInt32
    tempS := make(map[byte]int)
    for ; right < len(s); right++ {
        tempS[s[right]]++
        for isContain(tempT, tempS) {
            if right-left+1 < minLen {
                minLen = right - left + 1
                res = s[left : right+1]
            }
            tempS[s[left]]--
            left++
        }
    }
    return res
}

func isContain(tempT map[byte]int, tempS map[byte]int) bool {
    for i := range tempT {
        if tempS[i] < tempT[i] {
            return false
        }
    }
    return true
}

func min(a, b int) int {
    if a > b {
        return b
    }
    return a
}

992. K 个不同整数的子数组 (困难/滑动窗口)

给定一个正整数数组 nums和一个整数 k ,返回 num 中 「好子数组」 的数目。如果 nums 的某个子数组中不同整数的个数恰好为 k,则称 nums 的这个连续、不一定不同的子数组为 「好子数组 」。
例如,[1,2,3,1,2] 中有 3 个不同的整数:1,2,以及 3。
子数组 是数组的 连续 部分。

输入:nums = [1,2,1,2,3], k = 2
输出:7
解释:恰好由 2 个不同整数组成的子数组:[1,2], [2,1], [1,2], [2,3], [1,2,1], [2,1,2], [1,2,1,2].

输入:nums = [1,2,1,3,4], k = 3
输出:3
解释:恰好由 3 个不同整数组成的子数组:[1,2,1,3], [2,1,3], [1,3,4].

1248. 统计「优美子数组」 (中等/滑动窗口)

给你一个整数数组 nums 和一个整数 k。如果某个连续子数组中恰好有 k 个奇数数字,我们就认为这个子数组是「优美子数组」。请返回这个数组中 「优美子数组」 的数目。

输入:nums = [1,1,2,1,1], k = 3
输出:2
解释:包含 3 个奇数的子数组是 [1,1,2,1] 和 [1,2,1,1] 。

输入:nums = [2,4,6], k = 1
输出:0
解释:数列中不包含任何奇数,所以不存在优美子数组。

输入:nums = [2,2,2,1,2,2,1,2,2,2], k = 2
输出:16
// https://leetcode.cn/problems/count-number-of-nice-subarrays/solution/count-number-of-nice-subarrays-by-ikaruga/  (这里解释蛮好的)
func numberOfSubarrays(nums []int, k int) int {
    odd := []int{-1}
    res := 0
    i := 1

    for j := 0; j <= len(nums); j++ {
        if j == len(nums) || nums[j]&1 == 1 {
            odd = append(odd, j)
        }
        
        if len(odd)-i > k {
            left := odd[i] - odd[i-1]
            right := j - odd[len(odd)-2]
            res += left * right
            i++
        }
    }
    return res
}

1852. 每个子数组的数字种类数 (会员/中等/滑动窗口)

给你一个整数数组 nums与一个整数 k,请你构造一个长度 n-k+1 的数组 ans,这个数组第i个元素 ans[i] 是每个长度为k的子数组 nums[i:i+k-1] = [nums[i], nums[i+1], ..., nums[i+k-1]]中数字的种类数。

返回这个数组 ans。

输入: nums = [1,2,3,2,2,1,3], k = 3
输出: [3,2,2,2,3]
解释:每个子数组的数字种类计算方法如下:
- nums[0:2] = [1,2,3] 所以 ans[0] = 3
- nums[1:3] = [2,3,2] 所以 ans[1] = 2
- nums[2:4] = [3,2,2] 所以 ans[2] = 2
- nums[3:5] = [2,2,1] 所以 ans[3] = 2
- nums[4:6] = [2,1,3] 所以 ans[4] = 3

输入: nums = [1,1,1,1,2,3,4], k = 4
输出: [1,2,3,4]
解释: 每个子数组的数字种类计算方法如下:
- nums[0:3] = [1,1,1,1] 所以 ans[0] = 1
- nums[1:4] = [1,1,1,2] 所以 ans[1] = 2
- nums[2:5] = [1,1,2,3] 所以 ans[2] = 3
- nums[3:6] = [1,2,3,4] 所以 ans[3] = 4


func distinctNumbers(nums []int, k int) []int {
    lenNum := len(nums)
    tempMap1 := make(map[int]int)
    left, right := 0, 0
    res := make([]int, 0)
    for ; right < lenNum; right++ {
        tempMap1[nums[right]]++
        for right-left+1 > k {
            tempMap1[nums[left]]--
            if tempMap1[nums[left]] == 0 {
                delete(tempMap1, nums[left])
            }
            left++
        }
        
        if right-left+1 == k {
            res = append(res, len(tempMap1))
        }
    }
    return res
}

1918. 第 K 小的子数组和·(会员/中等/滑动窗口+二分查找)

给你一个 长度为 n 的整型数组 nums 和一个数值 k ,返回 第 k 小的子数组和。

子数组 是指数组中一个 非空 且不间断的子序列。  子数组和 则指子数组中所有元素的和。

输入: nums = [2,1,3], k = 4
输出: 3
解释: [2,1,3] 的子数组为:
- [2] 和为 2
- [1] 和为 1
- [3] 和为 3
- [2,1] 和为 3
- [1,3] 和为 4
- [2,1,3] 和为 6 
最小子数组和的升序排序为 1, 2, 3, 3, 4, 6。 第 4 小的子数组和为 3 。

输入:nums = [3,3,5,5], k = 7
输出:10
解释:[3,3,5,5] 的子数组为:
- [3] 和为 3
- [3] 和为 3
- [5] 和为 5
- [5] 和为 5
- [3,3] 和为 6
- [3,5] 和为 8
- [5,5] 和为 10
- [3,3,5], 和为 11
- [3,5,5] 和为 13
- [3,3,5,5] 和为 16
最小子数组和的升序排序为 3, 3, 5, 5, 6, 8, 10, 11, 13, 16。第 7 小的子数组和为 10 。
// =========== 解法一,二分查找+滑动窗口  ============
/*
对于长度为 n 的数组 nums,共有 n(n+1)/2个子数组,每个子数组都有对应的子数组和,n(n+1)/2 个子数组和。
由于数组 nums 的元素都大于 0,因此最小的子数组和为数组 nums 的最小元素,最大的子数组和为数组nums的所有元素之和。
第 k小的子数组和一定大于或等于最小的子数组和且小于或等于最大的子数组和。
可以使用二分查找的方法寻找第 k小的子数组和。
二分查找的做法是,初始化上界和下界分别为最大的子数组和和最小的子数组和,每次取mid,计算子数组和小于或等于mid的子数组的数量,
将该数量与 k 比较之后调整上下界,直到找到第 k 小的子数组和。
*/
func kthSmallestSubarraySum(nums []int, k int) int {
    lenNum := len(nums)
    sumNums := 0
    minValue := math.MaxInt32
    for i := 0; i < lenNum; i++ {
        minValue = min(minValue, nums[i])
        sumNums += nums[i]
    }

    low, high := minValue, sumNums

    res := 0
    for low <= high {
        mid := (low + high) / 2
        if numsLargerK(nums, mid, k) { // mid是子数组和,如果满足条件的子数组数量大于等于k,说明mid取大了,要往小调整
            res = mid
            high = mid - 1
        } else {
            low = mid + 1
        }
    }
    return res
}

func numsLargerK(nums []int, mid int, k int) bool {
    lenNum := len(nums)
    count := 0
    sum := 0
    left, right := 0, 0
    for ; right < lenNum; right++ {
        sum += nums[right]
        for sum > mid {
            sum -= nums[left]
            left++
        }
        count += right - left + 1 // 这一行代码是精髓,其实理解就是最右侧不动,有几个到最右侧的子数组。比如[1 2 3 4]中right=3,left=2,最右侧是4,就是[3 4]  [4]
    }
    return count >= k // 如果count>=k,那么就是不满足条件,因为我们要找的是刚好满足条件的。
}

// ===========  以下这种思路并不大行,会内存溢出的,碰到这种题目,最好就是控制一下内存,强行终止了。以下使用最简单的双指针,部分用例。===========
// 就是一种暴力解法,  先右指针到头,再移动左指针,硬求和
// [2] 2
// [2 1] 3 1
// [2 1 3] 6 4 3
func kthSmallestSubarraySum(nums []int, k int) int {
    lenNum := len(nums)
    sum := 0
    tempSum := 0
    res := make([]int, 0)
    for right := 0; right < lenNum; right++ {
        left := 0
        sum += nums[right]
        tempSum = sum
        res = append(res, sum)

        //if len(res) > 1000000 { // 投机取巧
        //  return 0
        //}

        for left < right {
            tempSum -= nums[left]
            left++
            res = append(res, tempSum)
        }

    }
    
    sort.Ints(res)
    fmt.Println(res)
    return res[k-1]
}

683. K 个关闭的灯泡(会员/困难/滑动窗口)

n 个灯泡排成一行,编号从 1 到 n 。最初,所有灯泡都关闭。每天 只打开一个 灯泡,直到 n 天后所有灯泡都打开。
给你一个长度为 n 的灯泡数组 blubs ,其中 bulls[i] = x 意味着在第 (i+1) 天,我们会把在位置 x 的灯泡打开,其中 i 从 0 开始,x 从 1 开始。
给你一个整数 k ,请返回恰好有两个打开的灯泡,且它们中间 正好 有 k 个 全部关闭的 灯泡的 最小的天数 。如果不存在这种情况,返回 -1 。

输入:
bulbs = [1,3,2],k = 1
输出:2
解释:
第一天 bulbs[0] = 1,打开第一个灯泡 [1,0,0]
第二天 bulbs[1] = 3,打开第三个灯泡 [1,0,1]
第三天 bulbs[2] = 2,打开第二个灯泡 [1,1,1]
返回2,因为在第二天,两个打开的灯泡之间恰好有一个关闭的灯泡。

输入:bulbs = [1,2,3],k = 1
输出:-1

/*
这一题真的比较难,想不通,但是用的是固定窗口滑动
需要返回的是满足条件的天数,条件是left与right之间正好有K个灯是关闭灯;
维护一个days数组,将灯与天数关联起来:days[i] = x,第i个灯在第x天打开
i表示灯号,范围是[0, bulbsSize) // 3: 0,1,2
x表示天数,范围是[1, bulbsSize] // 3: 1,2,3
按灯号来遍历,i是[0, bulbsSize)
满足题意灯条件是,[left, right]窗口内所有灯亮灯天数,都小于left,right灯亮灯天数
遍历完成,i == right,取结果
跳转到下一个窗口遍历
left = i
right = left + k + 1 // left与right之间有K个灯
*/

func kEmptySlots(bulbs []int, k int) int {
    bulbsNum := len(bulbs)
    days := make([]int, bulbsNum, bulbsNum)
    for i := 0; i < bulbsNum; i++ {
        days[bulbs[i]-1] = i + 1 // 将bulbs转换为days:days[i] = x,第i个灯在第x天打开
    }
    left := 0
    right := left + k + 1
    res := math.MaxInt32
    temp := 0
    // left=0,rigiht=left+k+1,i在left 和 right之间,所以i要从1开始遍历,for循环跳出条件是right不能超过数组长度
    for i := 1; right < bulbsNum; i++ {
        if days[i] > days[left] && days[i] > days[right] { // 第i个灯比开的时间比left和right都要晚,才说明中间有关闭的灯
            continue
        }

        if i == right { // 这一步很关键,i==right,就说明left和right之间有k个关闭的,因为[left i right]
            temp = max(days[left], days[right]) // 取二者之间最大值,
            res = min(res, temp)
        }

        // 下一个窗口
        left = i
        right = left + k + 1
    }

    if res == math.MaxInt32 {
        return -1
    }
    return res
}

727. 最小窗口子序列(会员/困难/滑动窗口)

给定字符串 S and T,找出 S 中最短的(连续)子串 W ,使得 T 是 W 的 子序列 。

如果 S 中没有窗口可以包含 T 中的所有字符,返回空字符串 ""。如果有不止一个最短长度的窗口,返回开始位置最靠左的那个。

输入:
S = "abcdebdde", T = "bde"
输出:"bcde"
解释:
"bcde" 是答案,因为它在相同长度的字符串 "bdde" 出现之前。
"deb" 不是一个更短的答案,因为在窗口中必须按顺序出现 T 中的元素。

220. 存在重复元素 III(困难/滑动窗口)

给你一个整数数组 nums 和两个整数 k 和 t 。请你判断是否存在 两个不同下标 i 和 j,使得 abs(nums[i] - nums[j]) <= t ,同时又满足 abs(i - j) <= k 。如果存在则返回 true,不存在返回 false。

输入:nums = [1,2,3,1], k = 3, t = 0
输出:true


输入:nums = [1,0,1,1], k = 1, t = 2
输出:true

输入:nums = [1,5,9,1,5,9], k = 2, t = 3
输出:false

904. 水果成篮(中等/滑动窗口)

你正在探访一家农场,农场从左到右种植了一排果树。这些树用一个整数数组 fruits 表示,其中 fruits[i] 是第 i 棵树上的水果 种类。你想要尽可能多地收集水果。然而,农场的主人设定了一些严格的规矩,你必须按照要求采摘水果:
你只有 两个 篮子,并且每个篮子只能装 单一类型 的水果。每个篮子能够装的水果总量没有限制。
你可以选择任意一棵树开始采摘,你必须从 每棵 树(包括开始采摘的树)上 恰好摘一个水果 。采摘的水果应当符合篮子中的水果类型。每采摘一次,你将会向右移动到下一棵树,并继续采摘。
一旦你走到某棵树前,但水果不符合篮子的水果类型,那么就必须停止采摘。
给你一个整数数组 fruits ,返回你可以收集的水果的 最大 数目。

输入:fruits = [1,2,1]
输出:3
解释:可以采摘全部 3 棵树。

输入:fruits = [0,1,2,2]
输出:3
解释:可以采摘 [1,2,2] 这三棵树。
如果从第一棵树开始采摘,则只能采摘 [0,1] 这两棵树。

输入:fruits = [1,2,3,2,2]
输出:4
解释:可以采摘 [2,3,2,2] 这四棵树。
如果从第一棵树开始采摘,则只能采摘 [1,2] 这两棵树。

输入:fruits = [3,3,3,1,2,1,1,2,3,3,4]
输出:5
解释:可以采摘 [1,2,1,1,2] 这五棵树。

1052. 爱生气的书店老板(中等/滑动窗口)

有一个书店老板,他的书店开了 n 分钟。每分钟都有一些顾客进入这家商店。给定一个长度为 n 的整数数组 customers ,其中 customers[i] 是在第 i 分钟开始时进入商店的顾客数量,所有这些顾客在第 i 分钟结束后离开。
在某些时候,书店老板会生气。 如果书店老板在第 i 分钟生气,那么 grumpy[i] = 1,否则 grumpy[i] = 0。
当书店老板生气时,那一分钟的顾客就会不满意,若老板不生气则顾客是满意的。
书店老板知道一个秘密技巧,能抑制自己的情绪,可以让自己连续 minutes 分钟不生气,但却只能使用一次。
请你返回 这一天营业下来,最多有多少客户能够感到满意 。

输入:customers = [1,0,1,2,1,1,7,5], grumpy = [0,1,0,1,0,1,0,1], minutes = 3
输出:16
解释:书店老板在最后 3 分钟保持冷静。
感到满意的最大客户数量 = 1 + 1 + 1 + 1 + 7 + 5 = 16.

输入:customers = [1], grumpy = [0], minutes = 1
输出:1

1696. 跳跃游戏 VI(中等/滑动窗口)

给你一个下标从 0 开始的整数数组 nums 和一个整数 k 。一开始你在下标 0 处。每一步,你最多可以往前跳 k 步,但你不能跳出数组的边界。也就是说,你可以从下标 i 跳到 [i + 1, min(n - 1, i + k)] 包含 两个端点的任意位置。
你的目标是到达数组最后一个位置(下标为 n - 1 ),你的 得分 为经过的所有数字之和。
请你返回你能得到的 最大得分 。

输入:nums = [1,-1,-2,4,-7,3], k = 2
输出:7
解释:你可以选择子序列 [1,-1,4,3] (上面加粗的数字),和为 7 。

输入:nums = [10,-5,-2,4,0,3], k = 3
输出:17
解释:你可以选择子序列 [10,4,3] (上面加粗数字),和为 17 。

输入:nums = [1,-5,-20,4,-1,3,-6,-3], k = 2
输出:0

1695. 删除子数组的最大得分(中等/滑动窗口)

给你一个正整数数组 nums ,请你从中删除一个含有 若干不同元素 的子数组。删除子数组的 得分 就是子数组各元素之 和 。
返回 只删除一个 子数组可获得的 最大得分 。
如果数组 b 是数组 a 的一个连续子序列,即如果它等于 a[l],a[l+1],...,a[r] ,那么它就是 a 的一个子数组。

输入:nums = [4,2,4,5,6]
输出:17
解释:最优子数组是 [2,4,5,6]

输入:nums = [5,2,1,2,5,2,1,2,5]
输出:8
解释:最优子数组是 [5,2,1] 或 [1,2,5]

2107 (huiyuan)

你可能感兴趣的:(二、leetcode刷题之【滑动窗口】)