js刷题笔记
数组篇
1. 查找
2. 移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1: 给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。
示例 2: 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
- 这题暴力解居然能100%
var removeElement = function(nums, val) {
let n=nums.length
let count=n
for(let i=0;i<n;i++){
if(nums[i]===val){
count--
for(let j=i;j<n;j++){
nums[j]=nums[j+1]
}
i--
}
}
return count
};
- 双指针
快指针寻找新数组的元素,慢指针指向新数组现在需要更新的下标
var removeElement = function(nums, val) {
let fast=0,slow=0,n=nums.length
while(fast<n){
if(nums[fast]!=val){
nums[slow++]=nums[fast]
}
fast++
}
return slow
};
3. 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
示例 1:
输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100],排序后,数组变为 [0,1,9,16,100]
示例 2:
输入:nums = [-7,-3,2,3,11]
输出:[4,9,9,49,121]
- 暴力
因为要排序所以暴力有点慢 - 双指针
因为原数组是升序,所以结果数组中的最大值肯定是在数组的首部或尾部
用一个指针指向首部,一个指针指向尾部,每次选取最大值放在新的数组中
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortedSquares = function(nums) {
let n=nums.length
let i=0,j=n-1
let res=new Array(n).fill(0)
let k=n-1
while(i<=j){
let left=Math.pow(nums[i],2)
let right=Math.pow(nums[j],2)
if(left<right){
res[k--]=right
j--
}else{
res[k--]=left
i++
}
}
return res
};
4. 长度最小的子数组
暴力解法,超时了
/**
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
var minSubArrayLen = function(target, nums) {
let n=nums.length
let res=0,min=n+1
for(let i=0;i<n;i++){
res=0
for(let j=i;j<n;j++){
res+=nums[j]
if(res>=target){
min=Math.min(min,j-i+1)
j=n
}
}
}
if(min>n) return 0
return min
};
滑动窗口
上面那个是我写的,用了一个条件判断,代码随想录给出的答案要快一点,感觉和回溯的写法有点像?
就是先一直扩大窗口到sum>=target, 然后更新min,再一直缩小窗口到sum<target, 实现滑动窗口
/**
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
var minSubArrayLen = function(target, nums) {
let l=0,r=0,n=nums.length,min=n+1
let window=nums[0]
while(r<n){
if(window<target){
r++
window+=nums[r]
}else{
min=Math.min(min,r-l+1)
if(min===1) return 1
window-=nums[l]
l++
}
}
return min==n+1?0:min
};
var minSubArrayLen = function(target, nums) {
let start, end
start = end = 0
let sum = 0
let len = nums.length
let ans = Infinity
while(end < len){
sum += nums[end];
while (sum >= target) {
ans = Math.min(ans, end - start + 1);
sum -= nums[start];
start++;
}
end++;
}
return ans === Infinity ? 0 : ans
};
5. 螺旋数组
startX++ 起始列右移一列
offset++ 终止列左移一列
……
/**
* @param {number} n
* @return {number[][]}
*/
var generateMatrix = function(n) {
let startX=startY=0
let loop=Math.floor(n/2)
let mid=Math.floor(n/2)
let offset=1
let count=1
let res=new Array(n).fill(0).map(()=>new Array(n).fill(0))
while(loop--){
let row=startX,col=startY
for(;col<n-offset;col++){
res[row][col]=count++
}
for(;row<n-offset;row++){
res[row][col]=count++
}
for(;col>startY;col--){
res[row][col]=count++
}
for(;row>startX;row--){
res[row][col]=count++
}
startX++
startY++
offset++
}
if(n%2){
res[mid][mid]=count
}
return res
};
6. 区间和
这道题就是用前缀和做就好了,但是要注意js怎么接收输入
function prefixSum() {
const readline = require('readline')
const rl = readline.createInterface({
input: process.stdin,
output:process.stdout
})
let inputLines = []
rl.on('line', (line) => {
inputLines.push(line.trim())
})
rl.on('close', () => {
const n = parseInt(inputLines[0])
let sum = new Array(n)
sum[0] = parseInt(inputLines[1])
for (let i = 1; i < n + 1; i++){
let value=parseInt(inputLines[i+1])
sum[i]=sum[i-1]+value
}
for (let i = n + 1; i < inputLines.length; i++){
let [left, right] = inputLines[i].split(' ').map(Number)
if (left == 0) {
console.log(sum[right])
} else {
console.log(sum[right]-sum[left-1])
}
}
})
}
prefixSum()
44. 开发商购买土地
和代码随想录中给出的题解思路有点不一样,这里直接存储的就是前缀和,划分时每次都用最后一列减去2倍的划分左侧部分
function func() {
const readline = require('readline')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let inputLines=[]
rl.on('line', (line) => {
inputLines.push(line.trim())
})
rl.on('close', () => {
let [n, m] = inputLines[0].split(' ').map(Number)
let res = new Array(n).fill(0)
let col = new Array(m).fill(0)
let row = new Array(n).fill(0)
for (let i = 0; i < n; i++){
res[i]=inputLines[i+1].split(' ').map(Number)
}
let sum1=0,sum2=0
for (let i = 0; i < n; i++){
for (let j = 0; j < m; j++){
row[i]+=res[i][j]
}
row[i] += sum1
sum1 = row[i]
}
for (let j = 0; j < m; j++){
for (let i = 0; i < n; i++){
col[j]+=res[i][j]
}
col[j] += sum2
sum2 = col[j]
}
let min = Infinity
for (let i = 1; i < n; i++){
min = Math.min(min, Math.abs(row[n-1]-row[i-1]*2))
}
for (let i = 1; i < m; i++){
min=Math.min(min,Math.abs(col[m-1]-col[i-1]*2))
}
console.log(min)
})
}
func()
链表篇
1. 移除链表元素
没什么好说的
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @param {number} val
* @return {ListNode}
*/
var removeElements = function(head, val) {
let hummy=new ListNode(0,null)
hummy.next=head
let cur=head
let pre=hummy
while(cur){
if(cur.val===val){
cur=cur.next
pre.next=cur
}else{
cur=cur.next
pre=pre.next
}
}
return hummy.next
};
2. 设计链表
class LinkNode{
constructor(val,next){
this.val=val
this.next=next
}
}
var MyLinkedList = function() {
this._size=0
this._tail=null
this._head=null
};
/**
* @param {number} index
* @return {number}
*/
MyLinkedList.prototype.get = function(index) {
if(index>=this._size||index<0) return -1
let cur=new LinkNode(0,this._head)
while(index-->=0){
cur=cur.next
}
return cur.val
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtHead = function(val) {
let newHead=new LinkNode(val,this._head)
this._head=newHead
this._size++
if(!this._tail){
this._tail=newHead
}
};
/**
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtTail = function(val) {
let newNode=new LinkNode(val,null)
this._size++
if(!this._head){
this._head=newNode
this._tail=newNode
}else{
this._tail.next=newNode
this._tail=newNode
}
};
/**
* @param {number} index
* @param {number} val
* @return {void}
*/
MyLinkedList.prototype.addAtIndex = function(index, val) {
if(index<0||index>this._size) return
let cur=new LinkNode(0,this._head)
if(!index){
this.addAtHead(val)
return
}
if(index===this._size){
this.addAtTail(val)
return
}
index--
while(index-->=0){
cur=cur.next
}
let tmp=cur.next
cur.next=new LinkNode(val,tmp)
this._size++
return
};
/**
* @param {number} index
* @return {void}
*/
MyLinkedList.prototype.deleteAtIndex = function(index) {
if(index < 0 || index >= this._size) return;
if(index===0) {
this._head = this._head.next;
if(index === this._size - 1){
this._tail = this._head
}
this._size--;
return;
}
let cur=new LinkNode(0,this._head)
// while(index-->0){
// cur=cur.next
// }
for(let i=0;i<index;i++){
cur=cur.next
}
cur.next=cur.next.next
if(index === this._size - 1) {
this._tail = cur;
}
this._size--;
};
/**
* Your MyLinkedList object will be instantiated and called as such:
* var obj = new MyLinkedList()
* var param_1 = obj.get(index)
* obj.addAtHead(val)
* obj.addAtTail(val)
* obj.addAtIndex(index,val)
* obj.deleteAtIndex(index)
*/
吗的不知道为什么注释部分不对
吗的凭什么说我代码写的史啊👊😭
3. 反转链表
var reverseList = function(head) {
let pre=null,cur=head
while(cur){
let nxt=cur.next
cur.next=pre
pre=cur
cur=nxt
}
return pre
};
4. 两两交换链表中的节点
递归,每次交换链表的前两个节点
var swapPairs = function(head) {
let help=function(head){
if(!head||!head.next){
return head
}
let pre=head
head=head.next
let nxt=head.next
head.next=pre
pre.next=help(nxt)
return head
}
return help(head)
};
5. 删除链表的倒数第N个节点
快慢指针,right先走n,再走length-n到终点,left走length-n正好是倒数第n个
var removeNthFromEnd = function(head, n) {
let dummy=new ListNode(null,head)
let left=dummy,right=dummy
while(--n){
right=right.next
}
right=right.next
while(right.next){
left=left.next
right=right.next
}
left.next=left.next.next
return dummy.next
};
6. 链表香蕉
交换变量注意加 “分号” ,
两个数组交换变量在同一个作用域下时如果不加分号,下面两条代码等同于一条代码: [curA, curB] = [lenB, lenA]
var getIntersectionNode = function(headA, headB) {
function getListLen(head){
let len=0
while(head){
len++
head=head.next
}
return len
}
let lenA=getListLen(headA),lenB=getListLen(headB)
let curA=headA,curB=headB
if(lenA<lenB){
[curA,curB]=[curB,curA];//⚠⚠⚠
[lenA,lenB]=[lenB,lenA]
}
let i=lenA-lenB
for(let j=0;j<i;j++){
curA=curA.next
}
while(curA){
if(curA==curB){
return curA
}
curA=curA.next
curB=curB.next
}
return null
};
7. 环形链表
var detectCycle = function(head) {
while(head!==null){
if(head.tag==true) return head
head.tag=true
head=head.next
}
return null
};
这就是js
再想一秒脑子就爆炸了,什么东西
思路
slow每次走一步,fast每次走两步,一定在环里相遇
相遇后从头出发一个指针每次走一步,从相遇的节点出发一个指针每次走一步,
再次相遇就是环的起点
var detectCycle = function(head) {
if(!head||!head.next) return null
let slow=head.next,fast=head.next.next
while(fast&&fast.next){
slow=slow.next
fast=fast.next.next
if(fast==slow){
slow=head
while(fast!==slow){
slow=slow.next
fast=fast.next
}
return slow
}
}
return null
};
寻找重复数
怎么这么冲明哇
数组里的数都小于等于索引最大值,所以可以把数组元素当做索引,用环形链表来做,环形链表的起点就是重复的数组元素的索引
/**
* @param {number[]} nums
* @return {number}
*/
var findDuplicate = function(nums) {
let slow=0,fast=0
while(true){
slow=nums[slow]
fast=nums[nums[fast]]
if(slow===fast){
let _slow=0
while(nums[_slow]!==nums[slow]){
_slow=nums[_slow]
slow=nums[slow]
}
return nums[slow]
}
}
};
8. 随机链表的复制
var copyRandomList = function(head, cachedNode = new Map()) {
// 1. 链表走到头了,返回 null
if (head === null) {
return null;
}
// 2. 如果当前节点还没拷贝
if (!cachedNode.has(head)) {
// 2.1 新建节点,只赋值 val,先放进 map(防止循环引用)
cachedNode.set(head, { val: head.val });
// 2.2 给这个新节点加上 next 和 random 的拷贝
Object.assign(cachedNode.get(head), {
next: copyRandomList(head.next, cachedNode),
random: copyRandomList(head.random, cachedNode)
});
}
// 3. 如果已经拷贝过,直接从 map 返回,避免死循环
return cachedNode.get(head);
};
LRU 缓存
可以看做是一摞书,get是抽出一本书放在顶部,put是在顶部放一本书,如果超出容量了就把底部的书拿走,这样就是实现了“逐出最久未使用的关键字”
维护一个双向链表,同时用一个map维护key和节点,实现O(1)复杂度
题解中只用了一个哨兵节点,实现了循环链表,太难理解了,这里用两个哨兵节点,用于处理边界问题
/**
* @param {number} capacity
*/
class Node{
constructor(key=0,value=0){
this.key=key
this.value=value
this.prev=null
this.next=null
}
}
class LRUCache {
constructor(capacity){
this.capacity=capacity
this.head=new Node()
this.tail=new Node()
this.head.next=this.tail
this.tail.prev=this.head
this.keyToNode=new Map()
}
#getNode(key){
if(!this.keyToNode.has(key)){
return null
}
const node=this.keyToNode.get(key)
this.#remove(node)
this.#pushFront(node)
return node
}
get(key){
const node=this.#getNode(key)
return node?node.value:-1
}
put(key,value){
let node=this.#getNode(key)
if(node){
node.value=value
return
}
node=new Node(key,value)
this.keyToNode.set(key,node)
this.#pushFront(node)
if(this.keyToNode.size>this.capacity){
const backNode=this.tail.prev
this.keyToNode.delete(backNode.key)
this.#remove(backNode)
}
}
#remove(x){
x.prev.next=x.next
x.next.prev=x.prev
}
#pushFront(x){
x.prev=this.head
x.next=this.head.next
this.head.next=x
x.next.prev=x
}
}
前缀树
总而言之言而总之,字符串前缀树就是一个二十六叉树,每个节点是一个字母,你可以顺着这个树查找连成的字符串,标记一个isEnd表示她是否是一个完整的字符串
var Trie = function() {
this.childen={}
};
/**
* @param {string} word
* @return {void}
*/
Trie.prototype.insert = function(word) {
let node=this.childen
for(const ch of word){
if(!node[ch]){
node[ch]={}
}
node=node[ch]
}
node.isEnd=true
};
/**
* @param {string} word
* @return {boolean}
*/
Trie.prototype.search = function(word) {
let node=this.startsWith(word)
return node.isEnd===undefined?false:true
};
/**
* @param {string} prefix
* @return {boolean}
*/
Trie.prototype.startsWith = function(prefix) {
let node=this.childen
for(const ch of prefix){
if(!node[ch]){
return false
}
node=node[ch]
}
return node
};
/**
* Your Trie object will be instantiated and called as such:
* var obj = new Trie()
* obj.insert(word)
* var param_2 = obj.search(word)
* var param_3 = obj.startsWith(prefix)
*/
哈希篇
1. 有效的字母异位词
hash hash hash
var isAnagram = function(s, t) {
if(s.length !== t.length) return false;
const resSet = new Array(26).fill(0);
const base = "a".charCodeAt();
for(const i of s) {
resSet[i.charCodeAt() - base]++;
}
for(const i of t) {
if(!resSet[i.charCodeAt() - base]) return false;
resSet[i.charCodeAt() - base]--;
}
return true;
};
2. 两个数组的交集
用Set()给大数组去重,再遍历小数组,用Set()存储交集
var intersection = function(nums1, nums2) {
if(nums1.length<nums2.length){
let _=nums1
nums1=nums2
nums2=_
}
let nums1Set=new Set(nums1)
let resSet=new Set()
for(let i=0;i<nums2.length;i++){
nums1Set.has(nums2[i])&&resSet.add(nums2[i])
}
return Array.from(resSet)
};
sbsbsbdsb!!!!!!!!!!1😭😭😭😭😭😭😭😭😭😭😭😭😭😭
字符串
1. 反转字符串
var reverseString = function(s) {
let i=0;j=s.length-1
while(i<j){
// [s[i],s[j]]=[s[j],s[i]]
let tmp=s[i]
s[i]=s[j]
s[j]=tmp
i++
j--
}
return s
};
能不能都是这种简单题啊
2. 右旋字符串
function fn() {
const readline = require('readline')
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let inputLines=[]
rl.on('line', (line) => {
inputLines.push(line.trim())
})
rl.on('close', () => {
let target = parseInt(inputLines[0])
let str = Array.from(inputLines[1])
let res = []
let n = str.length
for (let i = 0; i < n; i++){
res[(i + target) % n] = str[i]
}
console.log(res.join(''))
})
}
fn()
3. 和为k的子数组
前缀和+Map
在map中存入每个前缀和出现次数
遍历到当前前缀和时,在map中如果存在prefixSum-k ,则prefixSum - (prefixSum-k) == k
var subarraySum = function(nums, k) {
let ans=0,n=nums.length
let prefixSum=0
const map={0:1}
for(let i=0;i<n;i++){
prefixSum+=nums[i]
if(map[prefixSum-k]){
ans=ans+map[prefixSum-k]
}
if(map[prefixSum]){
map[prefixSum]++
}else{
map[prefixSum]=1
}
}
return ans
};
dp篇
重点是递归公式
dp[i]=dp[i-1]+dp[i-2]
1. 杨辉三角
var generate = function(numRows) {
const tri=[]
for(let i=0;i<numRows;i++){
tri.push(new Array(i))
tri[i][0]=1
tri[i][i]=1
}
for(let i=2;i<numRows;i++){
for(let j=1;j<i;j++){
tri[i][j]=tri[i-1][j-1]+tri[i-1][j]
}
}
return tri
};
2. 我是小偷
var rob = function(nums) {
let n=nums.length
const dp=[]
dp[0]=nums[0]
dp[1]=Math.max(dp[0],nums[1])
for(let i=2;i<n;i++){
dp[i]=Math.max(dp[i-1],dp[i-2]+nums[i])
}
return dp[n-1]
};
dp这么简单吗。。我不信
3. 最长回文子串
遍历字符串,把当前字符当作回文子串的中心进行判断
var longestPalindrome = function(s) {
if(s.length<2){
return s
}
let res=''
for(let i=0;i<s.length;i++){
helper(i,i)
helper(i,i+1)
}
function helper(m,n){
while(m>=0&&n<s.length&&s[m]==s[n]){
m--
n++
}
if(n-m-1>res.length){
res=s.slice(m+1,n)
}
}
return res
};
4. 最长公共子序列
var longestCommonSubsequence = function(text1, text2) {
let n=text1.length,m=text2.length
let dp=new Array(n+1).fill(0).map(()=>new Array(m+1).fill(0))
for(let i=1;i<=n;i++){
for(let j=1;j<=m;j++){
if(text1[i-1]==text2[j-1]){
dp[i][j]=dp[i-1][j-1]+1
}else{
dp[i][j]=Math.max(dp[i-1][j],dp[i][j-1])
}
}
}
console.log(dp)
return dp[n][m]
};
铺瓷砖
铺到第i列时前一列有四种情况,00 10 01 11,
根据这四种情况判断第i列铺满有几种方案
var numTilings = function (n){
let dp=new Array(n).fill(0).map(()=>new Array(4).fill(0))
let MOD=1e9+7 //防止整数溢出
dp[0][3]=1
dp[0][0]=1
for(let i=1;i<n;i++){
dp[i][0]=dp[i-1][3]
dp[i][1]=(dp[i-1][0]+dp[i-1][2])%MOD
dp[i][2]=(dp[i-1][0]+dp[i-1][1])%MOD
dp[i][3]=(dp[i-1][0]+dp[i-1][1]+dp[i-1][2]+dp[i-1][3])%MOD
}
return dp[n-1][3]
}
完全平方数
发现递推公式
还有你补药再把n[i][j]写成n[i,j]了
注意把记忆化搜索的表放在外面减少计算量
const memo=new Array(101).fill(0).map(()=>new Array(10001).fill(-1))
var numSquares = function(num) {
function dfs(i,j){
if(i===0) return j===0?0:Infinity
if(memo[i][j]!==-1) return memo[i][j]
if(i*i>j) {
memo[i][j]=dfs(i-1,j)
}else{
memo[i][j]=Math.min(dfs(i-1,j),dfs(i,j-i*i)+1)
}
return memo[i][j]
}
return dfs(Math.floor(Math.sqrt(num)),num)
};
换零钱
注意如果i<0或者j<0的时候说明该方案就不可行了,直接设置为Infinity排除这个方案
还是不要起和数组名字这么像的函数名了,debug都不好找
var coinChange = function(coins, amount) {
const dp=new Array(coins.length).fill(0).map(()=>new Array(10001).fill(-1))
function coin(i,j){
if(j===0) return 0
if(i<0||j<0) return Infinity
if(dp[i][j]!==-1) return dp[i][j]
if(coins[i]>j){
dp[i][j]=coin(i-1,j)
}else{
dp[i][j]=Math.min(coin(i-1,j), coin(i,j-coins[i])+1)
}
return dp[i][j]
}
return coin(coins.length-1,amount)===Infinity?-1:coin(coins.length-1,amount)
};
单词拆分字符串
dp[i]表示截止到i的字符串能否被拆分,那他能否被拆分呢,就要看有没有一个小于i的j能够实现dp[j]为true而且s.slice(j,i)也是一个字典里的单词
/**
* @param {string} s
* @param {string[]} wordDict
* @return {boolean}
*/
var wordBreak = function(s, wordDict) {
const wordSet=new Set(wordDict)
const len=s.length
const dp=new Array(len+1).fill(false)
dp[0]=true
for(let i=1;i<=len;i++){
for(let j=i-1;j>=0;j--){
const suffix=s.slice(j,i)
if(wordSet.has(suffix)&&dp[j]){
dp[i]=true
break
}
}
}
return dp[len]
};
lc32最长有效括号
维护一个栈,遇到左括号入栈,遇到右括号出栈并用当前索引减栈顶元素计算当前有效括号长度,
第一次遇到栈中没有左括号时会弹出存入的-1,并压入当前右括号的索引表示有效括号字串的终结
/**
* @param {string} s
* @return {number}
*/
var longestValidParentheses = function(s) {
let stk=[]
stk.push(-1)
console.log(stk)
let res=0
for(let i=0;i<s.length;i++){
if(s[i]==='('){
stk.push(i)
}else{
stk.pop()
if(stk.length){
res=Math.max(res,i-stk[stk.length-1])
}else{
stk.push(i)
}
}
}
return res
};
双指针
1. 接雨水
var trap = function(height) {
let n=height.length
let lMax=new Array(n).fill(0)
let rMax=new Array(n).fill(0)
lMax[0]=height[0],rMax[n-1]=height[n-1]
let ans=0
for(let i=1;i<n;i++){
lMax[i]=Math.max(lMax[i-1],height[i])
}
for(let i=n-2;i>=0;i--){
rMax[i]=Math.max(rMax[i+1],height[i])
}
for(let i=0;i<n;i++){
ans+=Math.min(lMax[i],rMax[i])-height[i]
}
return ans
};
颜色分类
用p,q作为0,1,2三个区域的分界线,遇到0就和p+1位置交换,i++,遇到1直接略过,遇到2就和q-1位置交换,这时不能i++,还要对交换过来的值重新判断
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var sortColors = function(nums) {
let i=0,p=-1,q=nums.length
while(i<q){
if(nums[i]===0) {
swap(nums,i,p+1)
p++
i++
}else if(nums[i]===2){
swap(nums,i,q-1)
q--
}else if(nums[i]===1){
i++
}
}
function swap(arr,i,j){
let temp=arr[i]
arr[i]=arr[j]
arr[j]=temp
}
return nums
};
单调栈
求一个元素的左边或右边第一个比当前元素大或小的元素
要找比他大的栈内就要递减排序,如果栈顶比他大直接弹出,比他小就压栈
239滑动窗口最大值
https://leetcode.cn/problems/sliding-window-maximum/?envType=study-plan-v2&envId=top-100-liked
维护一个双端队列存储索引,队列头是当前窗口的最大值,时间复杂度O(n)
var maxSlidingWindow = function(nums, k) {
let ans=[]
let quene=[]
let n=nums.length
for(let i=0;i<n;i++){
if(quene.length&&quene[0]<i-k+1){
quene.shift()
}
while(quene.length>0&&nums[quene[quene.length-1]]<nums[i]){
quene.pop()
}
quene.push(i)
if(i>=k-1){
ans.push(nums[quene[0]])
}
}
return ans
};
lc739
维护一个单调栈,存放那些还没有找到比他大的值的元素下标,遍历数组,如果当前值比栈顶元素大就出栈,并存入结果数组,比他小就入栈,得到的比某个元素大的元素的下标总是在该元素的右边
/**
* @param {number[]} temperatures
* @return {number[]}
*/
var dailyTemperatures = function(temperatures) {
const n=temperatures.length
const ans= new Array(n).fill(0)
const stk=[]
for(let i=0;i<n;i++){
const tmp=temperatures[i]
while(stk.length&&tmp>temperatures[stk[stk.length-1]]){
const j=stk.pop()
ans[j]=i-j
}
stk.push(i)
}
return ans
};
图论
所有可达路径
const rl = require('readline').createInterface({ input: process.stdin })
let iter = rl[Symbol.asyncIterator]();
const readline = async () => (await iter.next()).value;
let n,m
let graph
let result = []
let path = []
async function initGraph() {
let line
line = await readline();
[n, m] = line.split(' ').map(Number)
graph = new Array(n + 1).fill(0).map(() => new Array(n + 1).fill(0) )
while (m--) {
line = await readline()
const setStr=line?line.split(' ').map(Number):undefined
setStr?graph[setStr[0]][setStr[1]]=1:null
}
}
//递归时,比起细节问题,整体逻辑更重要
function dfs(graph,x,n) {
if (x == n) {
result.push([...path])
return
}
for (let i = 1; i <= n; i++){
if (graph[x][i] == 1) {
path.push(i)
dfs(graph, i, n)
path.pop()
}
}
}
(async function() {
await initGraph()
path.push(1)
dfs(graph, 1, n)
if (result.length > 0) {
result.forEach(i => {
console.log(i.join(' '))
})
} else {
console.log(-1)
}
})()
课程表
入度表,邻接表,
如果某个课最后能被选,那他的入度总会减为0,总会被选
队列中存储入度为0的课
/**
* @param {number} numCourses
* @param {number[][]} prerequisites
* @return {boolean}
*/
var canFinish = function(numCourses, prerequisites) {
let inDegree=new Array(numCourses).fill(0)
let map={}
for(let i=0;i<prerequisites.length;i++){
inDegree[prerequisites[i][0]]++
if(map[prerequisites[i][1]]){
map[prerequisites[i][1]].push(prerequisites[i][0])
}else{
map[prerequisites[i][1]]=[prerequisites[i][0]]
}
}
const queue=[]
for(let i=0;i<numCourses;i++){
if(inDegree[i]==0){
queue.push(i)
}
}
let count=0
while(queue.length){
let selected = queue.shift()
count++
let toEnQuene=map[selected]
if(toEnQuene&&toEnQuene.length){
for(let i=0;i<toEnQuene.length;i++){
inDegree[toEnQuene[i]]--
if(inDegree[toEnQuene[i]]==0){
queue.push(toEnQuene[i])
}
}
}
}
return count==numCourses
};
腐烂的橘子
BFS,队列中存储腐烂的橘子
/**
* @param {number[][]} grid
* @return {number}
*/
var orangesRotting = function(grid) {
let n=grid.length
let m=grid[0].length
let quene=[]
let unrotten=0
for(let i=0;i<n;i++){
for(let j=0;j<m;j++){
if(grid[i][j]===2){
quene.push([i,j])
}else if(grid[i][j]===1){
unrotten++
}
}
}
if (unrotten == 0) return 0
let level=-1
const dx=[0,1,0,-1]
const dy=[1,0,-1,0]
while(quene.length){
const levelSize=quene.length
level++
console.log(quene)
for(let i=0;i<levelSize;i++){
let cur=quene.shift()
for(let j=0;j<4;j++){
let x=dx[j]+cur[0]
let y=dy[j]+cur[1]
if(x<0||y<0||x>=n||y>=m||grid[x][y]!==1) continue
grid[x][y]=2
quene.push([x,y])
unrotten--
}
}
}
return unrotten===0?level:-1
};
二分查找
lc74
直接看作一维数组
/**
* @param {number[][]} matrix
* @param {number} target
* @return {boolean}
*/
var searchMatrix = function(matrix, target) {
let n=matrix.length
let m=matrix[0].length
let left=0,right=n*m-1
while(left<=right){
let mid=Math.floor((left+right)/2)
let [i,j]=findIndex(mid,m)
if(matrix[i][j]>target){
right=mid-1
}else if(matrix[i][j]<target){
left=mid+1
}else return true
}
return false
};
const findIndex=(index,n)=>{
return [Math.floor(index/n),index%n]
}
leetcode153
https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array/?envType=study-plan-v2&envId=top-100-liked
中值大于右值,截取右半段;中值小于右值,截取左半段
/**
* @param {number[]} nums
* @return {number}
*/
var findMin = function(nums) {
let n=nums.length
let l=0,r=n-1
while(l<r){
let mid=(l+r)>>1
if(nums[mid]>nums[r]){
l=mid+1
}else{
r=mid
}
}
return nums[l]
};
贪心
lc55
从左到右遍历nums,同时维护最大可达下标mx
如果当前下标>mx,直接判定为false
否则mx为当前值和当前下标的可达值取最大
/**
* @param {number[]} nums
* @return {boolean}
*/
var canJump = function(nums) {
let n=nums.length
let mx=0
for(let i=0;i<n;i++){
if(i>mx){
return false
}
mx=Math.max(i+nums[i],mx)
}
return true
};
lc45
同样从左到右遍历,维护当前可达最大下标,如果和当前下标重合就ans++
/**
* @param {number[]} nums
* @return {number}
*/
var jump = function(nums) {
let ans=0
let curRight=0
let nextRight=0
let n=nums.length
for(let i=0;i<n-1;i++){
nextRight=Math.max(nextRight,nums[i]+i)
if(i===curRight){
curRight=nextRight
ans++
}
}
return ans
};
lc763
题目的意思就是分割字符串,字符互不包含
从左到右遍历,滑动窗口为切割的字符串
/**
* @param {string} s
* @return {number[]}
*/
var partitionLabels = function(s) {
let maxPos={}
for(let i=0;i<s.length;i++){
maxPos[s[i]]=i
}
let cur=0
let start=0
let ans=[]
let scannedMax=0
for(let i=0;i<s.length;i++){
scannedMax=Math.max(scannedMax,maxPos[s[i]])
if(i===scannedMax){
ans.push(i-start+1)
start=i+1
}
}
return ans
};
堆
- 必须是完全二叉树
大根堆
每个父节点都大于子节点
小根堆
每个父节点都小于子节点
根据层序遍历存储在数组中
若父节点下标为i,左子节点下标为2i+1
右子节点下标为2i+2
上浮和下沉
下沉:把根节点向下调整
上浮:把子节点向上调整
建堆的方法- 自顶向下建堆法
- 插入堆
- 上浮
- 自底向上建堆法
原来的乱序数组中,从倒数第二层开始下沉直至根节点完成下沉操作
堆中的第一个非叶子节点下标为n/2-1
lc215
/**
* @param {number[]} nums
* @param {number} k
* @return {number}
*/
var findKthLargest = function(nums, k) {
let heapSize=nums.length
buildMaxHeap(nums,heapSize)
for(let i=nums.length-1;i>=nums.length-k+1;i--){//循环k-1次,即将堆顶元素与末尾元素互换k-1次,每次交换后重新维护大根堆,k-1次之后堆顶元素就是第k大的元素
swap(nums,0,i)
heapSize--
maxHeapify(nums,0,heapSize)
}
return nums[0]
function buildMaxHeap(nums,heapSize){//构建大根堆,对每个非叶子节点重新维护大根堆
for(let i=Math.floor(nums.length/2)-1;i>=0;i--){
maxHeapify(nums,i,heapSize)
}
}
function maxHeapify(nums,i,heapSize){//完成对单个非叶子节点的维护
let l=i*2+1
let r=i*2+2
let largest=i
if(l<heapSize&&nums[l]>nums[largest]){
largest=l
}
if(r<heapSize&&nums[r]>nums[largest]){
largest=r
}
if(largest!==i){
swap(nums,i,largest)
maxHeapify(nums,largest,heapSize)//如果该节点与子节点发生了交换则要重新维护这个交换后的子节点
}
}
function swap(nums,i,j){
let temp=nums[i]
nums[i]=nums[j]
nums[j]=temp
}
};
lc347
/**
* @param {number[]} nums
* @param {number} k
* @return {number[]}
*/
var topKFrequent = function(nums, k) {
let map=new Map()
let res=[]
for(let i=0;i<nums.length;i++){//map存储元素和频次对应关系
map.set(nums[i],(map.get(nums[i])||0)+1)
}
let counts=[]//得到第k大频次
Object.keys(map).forEach(key=>{
counts.push(mapget(key))
})
for(let value of map.values()){
counts.push(value)
}
counts.sort((a,b)=>b-a)
let flag=counts.
for(let [key,value] of map){//得到频次大于等于counts[k-1]的key
if(value>=counts[k-1]){
res.push(parseInt(key))
}
}
return res
};
二叉树
最近公共祖先
递归法
注意每个节点值各不相同,即p,q唯一。如果当前节点为空或为p或为q,直接返回
如果都不是的话就递归左子树和右子树,如果左子树和右子树都有返回值就返回当前节点,如果有一个为空就返回不为空的那个
var lowestCommonAncestor = function(root, p, q) {
if(!root||root.val===p.val||root.val===q.val) return root
const left=lowestCommonAncestor(root.left,p,q)
const right=lowestCommonAncestor(root.right,p,q)
if(left&&right) return root
return left?left:right
};
转单链表
- 递归
后序遍历
用pre记录已经处理好的链表部分
var flatten = function(root) {
let pre=null
let help=(root)=>{
if(!root) return
help(root.right)
help(root.left)
root.right=pre
root.left=null
pre=root
}
help(root)
return root
};
括号生成
用递归法
var generateParenthesis = function (n) {
const res=[]
function dfs(lRemain,rRemain,str){
if(str.length===2*n){
res.push(str)
return
}
if(lRemain>0){//压入左括号
dfs(lRemain-1,rRemain,str+'(')
}
if(lRemain<rRemain){//只有当剩余左括号数量小于剩余右括号时才压入右括号
dfs(lRemain,rRemain-1,str+')')
}
}
dfs(n,n,'')
return res
};