算法挑战 Day11 栈与队列

40 阅读2分钟

栈与队列 滑动窗口

239. 滑动窗口最大值

labuladong 题解思路

给你一个整数数组 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]

 

提示:

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

解题思路

采用单调队列来实现

单调队列(Monotonic Queue)是一种数据结构,它可以在的时间内完成以下两个操作:

在队尾添加一个元素。 删除队头元素。 它的特殊之处在于,它可以维护队列中元素的单调性。具体来说,如果队列中的元素单调递增(或单调递减),那么这个队列就是单调队列。

单调队列常用于滑动窗口问题,也可以用来解决一些其他的问题。例如,可以使用单调队列来求解一个数组中每个元素的前/后个最大/最小值等。

实现代码

class MonotonicQueue {
    
    var queue = [Int]()
    
    func push(_ num:Int) {
        while !queue.isEmpty && num > queue.last! {
            queue.removeLast()
        }
        queue.append(num)
    }
    
    func pop(_ num:Int) {
        // 因为比queue.first 小的数 在 push 进单调栈的时候就已经被移除了
        // 所以这里只需要处理 num == queue.first 的情况
        if num == queue.first {
            queue = Array(queue[1..<queue.count])
        }
    }
    
    func max() -> Int {
        return queue.first!
    }
    
}

func maxSlidingWindow(_ nums: [Int], _ k: Int) -> [Int] {
        var res = [Int]()
        
        let queue = MonotonicQueue()
        
        for i in 0..<nums.count {
            if i < k-1 {
                queue.push(nums[i])
            } else {
                // 入栈
                queue.push(nums[i])
                // 找最大值
                res.append(queue.max())
                // 出栈
                queue.pop(nums[i+1-k])
            }
        }
        return res
    }

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 **是数组大小。

实现逻辑

方案一: 将元素 与 元素出现的次数存入字典中,将这个字典按照 value 从大到小进行遍历 将遍历之后的字典value进行取值

方案二: 将元素 与 元素出现的次数存入字典中 将字典的value 存入一个指定大小的 小顶堆中 ,这样小顶堆中保存的就是高频元素出现的次数 遍历 map 将value 属于这个小顶堆的key 找出来

实现源码

class PriorityQueue {
    
    var list:[Int]
    
    var maxSize:Int
    
    init(maxSize:Int) {
        self.list = [Int]()
        self.maxSize = maxSize
    }
    
    private func leftChildIndex(index:Int) -> Int {
        return 2 * index + 1
    }
    
    private func rightChildIndex(index:Int) -> Int {
        return 2 * (index + 1)
    }
    
    private func parentIndex(index: Int) -> Int {
        return (index - 1) / 2
    }
    
    private func siftUp(_ index: Int) {
        var childIndex = index
        var parentIndex = self.parentIndex(index: index)
        
        while childIndex > 0 && list[parentIndex] > list[childIndex] {
            self.list.swapAt(parentIndex, childIndex)
            childIndex = parentIndex
            parentIndex = self.parentIndex(index: childIndex)
        }
        
    }
    
    private func siftDown(_ index: Int) {
        
        let parentIndex = index
        
        let leftChildIndex = self.leftChildIndex(index: parentIndex)
        
        let rightChildIndex = self.rightChildIndex(index: parentIndex)
        
        // 如果左边的子节点 大于 链表长度 说明 已经不需要下沉
        if leftChildIndex > list.count {
            return
        }
        
        // minChildIndex 表示左右子节点中 小的那个 的index
        var minChildIndex = leftChildIndex
        
        // 如果右边节点存在 找到 左右子节点中 小的那个 的index
        if rightChildIndex < list.count {
            minChildIndex = list[leftChildIndex] < list[rightChildIndex] ? leftChildIndex : rightChildIndex
        }
        
        // 如果下的那个的index 小于 这个节点 ,则交换他们 ,然后 继续下沉
        if  list[minChildIndex] < list[parentIndex] {
            list.swapAt(parentIndex, minChildIndex)
            siftDown(minChildIndex)
        }
    }
    
    func push(num:Int) {
        if list.count < maxSize {
            list.append(num)
            siftUp(list.count-1)
        } else {
            // 顶部是最小的 如果比顶部大 则加入进来 否则不需要加入
            if list.first! < num {
                list[0] = num
                siftDown(0)
            }
        }
        
    }
    
    func pop() -> Int? {
        guard !list.isEmpty else { return nil }
        // 将数组的前后进行交换
        list.swapAt(0, list.count - 1)
        // 删除尾部元素
        let removed = list.removeLast()
        // 同时顶部元素下沉
        siftDown(0)
        return removed
    }
}


class Solution_StackAndQueue_Day13_2 {
    func topKFrequent(_ nums: [Int], _ k: Int) -> [Int] {
        
        var map = [Int: Int]()
        
        for i in 0..<nums.count {
            map[nums[i],default: 0] += 1
        }

        let queue = PriorityQueue(maxSize: k)
        
        for num in map.values {
            queue.push(num: num)
        }
        
        let set = Set(queue.list)
        
        var res = [Int]()
        
        for key in map.keys {
            if set.contains(map[key]!) {
                res.append(key)
            }
        }
        
        return res
    }
}