42. 接雨水
题目链接:42. 接雨水
难度:困难
刷题状态:2刷
新知识:
- `nums.unshift(a)` 在数组前面加元素
解题过程
思考
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
从左往右,从右往左,我自己敲出来了,但是还有可以改进的地方
题解分析
参考题解链接:接雨水
1刷的过程
/**
* @param {number[]} height
* @return {number}
*/
var trap = function (height) {
//这种方法的核心是从两边往中间逼近
const n = height.length;
let res = 0;
let leftHeight = 0;
let rightHeight = 0;
let left = 0;
let right = n - 1;
while (left < right) {
leftHeight = Math.max(leftHeight, height[left]);
rightHeight = Math.max(rightHeight, height[right]);
if (leftHeight < rightHeight) {
//left右移
res += leftHeight - height[left];
left++;
} else {
//right左移
res += rightHeight - height[right];
right--;
}
}
return res;
};
这是2刷我自己敲的
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
let n=height.length,res=0
let left=0,right=n-1
let lh=height[0],rh=height[n-1],tag=lh<rh?'l':'r'
while(left<right){
lh=Math.max(lh,height[left])
rh=Math.max(rh,height[right])
if(tag=='l'){
x=Math.min(lh,rh)-height[left]
}else{
x=Math.min(lh,rh)-height[right]
}
x=x<0?0:x
res+=x
if(height[left]<height[right]){
left++
tag='l'
}else{
right--
tag='r'
}
}
return res
};
可以看出还有改进的地方,主要是res的计算可以放在判断height[left]<height[right] 的大小之后
而且判断条件改成了lh<rh,应该是不断往中间逼近左右两边的最大值
再手搓一遍
手搓答案(无非废话版)
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
let res=0,n=height.length
let lh=0,rh=0,left=0,right=n-1
while(left<right){
lh=Math.max(lh,height[left])
rh=Math.max(rh,height[right])
if(lh<rh){
res+=lh-height[left]
left++
}else{
res+=rh-height[right]
right--
}
}
return res
};
总结
典型多刷!
3. 无重复字符的最长子串
题目链接:3. 无重复字符的最长子串
难度:中等
刷题状态:2刷
新知识:
- `nums.shift(a)` 在数组前面加元素
解题过程
思考
示例 1:
输入: s = "abcabcbb"
输出: 3
解释: 因为无重复字符的最长子串是 "abc"
,所以其长度为 3。
滑动窗口题目,注意 s
由英文字母、数字、符号和空格组成
手搓出来了
题解分析
参考题解链接:无重复字符的最长子串
这里用的是map方法,还可以试试用set,还要把计算长度的逻辑放在处理完重复情况之后
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
let res=0,str=[],n=s.length
let left=0
let count=new Map()
for(let right=0;right<n;right++){
let x=s[right]
res=Math.max(res,str.length)
str.push(x)
if(!count.get(x)){
count.set(x,0)
}
count.set(x,count.get(x)+1)
while(count.get(x)==2){
let y=s[left]
count.set(y,count.get(y)-1)
str.shift(y)
left++
}
}
res=Math.max(res,str.length)
return res
};
手搓答案(无非废话版)
这里用的是set方法
/**
* @param {string} s
* @return {number}
*/
var lengthOfLongestSubstring = function(s) {
let res=0,n=s.length
let left=0,right=0
let set=new Set()
while(right<n){
while(set.has(s[right])){
set.delete(s[left])
left++
}
set.add(s[right])
res=+Math.max(res,right-left+1)
right++
}
return res
};
总结
pass
438. 找到字符串中所有字母异位词
题目链接:438. 找到字符串中所有字母异位词
难度:中等
刷题状态:1刷
新知识:
- `set.size` set的长度
解题过程
思考
示例 1:
输入: s = "cbaebabacd", p = "abc" 输出: [0,6] 解释: 起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。 起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。
这道题要处理两个问题,遇到重复的,和遇到不在p里面的字符
但是p可能包含重复元素,如果是有重复元素,set就不行了,只能考虑map
等等等等,要找的是异位词,只用确保字母和对应的个数是一样的就行了
手搓出来了!!,看看还能怎么改进
题解分析
参考题解链接:找到字符串中所有字母异位词
手搓答案(无非废话版)
两种方法
var findAnagrams = function(s, p) {
let res=[],n=s.length
let left=0,right=0
let set=new Set(p)
let map=new Map()
for(let m of p){
map.set(m,(map.get(m)||0)+1)
}
while(right<n){
//如果p中没有
if(!set.has(s[right])){
right++
left=right
let mapp=new Map()
for(let m of p){
mapp.set(m,(mapp.get(m)||0)+1)
}
map=mapp
continue
}
//如果加入这个字符会超出p中个数的限制
while(map.get(s[right])==0){
map.set(s[left],map.get(s[left])+1)
left++
}
map.set(s[right],map.get(s[right])-1)
if(isOk(map)){
res.push(left)
}
right++
}
return res
};
function isOk(map){
for(let [key,val] of map){
if(val) return false
}
return true
}
var findAnagrams = function(s, p) {
let res=[],n=s.length
let left=0,right=0
let map=new Map()
let mapp=new Map()
for(let m of p){
mapp.set(m,(mapp.get(m)||0)+1)
}
while(right<n){
if(!mapp.get(s[right])){
right++
left=right
map=new Map()
continue
}
while((map.get(s[right])||0)==mapp.get(s[right])){
map.set(s[left],map.get(s[left])-1)
left++
}
map.set(s[right],(map.get(s[right])||0)+1)
if(isOk(map,mapp)){
res.push(left)
}
right++
}
return res
}
function isOk(map,mapp){
if(map.size!=mapp.size) return false
for(let [key,val] of map){
if(val!=mapp.get(key)) return false
}
return true
}
总结
真是酣畅淋漓的一场战斗啊!不过好像还有更快的做法,是用到了
s
和 p
仅包含小写字母 的条件,
先把长度为26的数组建立好
560. 和为 K 的子数组
题目链接:560. 和为 K 的子数组
难度:中等
刷题状态:1刷
新知识:
解题过程
思考
示例 1:
输入:nums = [1,1,1], k = 2 输出:2
类型是子串,有很多种方法可以做出来,包括dfs递归,不过这题应该没那么麻烦
用滑动窗口做试试
对于每个新增的right,都要left从头筛选一遍
做出来了,但速度慢,复杂度O(n2)
var subarraySum = function(nums, k) {
let res=0,n=nums.length
let sum=0
let left=0,right=0
while(right<n){
left=right
sum=0
while(left>=0){
sum+=nums[left]
if(sum==k) res++
left--
}
right++
}
return res
};
题解分析
参考题解链接:和为K的子数组
详细分析如下,复杂度O(n)
var subarraySum = function(nums, k) {
//用来存target出现的次数
//初始化为了防止出现
//当遍历到某个位置时,如果当前的前缀和 sum 恰好等于 k(即找到了一个从数组开始到当前位置的子数组,其和为 k),那么我们需要一个初始的0前缀和来匹配这个情况。因为 sum - k = sum - sum = 0,所以我们需要 sum 中有一个键为0的条目来记录这种情况。
let map=new Map([[0,1]])//Map(1) { 0 => 1 }
let res=0
//用于计算当前遍历到某个位置时的前缀和。
let sum=0
for(let i=0;i<nums.length;i++){
sum+=nums[i]
// 目标前缀和
let target=0-(k-sum)//这里target是负数
//如果map中有target,说明存在一个或多个之前的子数组 它们的和(target)
//其和加上当前遍历到 i 的子数组的和(sum)等于 k
res+=map.has(target)?map.get(target):0
//更新map中当前前缀和 sum 的次数
map.set(sum,(map.get(sum)||0)+1)
// console.log(res,map)
}
return res
}
手搓答案(无非废话版)
var subarraySum = function(nums, k) {
let res=0,n=nums.length
let sum=0
let map=new Map([[0,1]])
for(let i=0;i<n;i++){
sum+=nums[i]
let target=0-(k-sum)
res+=map.has(target)?map.get(target):0
map.set(sum,(map.get(sum)||0)+1)
}
return res
}
总结
注意取值取的target,存值存的sum
239. 滑动窗口最大值
题目链接:239. 滑动窗口最大值
难度:困难
刷题状态:1刷
新知识:
解题过程
思考
示例 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
这题的难点在于怎么简化
下面这样可行,但会超时
var maxSlidingWindow = function(nums, k) {
let res=[]
let right=k
while(right<=nums.length){
let a=nums.slice(right-k,right)
res.push(Math.max(...a))
right++
}
return res
};
能不能想办法记录一下每段的最大值及其索引
看一下下一个left是否越过了这个最大值,以及right是否更新了最大值呢?
想不出来了,明天再看吧
题解分析
参考题解链接:预处理+递归(Python/Java/C++/Go/JS/Rust)
下面是详细分析
有点难理解,不过确实是最简单的方法,最主要要理解q是存的索引,而且是当前窗口的索引,而且只从当前窗口最大值开始存
注意几个细节 q.length&&nums[i]>=nums[q[q.length-1]]这里是>=
while(q[0]<=i-k){ 这里是while,if也可以
var maxSlidingWindow = function(nums, k) {
const n = nums.length;
//这个数组将用作双端队列(deque),用来存储当前窗口内元素的索引,这些元素按值从大到小排序。
//q[0]始终是窗口内的最大值
const q = [];
for (let i = 0; i < k; i++) {
while (q.length && nums[i] >= nums[q[q.length - 1]]) {
q.pop();
}
q.push(i);
}
//用于存储每个窗口的最大值
const ans = [nums[q[0]]];
for (let i = k; i < n; i++) {
while (q.length && nums[i] >= nums[q[q.length - 1]]) {
q.pop();
}
q.push(i);
// console.log(q)
//左边移出去了
while (q[0] <= i - k) {
q.shift();
}
ans.push(nums[q[0]]);
}
return ans;
};
手搓答案(无非废话版)
var maxSlidingWindow = function(nums, k) {
let res=[],n=nums.length
let q=[]
for(let i=0;i<k;i++){
while(q.length&&nums[i]>=nums[q[q.length-1]]){
q.pop()
}
q.push(i)
}
res.push(nums[q[0]])
for(let i=k;i<n;i++){
while(q.length&&nums[i]>=nums[q[q.length-1]]){
q.pop()
}
q.push(i)
while(q[0]<=i-k){
q.shift()
}
res.push(nums[q[0]])
}
return res
}
总结
这题的类型是子串,要注意第一个循环是为了给q赋初值,不会出现left离开窗口的情况