代码随想录算法训练营Day13

58 阅读5分钟

代码随想录算法训练营Day13

239.滑动窗口最大值

题目

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

示例 1:

输入: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

示例 2:

输入:nums = [1], k = 1
输出:[1]

请你结合题目和示例,分步骤给出尽可能最优的问题解决的思路,在关键步骤上(如边界问题处理、算法原理)需要详细解释,并用GO语言完成下面的方法填写,在必要的步骤上需要补上中文注释:

func maxSlidingWindow(nums []int, k int) []int {

}

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length

思路

思路分析

  1. 使用双端队列(deque):我们可以使用双端队列来解决这个问题,双端队列可以在O(1)时间内进行插入和删除操作,从而优化算法的时间复杂度。
  2. 维护窗口内的最大值:我们需要维护一个双端队列,队列中存储的是数组元素的索引。在遍历数组时,我们将元素的索引加入队列,并且在每次移动窗口时,我们需要确保队列中的元素按照从大到小的顺序排列,这样队列的第一个元素就是当前窗口的最大值。

算法步骤

  1. 初始化双端队列和结果数组:我们需要初始化一个双端队列deque和一个结果数组res

  2. 遍历数组

    :遍历数组nums,对于每个元素执行以下操作:

    • 维护队列的顺序:在每次移动窗口时,我们需要确保队列中的元素按照从大到小的顺序排列。
    • 添加元素到队列:将当前元素的索引加入队列。
    • 移除超出窗口范围的元素:如果队列中的第一个元素已经超出窗口范围,则将其从队列中移除。
    • 记录窗口最大值:当窗口的大小达到k时,将队列的第一个元素对应的值加入结果数组res
  3. 返回结果数组:遍历完成后,返回结果数组res

时间和空间复杂度

  • 时间复杂度:遍历数组需要O(n)的时间,其中n为数组的长度。在遍历过程中,双端队列的操作均为O(1),因此总体时间复杂度为O(n)。
  • 空间复杂度:双端队列和结果数组的空间复杂度均为O(n),因此总体空间复杂度为O(n)。

代码实现

func maxSlidingWindow(nums []int, k int) []int {
    deque := make([]int, 0)
    res := 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+1 {
            deque = deque[1:]
        }

        // 记录窗口最大值
        if i >= k-1 {
            res = append(res, nums[deque[0]])
        }
    }

    return res
}

image-20240116004532818

347.前 K 个高频元素

题目

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:

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

示例 2:

输入: nums = [1], k = 1
输出: [1]

提示:

  • 1 <= nums.length <= 105

  • k 的取值范围是 [1, 数组中不相同的元素的个数]

  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

**进阶:**你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

请你结合题目和示例以及提示,分步骤给出尽可能最优的问题解决的思路,在关键步骤上需要详细解释,需要详细到让看到步骤的人可以直接这个思路完成相关的算法的程度。最后结合思路分析一下算法的时间和空间复杂度,并按照思路用GO语言完成下面的方法填写,代码中变量声明和所有步骤都需要补上中文注释:

func topKFrequent(nums []int, k int) []int {

}

思路

思路分析

可以使用哈希表来统计每个元素出现的频率。然后可以使用桶排序,将频率作为数组的下标,将出现相同频率的元素放入同一个桶中。接着,从桶中逆序取出前 k 个高频元素,即为答案。

算法步骤

  1. 创建一个哈希表 frequencyMap,用于统计每个元素出现的频率。
  2. 遍历数组 nums,更新哈希表 frequencyMap
  3. 创建一个桶数组 bucket,桶的索引表示元素的频率,桶中存放出现相同频率的元素。
  4. 遍历哈希表 frequencyMap,将元素按照频率放入对应的桶中。
  5. 从桶数组中逆序取出前 k 个高频元素,放入结果数组中。

时间和空间复杂度:

  • 时间复杂度:遍历数组统计频率 O(n),遍历哈希表放入桶 O(n),从桶中取出前 k 个高频元素 O(n),总共为 O(n)。
  • 空间复杂度:哈希表和桶数组的空间占用均为 O(n)。

代码实现

func topKFrequent(nums []int, k int) []int {
    // 创建哈希表统计每个元素出现的频率
    frequencyMap := make(map[int]int)
    for _, num := range nums {
        frequencyMap[num]++
    }

    // 创建桶数组,桶的索引表示元素的频率,桶中存放出现相同频率的元素
    bucket := make([][]int, len(nums)+1)
    for num, freq := range frequencyMap {
        bucket[freq] = append(bucket[freq], num)
    }

    // 从桶中逆序取出前 k 个高频元素,放入结果数组中
    result := make([]int, 0, k)
    for i := len(bucket) - 1; i >= 0 && len(result) < k; i-- {
        result = append(result, bucket[i]...)
    }

    return result
}

image-20240116011352360