剑指offer59 滑动窗口的最大值 Kotlin

描述

给定一个长度为 n 的数组 num 和滑动窗口的大小 size ,找出所有滑动窗口里数值的最大值。

例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

窗口大于数组长度或窗口长度为0的时候,返回空。

数据范围: 1≤n≤100001≤n≤10000,0≤size≤100000≤size≤10000,数组中每个元素的值满足 ∣val∣≤10000∣val∣≤10000

要求:空间复杂度 O(n)O(n),时间复杂度 O(n)O(n)

题目解析

我们需要找到数组 num 中所有滑动窗口的最大值。那么我们不妨先尝试用暴力解法,直接遍历数组,通过在每个窗口位置遍历窗口内的所有元素来找到最大值。

object Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * @param num int整型一维数组 
     * @param size int整型 
     * @return int整型一维数组
     */
    fun maxInWindows(num: IntArray, size: Int): IntArray {
        // 如果窗口大小大于数组长度,返回空数组
        if (num.size < size || size == 0) return intArrayOf()
        
        // 初始化结果数组
        val result = IntArray(num.size - size + 1)
        
        // 遍历数组,计算每个窗口的最大值
        for (i in 0..num.size - size) {
            var max = num[i]
            for (j in i until i + size) {
                if (num[j] > max) {
                    max = num[j]
                }
            }
            result[i] = max
        }
        
        return result
    }
}

在初始化结果数组时,使用条件'if (num.size < size || size == 0)'来避免size为0的情况。

外层循环的条件使用'0..num.size - size',以确保不会访问越界索引。

方法简单直观,但对于每个窗口起始位置,都需要遍历size个元素。

时间复杂度:O(n * size)     空间复杂度:O(n),存储结果数组。

如果想在时间效率上优化,则可以考虑单调队列。

单调队列

滑动窗口问题,我们可以通过维护一个单调队列来解决。单调队列是一种特殊的数据结构,它能够保证队列内的元素按照某种顺序排列(比如递增或递减),并且可以高效地支持添加元素和删除元素的操作。

首先我们创建一个双端队列deque来维护窗口内的最大值的索引并创建一个结果列表result来存储每个滑动窗口的最大值随后遍历数组nums:

对于每个索引i,维护deque,确保队列的末尾始终是当前窗口内的最大值的索引。

如果deque中存在比当前元素小的元素,移除它们,因为它们不可能是未来窗口的最大值。

将当前索引i添加到deque。

如果deque的长度大于窗口大小,移除队列头部的索引,因为它已经不在当前窗口内。

当遍历的索引i大于窗口大小减一时,将deque的第一个元素对应的值添加到结果列表中,因为这是当前窗口的最大值。

最后将结果列表转换为IntArray并返回。

object Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * @param num int整型一维数组
     * @param size int整型
     * @return int整型一维数组
     */
    fun maxInWindows(num: IntArray, size: Int): IntArray {
        // 如果窗口大于数组长度或窗口大小为0,返回空数组
        if (num.size < size || size == 0) return intArrayOf()
        
        val deque = ArrayDeque<Int>() // 用于存储索引的双端队列
        val maxVals = mutableListOf<Int>() // 存储每个窗口的最大值

        for (i in num.indices) {
            // 移除队列尾部所有不大于当前元素的索引
            while (deque.isNotEmpty() && num[deque.last()] <= num[i]) {
                deque.removeLast()
            }
            deque.addLast(i) // 当前索引入队

            // 如果队列头部索引不在窗口内,则移除
            if (deque.first() < i - size + 1) {
                deque.removeFirst()
            }

            // 当窗口已经滑动了size次,记录最大值
            if (i >= size - 1) {
                maxVals.add(num[deque.first()])
            }
        }

        return maxVals.toIntArray()
    }
}

时间复杂度:由于每个元素最多只会被插入和删除 deque 一次,因此该算法的时间复杂度是 O(n),其中 n 是数组的长度。

如果要进一步降低时间复杂度和空间复杂度,可以使用双指针方法

双指针方法

双指针的思路是维护一个当前滑动窗口的最大值和它的位置,并在窗口滑动时进行更新。在保证正确性的同时,利用双指针和局部最大值的特点,实现了高效的滑动窗口最大值的计算。

object Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * @param num int整型一维数组
     * @param size int整型
     * @return int整型一维数组
     */
    fun maxInWindows(num: IntArray, size: Int): IntArray {
        if (num.size < size || size == 0) return intArrayOf() // 如果窗口大于数组长度或窗口大小为0,返回空数组

        val result = mutableListOf<Int>()
        var start = 0
        var end = 0
        var maxIdx = -1 // 记录当前窗口最大值的索引

        while (end < num.size) {
            // 如果最大值的索引小于start,重新计算当前窗口最大值
            if (maxIdx < start) {
                maxIdx = start
                for (i in start..end) {
                    if (num[i] > num[maxIdx]) {
                        maxIdx = i
                    }
                }
            } else if (num[end] > num[maxIdx]) {
                maxIdx = end
            }

            // 当窗口大小达到 size 时,记录最大值
            if (end - start + 1 == size) {
                result.add(num[maxIdx])
                start++ // 窗口滑动
            }
            end++
        }

        return result.toIntArray()
    }
}
  • 时间复杂度 O(n)

    • 双指针和双端队列的组合使得每个元素最多进入和离开队列一次,因此总的时间复杂度为 O(n)。
    • 相比于暴力方法每次计算每个窗口最大值的 O(n * size) 时间复杂度,显著降低了计算时间。
  • 空间复杂度 O(size)

    • 双端队列最多存储 size 个元素的索引,因此空间复杂度为 O(size)。
    • 使用双端队列存储索引而非值,进一步减少了空间消耗。

总结

解决滑动窗口问题通常需要用到双指针技术,通过维护一个固定大小的窗口来遍历数组。在每次窗口滑动过程中,计算并记录下窗口内所需的统计量,如最大值、最小值或总和。为了提高效率,可以使用额外的数据结构,例如单调队列或平衡树,来快速找到窗口内的最大或最小元素。通过这种方式,可以在线性时间内完成窗口的更新和统计量的计算。

  • 22
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 在 Kotlin 中,你可以使用以下算法来查询一个数组中的最大: ``` fun findMax(numbers: IntArray): Int { var max = numbers[0] for (num in numbers) { if (num > max) { max = num } } return max } ``` 你可以调用这个函数来查询数组 `numbers` 中的最大。例如,你可以这样调用: ``` val numbers = intArrayOf(1, 2, 3, 4, 5) val max = findMax(numbers) ``` 这样,`max` 变量就会保存数组 `numbers` 中的最大。 ### 回答2: 在 Kotlin 中,可以使用以下代码来编写查询最大的算法: ```kotlin fun findMaxValue(numbers: Array<Int>): Int { var maxValue = numbers[0] // 将第一个元素设为最大 for (number in numbers) { if (number > maxValue) { maxValue = number // 如果当前元素比最大大,则将它设为最大 } } return maxValue } fun main() { val numbers = arrayOf(10, 5, 15, 8, 3) val max = findMaxValue(numbers) println("最大是: $max") } ``` 这段代码定义了一个名为 `findMaxValue` 的函数,用于查询传入的整数数组中的最大。在函数中,我们首先将数组中的第一个元素赋给变量 `maxValue`,然后通过遍历整个数组来找到更大的。如果当前元素比 `maxValue` 大,则更新 `maxValue` 的为当前元素。最后函数返回 `maxValue`。 在 `main` 函数中,创建了一个整数数组 `numbers`,并将其作为参数传递给 `findMaxValue` 函数。然后将返回的最大打印出来。 运行这段代码,输出结果为:最大是 15。 ### 回答3: 在Kotlin中,可以使用以下代码编写一个查询最大的算法: ```kotlin fun findMaxValue(numbers: List<Int>): Int { var maxValue = Int.MIN_VALUE for (number in numbers) { if (number > maxValue) { maxValue = number } } return maxValue } ``` 这段代码定义了一个名为`findMaxValue`的函数,它接受一个整数列表作为参数,并返回列表中的最大。我们将`maxValue`初始化为`Int.MIN_VALUE`,这样列表中的任何都可以大于它。 然后,我们使用`for`循环遍历整数列表中的每个元素。对于每个元素,我们检查它是否大于`maxValue`,如果是,则将`maxValue`更新为当前元素的。通过这种方式,我们可以找到整个列表中的最大。 最后,我们返回最大。 使用这个算法,我们可以轻松地找到任何给定整数列表的最大。例如,如果我们有一个包含`[3, 9, 5, 1, 7]`的整数列表,调用`findMaxValue([3, 9, 5, 1, 7])`函数将返回`9`作为最大

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值