链接
开头
下面三个都要掌握的技巧性数据结构
小顶堆 找出第k小的数 (找一天去背一下,因为js没有,也怕被问到手写)
单调队列 处理 滑动窗口最大值(困难)
单调栈:单调栈用途不太广泛,只处理一种典型的问题,叫做 Next Greater Element。
496. 下一个更大元素 I (简单)
这里多了一步是,在nums2中找到目标值nums1。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
var nextGreaterElement = function(nums1, nums2) {
let stack = [], nge = new Array(nums2.length)
// nums[i] 身后的 next great number
for(let i=nums2.length-1; i>=0; i--) {
// 倒着往栈里放
while(stack.length && stack[stack.length-1] <= nums2[i]){
// 矮个起开
stack.pop()
}
nge[i] = stack.length==0 ? -1 : stack[stack.length-1]
// 最后才把自己加进去
stack.push(nums2[i])
}
// 索引在nums2的位置
ans = nums1.map(val => {
return nge[nums2.indexOf(val)]
})
return ans
};
739. 每日温度 (中等)
来一个简单的变形
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指在第 i 天之后,才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。
这个问题本质上也是找 Next Greater Number,只不过现在不是问你 Next Greater Number 是多少,而是问你当前距离 Next Greater Number 的距离而已。
stack放元素索引,而不是元素
而res不是存放更大数的索引,而是与更大数的索引距离
var dailyTemperatures = function(temperatures) {
let nums = temperatures
let stack = [], res = new Array(nums.length)
for (let i=nums.length-1; i>=0; i--) {
while(stack.length && nums[stack[stack.length-1]] <= nums[i]) stack.pop()
res[i] = stack.length==0 ? 0 : (stack[stack.length-1] - i)
stack.push(i)
}
return res;
};
503. 下一个更大元素 II (中等)
给定一个循环数组 nums ( nums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素 。
数字 x 的 下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1 。
处理环形数组
看了这个图就可以动手写了:
var nextGreaterElements = function(nums) {
let pre = nums.length - 1
for(let i=0; i<pre; i++) nums.push(nums[i])
let stack = [], res = new Array(nums.length)
for (let i=nums.length-1; i>=0; i--) {
while(stack && stack[stack.length-1]<=nums[i]) stack.pop()
res[i] = stack.length==0 ? -1 : stack[stack.length-1]
stack.push(nums[i])
}
// slice不改变原数组,splice改变
res.splice(pre+1,)
return res
};
但是,我们可以不用构造新数组,而是利用循环数组的技巧来模拟数组长度翻倍的效果。
代码随想录
84. 柱状图中最大的矩形 (困难)
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
看了前面两题关于双指针,再加上单调栈,我是完成了该困难题目。因为前面给了我启发:
- 单独看每一个柱子应该做什么
- 以该柱子为高,那它最左和最右的边界是什么——就是第一个比它小的柱子
- 第一个比自身小的数的索引,不就是每日温度,只不过每日温度求比自身大,而且是索引距离。
你不会单调栈,直接循环(并不能通过leetcode,超时了)。
但发现42.接雨水,如果使用单调栈是从横向出发
那这一题是结合了单调栈 与 以列为单位
var largestRectangleArea = function(heights) {
// 找到第一个比自己低的柱子的索引
let stack = [], len = heights.length
let lmin = new Array(len), rmin = new Array(len)
for(let i=0; i<len; i++) {
while(stack.length && heights[stack[stack.length-1]] >= heights[i]) stack.pop()
lmin[i] = stack.length==0 ? -1 : stack[stack.length-1]
stack.push(i)
}
stack = []
for(let i=len-1; i>=0; i--) {
while(stack.length && heights[stack[stack.length-1]] >= heights[i]) stack.pop()
rmin[i] = stack.length==0 ? len : stack[stack.length-1]
stack.push(i)
}
// console.log(lmin,rmin)
let max = 0
for(let i=0; i<len; i++){
max = Math.max(max, heights[i] * (rmin[i] - lmin[i] - 1))
}
return max;
};
402. 移掉 K 位数字 (困难)
给你一个以字符串表示的非负整数 num 和一个整数 k ,移除这个数中的 k 位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。
这道题不是说把较大的数删除就可以了,因为要考虑他的位置,位置比数值重要。
实例1:把低位的3代替高位的4
实例2:低位的0代替高位的1
可得出结果:遇到前一个比自己值要大的,把他替换掉。(前一个更大的元素),这题特别一点,看到比自己小的留着,而不是把他扔掉。比自己大的,不是作为答案,而是扔掉。
最后还要删的话,因为是递增序列,肯定从最后删除
如果删除次数够了,后面数字就补上
一步步来,难度不大:
var removeKdigits = function(num, k) {
let stack = [], i = 0
for(; i<num.length && k; i++) {
while(stack.length && stack[stack.length-1] > num[i] && k) {
stack.pop()
k--
}
if(k==0) break
stack.push(num[i])
}
while(k && stack.length) {
stack.pop()
k--
}
while(i<num.length) {
stack.push(num[i])
i++
}
//"0200"
while(stack[0] == '0') stack.shift()
return stack.length==0 ? "0" : stack.join("")
};
算法小抄:一道数组去重的算法题
316. 去除重复字母
给你一个字符串 s ,请你去除字符串中重复的字母,使得每个字母只出现一次。需保证 返回结果的字典序最小(要求不能打乱其他字符的相对位置)。
与1081. 不同字符的最小子序列 题目完全一样:
返回 s 字典序最小的子序列,该子序列包含 s 的所有不同字符,且只包含一次。
要去重
不能打乱s中字符出现的相对顺序
字典序最小
注意:dic和count 的操作都是对应的。
var removeDuplicateLetters = function(s) {
let dic = {} // 记录是否重复
let count = {} // 记录个数
let stack = [] // 答案栈
for(c of s) {
count[c] = (count[c] || 0) + 1
}
// console.log(count)
for(c of s) {
count[c]--
if(dic[c]) continue //已经存在
while(stack.length && stack[stack.length-1] > c && count[stack[stack.length-1]]) {
let x = stack.pop()
dic[x] = false // 栈保证只会出现一次, 删除了就不存在
}
stack.push(c)
dic[c] = true
// console.log(stack)
}
return stack.join("")
};
展望
下一步就看一下 二叉堆 和 区间问题。这两个解决完就正式进入三大算法(回溯,贪心,动态规划)