:子串进阶与普通数组(Go 实现))
个人主页会编程的土豆欢迎来访作者简介后端学习者❄️个人专栏数据结构与算法数据库leetcode✨那些你一个人走过的夜路终将化作照亮未来的光本文覆盖力扣「热题 100」学习计划第1115题全部使用Go实现。第 10 题「和为 K 的子数组」见上一篇本篇从滑动窗口最大值开始。题单入口LeetCode 热题 100239. 滑动窗口最大值难度困难标签队列、数组、滑动窗口、单调队列、堆优先队列题目链接239. 滑动窗口最大值题目描述给你一个整数数组nums有一个大小为k的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的k个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。示例输入nums [1,3,-1,-3,5,3,6,7], k 3 输出[3,3,5,5,6,7] 解释 窗口位置 最大值 --------------- ----- [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思路分析暴力每个窗口扫一遍是 O(nk)。最优解用单调队列存下标队列内下标对应的值从大到小新元素入队前从队尾弹出所有「值 ≤ 当前值」的下标它们不可能再成为最大值队头若滑出窗口下标 ≤ i-k弹出窗口形成后队头就是当前最大值下标Go 代码func maxSlidingWindow(nums []int, k int) []int { n : len(nums) res : make([]int, 0, n-k1) deque : make([]int, 0) // 存下标对应值单调递减 for i, num : range nums { // 弹出队尾较小元素 for len(deque) 0 nums[deque[len(deque)-1]] num { deque deque[:len(deque)-1] } deque append(deque, i) // 弹出滑出窗口的队头 if deque[0] i-k { deque deque[1:] } // 窗口已形成 if i k-1 { res append(res, nums[deque[0]]) } } return res }复杂度时间复杂度O(n)每个下标最多入队、出队一次空间复杂度O(k)76. 最小覆盖子串难度困难标签哈希表、字符串、滑动窗口题目链接76. 最小覆盖子串题目描述给你一个字符串s、一个字符串t。返回s中涵盖t所有字符的最小子串。如果s中不存在涵盖t所有字符的子串则返回空字符串。注意对于t中重复字符子串中该字符数量必须不少于t中该字符数量。示例输入s ADOBECODEBANC, t ABC 输出BANC 解释最小覆盖子串 BANC 包含来自字符串 t 的 A、B 和 C。思路分析变长滑动窗口经典题统计t的字符需求need以及还需满足的「有效字符种类数」needKinds右指针扩张更新窗口计数某字符刚好凑齐时valid当valid needKinds说明窗口已覆盖尝试收缩左指针并记录最短子串左端字符不够时valid--继续扩张Go 代码func minWindow(s string, t string) string { if len(s) len(t) || len(t) 0 { return } need : make(map[byte]int) for i : 0; i len(t); i { need[t[i]] } needKinds : len(need) window : make(map[byte]int) valid : 0 left : 0 start, minLen : 0, len(s)1 for right : 0; right len(s); right { c : s[right] if _, ok : need[c]; ok { window[c] if window[c] need[c] { valid } } for valid needKinds left right { if right-left1 minLen { minLen right - left 1 start left } d : s[left] left if _, ok : need[d]; ok { if window[d] need[d] { valid-- } window[d]-- } } } if minLen len(s)1 { return } return s[start : startminLen] }复杂度时间复杂度O(|s| |t|)空间复杂度O(|Σ|)53. 最大子数组和难度中等标签数组、分治、动态规划题目链接53. 最大子数组和题目描述给你一个整数数组nums请你找出一个具有最大和的连续子数组子数组最少包含一个元素返回其最大和。示例输入nums [-2,1,-3,4,-1,2,1,-5,4] 输出6 解释连续子数组 [4,-1,2,1] 的和最大为 6。思路分析Kadane 算法可看作一维 DPcur以当前元素结尾的最大子段和转移cur max(nums[i], cur nums[i])含义前面累加和若为负不如从当前重新开始同时用ans维护全局最大值。Go 代码func maxSubArray(nums []int) int { cur, ans : nums[0], nums[0] for i : 1; i len(nums); i { if cur 0 { cur nums[i] } else { cur nums[i] } if cur ans { ans cur } } return ans }复杂度时间复杂度O(n)空间复杂度O(1)56. 合并区间难度中等标签数组、排序题目链接56. 合并区间题目描述以数组intervals表示若干个区间的集合其中单个区间为intervals[i] [starti, endi]。请你合并所有重叠的区间并返回一个不重叠的区间数组该数组需恰好覆盖输入中的所有区间。示例输入intervals [[1,3],[2,6],[8,10],[15,18]] 输出[[1,6],[8,10],[15,18]] 解释区间 [1,3] 和 [2,6] 重叠将它们合并为 [1,6]。思路分析按区间左端点排序遍历若当前区间与结果中最后一个区间重叠start lastEnd则合并更新右端点为两者较大值否则直接追加新区间Go 代码func merge(intervals [][]int) [][]int { if len(intervals) 0 { return nil } sort.Slice(intervals, func(i, j int) bool { return intervals[i][0] intervals[j][0] }) res : [][]int{intervals[0]} for i : 1; i len(intervals); i { last : res[len(res)-1] cur : intervals[i] if cur[0] last[1] { if cur[1] last[1] { last[1] cur[1] } } else { res append(res, cur) } } return res }记得导入import sort复杂度时间复杂度O(n log n)主要在排序空间复杂度O(log n)排序栈空间不计返回数组189. 轮转数组难度中等标签数组、数学、双指针题目链接189. 轮转数组题目描述给定一个整数数组nums将数组中的元素向右轮转k个位置其中k是非负数。示例输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转 3 步: [5,6,7,1,2,3,4]思路分析经典「三次反转」空间 O(1)整体反转[1,2,3,4,5,6,7]→[7,6,5,4,3,2,1]反转前k个[5,6,7,4,3,2,1]反转后n-k个[5,6,7,1,2,3,4]注意先对k取模避免k n。Go 代码func rotate(nums []int, k int) { n : len(nums) k % n reverse(nums, 0, n-1) reverse(nums, 0, k-1) reverse(nums, k, n-1) } func reverse(nums []int, left, right int) { for left right { nums[left], nums[right] nums[right], nums[left] left right-- } }复杂度时间复杂度O(n)空间复杂度O(1)小结题目核心技巧时间复杂度滑动窗口最大值单调队列O(n)最小覆盖子串变长滑动窗口O(n)最大子数组和Kadane / 一维 DPO(n)合并区间排序 线性合并O(n log n)轮转数组三次反转O(n)前 15 题进度序号题目篇目15两数之和 … 盛最多水的容器第一篇610三数之和 … 和为 K 的子数组第二篇1115滑动窗口最大值 … 轮转数组本文下一篇可继续写普通数组剩余题除自身以外数组的乘积、缺失的第一个正数以及矩阵、链表专题。如果对你有帮助欢迎点赞收藏一起把 Hot100 刷完