239. 滑动窗口最大值
题目:
给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回 滑动窗口中的最大值
思路:
只想到一个暴力解法,用两次循环,然后分别获得起始的滑动窗口和最大值,但是在输入特别大的案例的时候超出时间限制。
代码:
自己写的错误代码
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var maxSlidingWindow = function(nums, k) {
const res = [],tmp = []
for (let i = 0;i<k;i++) {
tmp.push(nums[i])
}
console.log(tmp)
for (let i =0;i<nums.length-k+1;i++) {
res.push(Math.max(...tmp))
tmp.shift()
tmp.push(nums[i+k])
}
return res
};
优化:
看了代码随想录的思路,没看明白,所以去看了视频看明白了思路,自己写了一下,单调队列能写出来,主函数里的添加逻辑有点迷糊,于是去看了大佬的写法,思路如下:
- 用两个指针分别指向数组的头尾
- 先对尾指针遍历,添加k个元素进单调队列中
- 然后对尾指针遍历,每次添加尾指针的元素进入单调,删除单调队列中头指针的元素,然后把当前滑动窗口的最大值添加进结果数组中。
- 最后返回结果数组。
代码:
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var maxSlidingWindow = function(nums, k) {
if (nums.length<=k) return [Math.max(...nums)]
const res = [],queue = new myQueue()
let i = 0,j=0
while (i<k) {
queue.myPush(nums[i++])
}
res.push(queue.myGetMax())
while (i<nums.length) {
queue.myPush(nums[i++])
queue.myPop(nums[j++])
res.push(queue.myGetMax())
}
return res
};
class myQueue {
constructor() {
this.queue = []
}
myPush = (x) => {
while (this.queue[this.queue.length-1]<x){
this.queue.pop()
}
this.queue.push(x)
}
myPop=(x)=>{
if (this.queue.length&&this.queue[0]===x){
this.queue.shift()
}
}
myGetMax=()=> {
return this.queue[0]
}
}
总结:
这道题是做的第一个难度为困难的题,心里难免有点虚,可能会觉得自己肯定做不出来,还是尝试做了一下,暴力解法超时,去看了代码随想录的思路,最难的可能就是想出单调队列的方法,其实整个代码可以说不难,除了主函数那里有一点点难想到逻辑,有一点点绕,但是做完这道题以后,这个思路就是我的了。
347. 前 K 个高频元素
题目:
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
思路:
一开始想的就是用一个map遍历数组,key是元素,value是元素出现的次数,然后基于value对map排序,输出前k个。但是这个跟队列也没关系,代码我就没写,直接去看了代码随想录的思路。
代码:
没写。
优化:
看了代码随想录的思路,感觉不太好理解,可能是当初二叉树没学好,堆啥的不太了解,看到优先级序列一脸懵,而且看完写不出来,去看了大佬的代码甚至也懵,只能一遍一遍捋,捋完以后自己敲了一遍,半卡不卡的写出来,中间停下来捋了好几次逻辑。 下面说一下思路:
- 首先定义一个优先级序列也就是一个堆
- 定义一个数组做队列,定义一个函数传入回调函数,后续用来定义堆的排列顺序。
- 定义堆的添加元素方法:
- 首先添加一个元素
- 然后声明两个指针,第一个指针index指向新添加的元素,第二个指针指向新添加元素的父元素。
- 声明一个循环,判断条件是只要父元素不小于0并且父元素的value大于新添加元素的value就执行:
- 把父元素和新添加元素换个位置
- 把父元素指针指向的位置赋值给index
- 重新找到index的父元素
- 定义堆的删除元素方法:
- 把堆顶元素拿到,最后用来返回,方便最后获取结果。
- 把队列末尾的元素取出放到堆顶位置
- 声明一个指针index指向堆顶,指针left指向左节点,指针child代表要操作的节点,指向左右节点中value更小的那个节点
- 声明一个循环,判断条件是child节点有含义并且index堆顶的value大于当前最小value的节点,执行:
- index和child节点的元素互换位置
- 把index重新指向child节点,此时child作为新的堆顶
- left指向index的左节点
- child再次指向左右节点中value较小的那个节点
- 定义一个size方法获取队列的长度
- 定义一个compare方法,设置了堆的排列顺序,利用传入的回调函数。同时判断如果相比较的节点中有一个不存在的情况。
- 然后是主函数,首先设置一个map,存入数组中出现的元素以及他们出现的次数,用键值对的形式保存
- 遍历map,把每个元素放入到堆中,超出k,就删除一个元素
- 声明一个res数组存放结果
- 倒序遍历堆,把最后的结果放入res数组中然后返回
代码:
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var topKFrequent = function(nums, k) {
const map = new Map()
for (const num of nums) {
map.set(num,(map.get(num)||0)+1)
}
const heap = new Heap((a,b)=>a[1]-b[1])
for (const entry of map.entries()) {
heap.myPush(entry)
if (heap.size()>k) {
heap.myPop()
}
}
const res = []
for (let i = heap.size()-1;i>=0;i--) {
res[i] = heap.myPop()[0]
}
return res
};
class Heap {
constructor(compareFn) {
this.compareFn = compareFn
this.queue = []
}
myPush = (item) => {
this.queue.push(item)
let index = this.queue.length - 1,
parent = Math.floor((index-1)/2)
while (parent>=0&&this.compare(parent,index)>0) {
[this.queue[index],this.queue[parent]] = [this.queue[parent],this.queue[index]]
index = parent
parent= Math.floor((index-1)/2)
}
}
myPop = () => {
const out = this.queue[0]
this.queue[0] = this.queue.pop()
let index = 0,
left = 1,
child = this.compare(left,left+1)>0?left+1:left
while (child!==undefined&&this.compare(index,child)>0) {
[this.queue[index],this.queue[child]] = [this.queue[child],this.queue[index]]
index = child
left = 2*index + 1
child = this.compare(left,left+1)>0?left+1:left
}
return out
}
size = () => {
return this.queue.length
}
compare = (index1, index2) => {
if (this.queue[index1]===undefined) return 1
if (this.queue[index2]===undefined) return -1
return this.compareFn(this.queue[index1],this.queue[index2])
}
}
总结:
这道题我感觉比上道题还难,感觉是十二天以来做的最难的一道题,可能是因为对堆这个数据结构的不熟悉,感觉特别难理解,看了答案代码都要一遍一遍的捋逻辑,遇到卡壳的地方,就多代入几个用例,一遍一遍的顺,大概弄清楚以后,写了代码。 其实在写这篇博客之前,对于删除操作中,left左节点的取值还是有点模糊,可能是因为对堆不熟悉,然后硬着头皮写了一遍思路以后,豁然开朗了,收获很多。
Day13总结
今天的题目还是挺难的,前天做的栈的题目以为队列一样简单,但是今天的两题思路都挺难想的,也是做了那么多天题目感觉最难的一道,希望二刷的时候能见证自己的成果。