美文网首页
LeetCode习题:滑动窗口的最大值

LeetCode习题:滑动窗口的最大值

作者: 华子的学习之路 | 来源:发表于2021-04-01 11:24 被阅读0次

题目描述:给定一个数组 nums 和滑动窗口的大小 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
提示:你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。

题解一:循环暴力破解

解题思路:从数组第i(i=0)个数据开始,将其与其后第i+1..<i+k依次进行比较取最大值,直至遍历到第count-k+1,得到最后一个窗口中的最大值为止。

解题开发语言:Swift

class Solution {
    func maxSlidingWindow(_ nums: [Int], _ k: Int) -> [Int] {
        if nums.count <= 1 || k == 1 {
            return nums
        }
        var result = [Int]()
        for i in 0..<nums.count-k+1 {
            var maxNum = Int.min
            for j in i..<i+k {
                maxNum = max(maxNum, nums[j])
            }
            result.append(maxNum)
        }
        return result
    }
}

缺点:存在重复比较,以示例中的数据[1,3,-1,-3,5,3,6,7],3为例,1,3,-1中最大数据为3,窗口滑动,比较3,-1,-3中最大值为3,其中3与-1被重复比较了两次。

复杂度分析:

时间复杂度:O(n), 其中n为数组长度,外层for循环执行n-k+1次,内层for循环每次执行k次,所以总共是k(n-k+1), 忽略乘数因子等,所以是O(n)
空间复杂度:O(1), 运算过程中未额外开辟内存

题解二:双端队列

解题思路:以示例中数据为例,第一组窗口数据是,1,3,-1, 由于后面的3比前面的1打,所以当比较-1时,前面的1对比较结果是没有意义的,因为3比它大,1不可能是这组数据里的最大值了,所有按照这个思路,可以创建一个队列,将数组中的数据依次入队,但是由于我们求最大值,所以当一个数据入队前,将其与队尾的数据进行比较,如果队尾数据比当前数据小,即队尾数据在此次窗口数据中不可能是最大值,所以其可以直接出队,由于其要从队尾出队,所以创建的这个队列有点特殊,需要是一个双端队列,然后将当前数据入队,队列中最多需要保持最多k个数据,当队列中已经存在k个数据时,新数据入队前,将队首数据直接出栈,因为其不在本次窗口的比较范围内,当窗口形成后,队首数据即为窗口最大数据。

// 双向队列
struct DoubleQueue {
    private var data = [Int]()
    private var n = 0, pre = 0, tra = 0
    
    public var trail: Int? {
        if isEmpty {
            return nil
        }
        return data[tra - 1]
    }
    
    public var prev: Int? {
        if isEmpty {
            return nil
        }
        return data[pre]
    }
    
    public var total: Int {
        return tra - pre
    }
    
    public var isEmpty: Bool {
        return tra - pre == 0
    }
    
    init(num: Int) {
        n = num
        pre = 0
        tra = 0
        data = [Int](repeating: Int.min, count: num)
    }
    
    @discardableResult
    public mutating func enqueue(val: Int) -> Bool {
        if total == n {
            return false
        }
        // 数组前方有空余空间, 需要做数据搬移
        if tra == n && pre > 0 {
            for i in pre..<tra {
                data[i-pre] = data[i]
            }
            tra -= pre
            pre = 0
        }
        data[tra] = val
        tra += 1
        return true
    }
    
    @discardableResult
    public mutating func enqueueFront(val: Int) -> Bool {
        if total == n {
            return false
        }
        pre = pre > 0 ? pre - 1 : 0
        data.insert(val, at: pre)
        return true
    }
    
    @discardableResult
    public mutating func dequeue() -> Int? {
        if total == 0 {
            return nil
        }
        let val = data[pre]
        pre += 1
        return val
    }
    
    @discardableResult
    public mutating func dequeueBack() -> Int? {
        if total == 0 {
            return nil
        }
        let val = data[tra - 1]
        tra -= 1
        return val
    }
}


class Solution {
    func maxSlidingWindow(_ nums: [Int], _ k: Int) -> [Int] {
        if nums.count <= 1 || k <= 1 {
            return nums
        }
        var result = [Int](repeating: Int.min, count: nums.count - k + 1)
        var doubleQueue = DoubleQueue(num: k)
        for i in 0..<nums.count {
            let val = nums[i]
            // 移除队列中比当前值小的数据,因为最大值肯定不是这些数据
            while (!doubleQueue.isEmpty && val > nums[doubleQueue.trail!]) {
                doubleQueue.dequeueBack()
            }
            if doubleQueue.prev != nil && doubleQueue.prev! < i - k + 1 {
                doubleQueue.dequeue()
            }
            // 将当前值入队
            doubleQueue.enqueue(val: i)
            // 如果是第三个及之后的数据,滑动窗口每移动一次,需保存一次最大值
            if i + 1 >= k {
                result[i - k + 1] = nums[doubleQueue.prev!]
            }
        }
        return result
    }
}

复杂度分析

时间复杂度:O(n), n为数组长度,遍历数组时间为O(n), 整个遍历过程中每个元素最多入队和出队1次,所以队列操作为O(2n), 所以总的时间复杂度为O(n) + O(2n) = O(3n), 忽略乘数因子即为O(n)
空间复杂度:O(k), 双端队列中最多存在k个数据(窗口大小)
题目来源:LeetCode

相关文章

网友评论

      本文标题:LeetCode习题:滑动窗口的最大值

      本文链接:https://www.haomeiwen.com/subject/zictkltx.html