🍪🧁🍧

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. 最长公共子序列

image

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
    上浮和下沉
    下沉:把根节点向下调整
    上浮:把子节点向上调整
    建堆的方法
  • 自顶向下建堆法
  1. 插入堆
  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
};

转单链表

  1. 递归
    后序遍历
    用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
};
posted @ 2025-04-15 23:25  不想吃fun  阅读(20)  评论(0)    收藏  举报