23届前端菜鸡选手算法刷题笔记

179 阅读9分钟

javascript算法小抄之笔记

BFS算法

leetcode 109 开密码锁 tips:可以使用双向BFS优化
let bfs(start,target){
    //用队列,并定义好visited,将第一个节点加入队列
    let queue=[]
    let visited=new Set()
    queue.push(start)
    visited.add(start)
    let count=0;
    //开始雷达式出队
    while(queue.length){
        size=queue.length
        //for循环按顺序
        for(let i=0;i<size;i++){
            temp=queue.shift()
            if(temp==target){
                return count;
            }
            //如果没有没访问过就进队并标记
            if(!visited.has(temp)){
                queue.push(temp)
                visited.add(temp)
            }
        } 
        count++;
    }
}

回溯法

leetcode 51 n皇后
let ans=[];
let visited=new Array(50).fill(0)
let diago=new Array(50).fill(0)
let anti_diago=new Array(50).fill(0)
let cur=1;
let memo=[]
var solveNQueens = function(n) {
    ans=[]
    // 记得是0开始
    bfs(0,n)
    return ans;
};

function bfs(cur,n){
    if(cur==n){
        //js中小坑 需要用JSON去完成深拷贝
        let copy =JSON.parse(JSON.stringify(memo))
        ans.push(copy)
        return ;
    }
    for(let i=0;i<n;i++){
        //副对角线
        let u=cur+i+n;
        //主对角线
        let v=cur-i+n;
        if(visited[i]==0&&diago[v]==0&&anti_diago[u]==0){
            visited[i]=diago[v]=anti_diago[u]=1;
            let tempString=''
            for(let k=0;k<n;k++){
                tempString=tempString+'.'
            }
            // js中小坑 字符串中更换需要用到string->Array->string 使用Array和join转换
            let temp=Array.from(tempString)
            temp[i]='Q'
            temp=temp.join('')
            memo.push(temp)
            bfs(cur+1,n)
            memo.pop()
            visited[i]=diago[v]=anti_diago[u]=0;
        }
    }
}

动态规划算法

leetcode 322 开密码锁
leetcode 322 零钱兑换
var coinChange = function(coins, amount) {
    dp=new Array(amount+1).fill(amount+1)
    //初始化!!! 很重要
    dp[0]=0;
    for(let i=1;i<=amount;i++){
        for(let j=0;j<coins.length;j++){
            //终止条件
            if(i<coins[j])continue
            // 转移方程!!
            dp[i]=Math.min(dp[i],dp[i-coins[j]])
        }
    }
   if(dp[amount]==amount+1){
        return -1
    }
    else{
        return dp[amount];
    }
}
leetcode 300 最长递增子序列
var lengthOfLIS = function(nums) {
    //初始化dp数组为1,因为至少为本身为递增子序列
    dp=new Array(nums.length).fill(1)
    for(let i=0;i<nums.length;i++){
        for(let j=0;j<i;j++){
            if(nums[j]<nums[i]){
                //dp数组是找到之前比nums[i]小的数
                dp[i]=Math.max(dp[i],dp[j]+1)
            }
        }
    }
    let ans=0
    //再找dp数组中最大的
    for(let i=0;i<nums.length;i++){
        ans=Math.max(ans,dp[i])
    }
    return ans
    
};
leetcode 354 俄罗斯套娃信封问题
var maxEnvelopes = function(envelopes) {
    let size=envelopes.length
    //先排序,先把一列按照递增排,如果相等则递减排
    envelopes.sort((e1,e2)=>{
        if(e1[0]!=e2[0]){
            return e1[0]-e2[0]
        }else{
            return e2[1]-e1[1]
        }
    })
    let ans=0
    let dp=new Array(size).fill(1)
    for(let i=0;i<size;i++){
        for(let j=0;j<i;j++){
            //和一维最长上升子序列相同
            if(envelopes[i][1]>envelopes[j][1]){
                dp[i]=Math.max(dp[i],dp[j]+1)
            }
        }
    }
    for(let i=0;i<size;i++){
        ans=Math.max(dp[i],ans)
    }
    return ans
};

双指针算法

leetcode 19 删除链表的倒数第 N 个结点
剑指 Offer II 006. 排序数组中两个数字之和
leetcode 142 环形链表
leetcode 344 反转字符串
var removeNthFromEnd = function(head, n) {
    let slow,fast
    slow=fast=head
    //创建个新的头,保证只有一个节点删除掉这种情况
    newHead=new ListNode(0,head)
    //保存下新的头
    pre=newHead
    for(let i=0;i<n;i++){
        //快指针先走n步
        fast=fast.next
    }
    while(fast!=null){
        //快慢指针和新头指针都往后走
        fast=fast.next
        slow=slow.next
        pre=pre.next
    }
    pre.next=slow.next
    return newHead.next
};
leetcode 160 相交链表
var getIntersectionNode = function(headA, headB) {
    if(headA==null||headB==null){
        return false
    }
    let left,right  
    left=headA
    right=headB
    while(left!=right){
        if(left==null){
            left=headB
        }else{
            left=left.next 
        }
        if(right==null){
            right=headA
        }else{
            right=right.next
        }
    }
    return left
};

二分查找算法

leetcode 704 二分查找
var search = function(nums, target) {
    let left=0
    //左闭右闭
    let right=nums.length-1
    //终止条件为left=right+1
    while(left<=right){
        //重点!! 因为js特性所以要用ParseInt去把小数变成整数,否则会进入死循环
        let mid = left+parseInt((right-left)/2);
        if(nums[mid]==target){
            return mid
        }else if(nums[mid]<target){
            left=mid+1
        }else if(nums[mid]>target){
            right=mid-1
        }
    }
    return -1
};
补充
左侧边界的二分查找
var left_search=function(nums, target){
    let left=0
    let right=nums.length-1
    while(left<=right){
        let mid =left+parseInt((right-left)/2)
        if(nums[mid]<target){
            left=mid+1
        }else if(nums[mid]>target){
            right=mid-1
        }else if(nums[mid]==target){
            right=mid-1
        }
    }
    if(left>=nums.length||nums[left]!=target){
        return -1
    }
    return left
}
右侧边界的二分查找
var right_search=function(nums,target){
    let left=0
    let right=nums.length-1
    while(left<=right){
        let mid =left+parInt((right-left)/2)
        if(nums[mid]<target){
            left=mid+1
        }else if(nums[mid]>target){
            right=mid-1
        }else if(nums[mid]==target){
            left=mid+1
        }
    }
    if(right<0||nums[right]!=target){
        return -1
    }
    return right
}

滑动窗口算法

leetcode 76 最小覆盖子串
leetcode 567 字符串的排列
leetcode 438 找到字符串中所有字母异位词
leetcode 3 无重复字符的最长子串
var minWindow = function(s, t) {
    //window为滑动窗口,needs为目标字符串
    let window={}
    let needs={}
    //js中小特性
    for(let i of t){
        needs[i]=(needs[i]||0)+1
    }
    let left=0
    let right=0
    //valid表示有几个满足needs的条件
    let valid=0
    let len=Infinity
    let start
    //移动右边界
    while(right<s.length){
        const c=s[right]
        right++
        //如果是目标字符串就把滑动窗口内加1
        if(needs[c]){
            window[c]=(window[c]||0)+1
            if(needs[c]==window[c]){
                valid++
            }
        }
        //移动左边界
        while(valid==Object.keys(needs).length){
            //判断是否更小要更新
            if(right-left<len){
                len=right-left
                start=left   
            }
            const d=s[left]
            left++
            //如果左边界缩小需要调整valid
            if(needs[d]){
                if(needs[d]==window[d]){
                    valid--
                }
                window[d]--
            }
        }
    }
    //返回答案
    return len==Infinity?"":s.substr(start,len)
};

动态规划----二维数组算法

总结步骤:

  • 想清楚dp代表什么!!!(要方便后面找传递函数)

  • 找传递函数!!(可以先从已知来开始找)

  • 填数组看动态规划的方向

leetcode 1143 最长公共子序列
dp[i][j]代表text1的0-i中和text2的0-j中最长公共子序列
var longestCommonSubsequence = function(text1, text2) {
    //二维数组定义小坑(1.map只能遍历有空间的数组 2.箭头函数中{}表示函数体,如果没有return 默认返回undefined)
    let dp=new Array(text1.length+1).fill(0).map(()=>new Array(text2.length+1).fill(0))
    //第一行和第一列设为0,表示空字符串
    for(let i=1;i<=text1.length;i++){
        for(let j=1;j<=text2.length;j++){
            //如果前一个字符是相同的话那么就+1
            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])
            }
        }
    }
    return dp[text1.length][text2.length]
};
leetcode 72 [编辑距离](leetcode-cn.com/problems/lo…)
dp[i][j]代表word1的0-i中和word2的0-j中编辑距离
var minDistance = function(word1, word2) {
    let dp=new Array(word1.length+1).fill(0).map(()=>new Array(word2.length+1).fill(0))
    //将空串的base case初始化
    for(let i=1;i<=word1.length;i++){
        dp[i][0]=i
    }
    for(let j=1;j<=word2.length;j++){
        dp[0][j]=j
    }
    for(let i=1;i<=word1.length;i++){
        for(let j=1;j<=word2.length;j++){
            if(word1[i-1]==word2[j-1]){
                dp[i][j]=dp[i-1][j-1]//无需操作
            }
            else{
                dp[i][j]=Math.min(
                    dp[i-1][j-1]+1,//替换
                    dp[i-1][j]+1,//删除
                    dp[i][j-1]+1//插入
                )
            }
        }
    }
    return dp[word1.length][word2.length]
};
leetcode 516 最长回文子序列
dp[i][j]代表i,j之间最长回文子序列
var longestPalindromeSubseq = function(s) {
    //所有j<i的地方是不符合我们定义的dp,所以初始化为0
    let dp=new Array(s.length).fill(0).map(()=>new Array(s.length).fill(0))
    for(let i=0;i<s.length;i++){
        for(let j=0;j<s.length;j++){
            //只有一个字符是回文
            if(i==j){
                dp[i][j]=1
            }
        }
    }
    //这里动态规划方向是重点!!!
    //是从右下角往上推
    for(let i=s.length-2;i>=0;i--){
        for(let j=i+1;j<s.length;j++){
            if(s[i]==s[j]){
                dp[i][j]=dp[i+1][j-1]+2
            }else{
                dp[i][j]=Math.max(dp[i+1][j],dp[i][j-1])
            }
        }
    }
    return dp[0][s.length-1]
};

这里可以用一维的数组来优化空间复杂度,优化为O(N),具体可见算法小抄P146

leetcode 5 最长回文子串
var longestPalindrome = function(s) {
    let n=s.length
    let dp=new Array(n).fill(false).map(()=>new Array(n).fill(false))
    let maxx=0;
    let ans=''
    for(let i=n-1;i>=0;i--){
        for(let j=i;j<n;j++){
            if(s[i]==s[j]){
                if(j-i<=1||dp[i+1][j-1]){
                    if(j-i+1>maxx){
                        ans=s.slice(i,j+1)
                        maxx=j-i+1
                    }
                    dp[i][j]=true
                }
            }          
        }
    }
    return ans
};

tips:

var test = 'hello world';

alert(test.slice(4,7)); //o w

alert(test.substring(4,7)); //o w

alert(test.substr(4,7)); //o world

leetcode 312 *Hard 戳气球
//dp数组表示(i,j)之间的气球能得分最大值
var maxCoins = function(nums) {
    //左右两边气球分值初始化为1
    let points=new Array(nums+2).fill(1)
    points[0]=points[nums.length+1]=1
    for(let i=1;i<=nums.length;i++){
        points[i]=nums[i-1]
    }
    let dp=new Array(nums.length+2).fill(0).map(()=>new Array(nums.length+2).fill(0))
    //这里相当于二维数组从右下角开始推到左上角
    for(let i=nums.length;i>=0;i--){
        for(let j=i+1;j<nums.length+2;j++){
            //假设k为最后一个戳破的气球
            for(let k=i+1;k<j;k++){
                dp[i][j]=Math.max(dp[i][k]+dp[k][j]+points[i]*points[k]*points[j],dp[i][j])
            }
        }
    }
    return dp[0][nums.length+1]
};

动态规划---01背包算法

典中典 01背包
var knapsack(W,N,wt,val){
    let dp= new Array(N+1).fill(0).map(()=>new Array(W+1).fill(0))
    for(let i=1;i<=N;i++){
        for(let j=1;j<=W;j++){
            //可以放入的情况下
            if(j>=wt[i-1]){
                //放或者不放取max
                dp[i][j]=Math.max(dp[i-1][j-wt[i-1]]+val[i-1],dp[i-1][j])
            }else{
                //放不进去
                dp[i][j]=dp[i-1][j]
            }
        }
    }
}
leetcode 416 分割等和子集
var canPartition = function(nums) {
    let sum=0
    nums.forEach((x)=>{
        sum+=x,
    })
    //如果sum为奇数,则不可能分成两堆
    if(sum&1) return false
    let dp =new Array(nums.length+1).fill(false).map(()=>new Array(parseInt(sum/2)+1).fill(false))
    //如果每堆要求0则直接满足可以凑满,设为true
    for(let i=0;i<=nums.length;i++){
        dp[i][0]=true
    }
    for(let i=1;i<=nums.length;i++){
        for(let j=1;j<=parseInt(sum/2);j++){
            //如果可以加上
            if(nums[i-1]<=j){
                dp[i][j]=dp[i-1][j-nums[i-1]] || dp[i-1][j]
            }else{
                dp[i][j]=dp[i-1][j]
            }
        }
    }
    return dp[nums.length][parseInt(sum/2)]
};
leetcode 494 目标和
//本题可以直接用dfs回溯来爆破或者用以下转换成背包问题
var findTargetSumWays = function(nums, target) {
    function bag(nums,sum){
        let dp=new Array(nums.length+1).fill(0).map(()=>new Array(sum+1).fill(0))
        for(let i=0;i<=nums.length;i++){
            dp[i][0]=1
        }
        for(let i=1;i<nums.length+1;i++){
            for(let j=0;j<=sum;j++){
                if(j>=nums[i-1]){
                    dp[i][j]=dp[i-1][j-nums[i-1]]+dp[i-1][j]
                }else{
                    dp[i][j]=dp[i-1][j]
                }
            }
        }
        return dp[nums.length][sum]
    }
    let sum=0
    nums.forEach((x)=>{
        sum+=x
    })
    //下面两种情况都无法分成两个A集合满足元素和=(target+sum)
    if((sum+target)%2==1||(target+sum)<0) return 0
    //开始背包算法
    return bag(nums,parseInt((target+sum)/2))
};
leetcode 518 零钱兑换 II
//实质同上的背包算法相同 可以看上面的题目
var change = function(amount, coins) {
    let dp=new Array(coins.length+1).fill(0).map(()=>new Array(amount+1).fill(0))
    for(let i=0;i<coins.length+1;i++){
        dp[i][0]=1
    }
    for(let i=1;i<=coins.length;i++){
        for(let j=0;j<=amount;j++){
            if(coins[i-1]<=j){
                dp[i][j]=dp[i][j-coins[i-1]]+dp[i-1][j]
            }else{
                dp[i][j]=dp[i-1][j]
            }
        }
    }
    return dp[coins.length][amount]
};

动态规划---打家劫舍算法

leetcode 198 打家劫舍
var rob = function(nums) {
    let n=nums.length
    let dp_i
    let dp_i1=0
    let dp_i2=0
    //因为动态规划依赖于dp[i+1],dp[i+2]的情况,所以可以把空间复杂度优化成O(N)
    for(let i=n-1;i>=0;i--){
        //dp[i]=Math.max(dp[i+1],dp[i+2]+nums[i])
        dp_i=Math.max(dp_i1,dp_i2+nums[i])
        dp_i2=dp_i1
        dp_i1=dp_i
    }
    return dp_i
};
leetcode 213 打家劫舍 II
var rob = function(nums) {
    function robRes(nums,start,end){
        let n=nums.length
        let dp=new Array(n+2).fill(0)
        for(let i=end;i>=start;i--){
            dp[i]=Math.max(dp[i+1],dp[i+2]+nums[i])
        }
        return dp[start]
    }
    if(nums.length==1){
        return nums[0]
    }
    //1.打劫第一家放弃最后一家 或者 打劫最后一家放弃第一家  tips:因为两家都不打劫肯定被包括在情况1和2中了所以不考虑
    return Math.max(robRes(nums,0,nums.length-2),robRes(nums,1,nums.length-1))
};
leetcode 337 打家劫舍 III
var rob = function(root) {
    const memo=new Map()
    function robTree(node){
        if(node==null) return 0
        //查看备忘录是否存储过
        if(memo.get(node)){
            return memo.get(node)
        }
        //打劫这一层
        let ans1=node.val
        +(node.left==null?0:robTree(node.left.left)+robTree(node.left.right))
        +(node.right==null?0:robTree(node.right.left)+robTree(node.right.right))
        //不打劫这一层
        let ans2=(robTree(node.left)+robTree(node.right))
        let ans=Math.max(ans1,ans2)
        //加入备忘录
        memo.set(node,ans)
        return ans
    }
    return robTree(root)
};

LRU算法

leetcode 146 LRU 缓存
//vue中keep alive 使用这个缓存淘汰策略 实质是哈希链表 哈希表+双向链表实现
var LRUCache = function(capacity) {
    this.capacity=capacity
    //用map来实现
    this.map=new Map()
};

LRUCache.prototype.get = function(key) {
    //如果有的话 就先删除再重新加入
    if(this.map.has(key)){
        let temp=this.map.get(key)
        this.map.delete(key)
        this.map.set(key,temp)
        return temp
    }
    return -1
};

LRUCache.prototype.put = function(key, value) {
    //有的话先删除
    if(this.map.has(key)){
        this.map.delete(key)
    }
    this.map.set(key,value)
    //大于了最大缓存量,删除头
    if(this.map.size>this.capacity){
        //点睛之笔通过this.map.keys().next().value来获得多余的节点
        this.map.delete(this.map.keys().next().value)
    }
};

二叉树算法

典型题目:
leetcode 144 二叉树的前序遍历
leetcode 94 二叉树的中序遍历
leetcode 145 二叉树的后序遍历

前三种过于简单,这里就不贴代码了

leetcode 102 二叉树的层序遍历
var levelOrder = function(root) {
    let ans=[]
    //层次遍历通过队列先进先出特性
    let queue=[]
    queue.push(root)
    //存储每一层的节点
    let cur=[]
    //在队列不为空的时候
    while(queue.length){
        let len=queue.length
        //将这一层的所有节点取出
        while(len--){
            let temp=queue.shift()      
            if(temp){
                cur.push(temp.val)
                queue.push(temp.left)
                queue.push(temp.right)          
            }
        }
        //将该层节点推入ans中
        if(cur.length){
            ans.push(cur)
        }
        cur=[]
    }
    return ans 
};
leetcode 236 二叉树的最近公共祖先
var lowestCommonAncestor = function(root, p, q) {
    //递归终点 1.如果root为null  2.如果root为p、q其中一个则返回root
    if(root==null) return null
    if(p==root||q==root) return root
    //后序遍历递归
    let left=lowestCommonAncestor(root.left,p,q)
    let right=lowestCommonAncestor(root.right,p,q)
    //如果左右都为null 则表示不在当前root节点下
    if(left==null&&right==null){
        return null
    }
    //如果左右都存在,则表示当前的root为最近公共父亲
    if(left!=null&&right!=null){
        return root
    }
    //如果一方为null,则返回另一方
    return left==null?right:left
};

完全二叉树算法

leetcode 100 相同的树
var isSameTree = function(p, q) {
    function isSame(p,q){
        //都为空返回真
        if(p==null&&q==null) return true
        //一个空则为假
        if(p==null||q==null) return false
        if(p.val==q.val){
            return isSame(p.left,q.left)&&isSame(p.right,q.right)
        }
        //两者值不相等就返回假
        return false
    }
    return isSame(p,q)
};
leetcode 98 验证二叉搜索树
var isValidBST = function(root) {
    //增加了两个参数min,max为了保证左孩子一定小于父节点
    function isValid(node,min,max){
        if(node==null) return true
        //如果min存在并且不符合左孩子小于父亲 则返回假
        if(min!=null && node.val<=min.val) return false
        //同上
        if(max!=null && node.val>=max.val) return false
        //返回两者递归都为真的情况
        return isValid(node.left,min,node)&&isValid(node.right,node,max)
    }
    return isValid(root,null,null)
};
leetcode 701 二叉搜索树中的插入操作
//整体思路先找到对应的节点后才插入
var insertIntoBST = function(root, val) {
    function myInsert(node,val){
        if(node==null){
            //找到了插入点则返回new的新节点
            return new TreeNode(val,null,null)
        }
        if(node.val<val){
            //更新
            node.right=myInsert(node.right,val)
        }
        if(node.val>val){
            //更新
            node.left=myInsert(node.left,val)
        }
        //返回更新后的新节点
        return node 
    }
    root=myInsert(root,val)
    return root
};
leetcode 450 删除二叉搜索树中的节点
var deleteNode = function(root, key) {
    function delete_node(node,key){
        if(node==null) return null
        //找到对应要删除的节点
        if(node.val==key){
            //情况1 左孩子为空 将右孩子接上
            if(node.left==null&&node.right){
                return node.right
            }
             //情况2 右孩子为空 将左孩子接上
            if(node.right==null&&node.left){
                return node.left
            }
            //情况3 左右孩子都为空 直接删除
            if(node.left==null&&node.right==null){
                return null
            }
            //情况4 找右孩子中最小的节点然后递归继续删除
            minNode=getMinNode(node.right)
            node.val=minNode.val
            node.right=delete_node(node.right,node.val)
        }
        if(node.val<key){
            node.right=delete_node(node.right,key)
        }
        if(node.val>key){
            node.left=delete_node(node.left,key)
        }
        return node 
    }
    function getMinNode(node){
        while(node.left){
            node=node.left
        }
        return node
    }
    root=delete_node(root,key)
    return root
};

单调栈和单调队列算法

单调栈典型题目:
算法小抄P267 下一个更大元素1
var nextGeaterElement= function(nums){
    let stack=[]
    let ans=new Array(nums.length)
    for(let i=nums.length-1;i>=0;i--){
        while(stack.length>0&&stack[stack.length-1]<=nums[i]){
            stack.pop()
        }
        ans[i]=(stack.length==0)?-1:stack[stack.length-1]
        stack.push(nums[i])
    }
    return ans 
}
算法小抄P270 下一个更大元素2
var nextGeaterElement= function(nums){
    let stack=[]
    let n=nums.length
    let ans=new Array(n)
    //逻辑上将原来的数组复制成了双倍大小
    for(let i=2*n-1;i>=0;i--){
        //实际上通过i%n来解决循环数组的问题
        while(stack.length>0&&stack[stack.length-1]<=nums[i%n]){
            stack.pop()
        }
        ans[i%n]=(stack.length==0)?-1:stack[stack.length-1]
        stack.push(nums[i%n])
    }
    return ans 
}
leetcode 739 每日温度
var dailyTemperatures = function(temperatures) {
    let ans=new Array(temperatures.length).fill(0)
    //用数组代替栈
    let stack=[]
    //从右往左遍历
    for(let i=temperatures.length-1;i>=0;i--){
        //如果栈不为空并且栈顶的气温小于等于当前的气温
        while(stack.length>0&&temperatures[stack[stack.length-1]]<=temperatures[i]){
            //出队
            stack.pop()
        }
        //当前的ans[i]为栈顶元素也就是最高的元素索引
        ans[i]=(stack.length==0)?0:(stack[stack.length-1]-i)
        stack.push(i)
    }
    return ans
};
单调队列典型题目:
leetcode 239 滑动窗口最大值
var maxSlidingWindow = function(nums, k) {
    let ans=[]
    let deque=[]
    for(let i=0;i<nums.length;i++){
        //判断当前元素和单调队列第一个元素是否还是k之内
        if(i-deque[0]>k-1){
            deque.shift()
        }
        //判断是否新进的元素后是否能保持单调
        if(deque.length){
            while(nums[i]>nums[deque[deque.length-1]]){
                deque.pop()
            }
        }
        deque.push(i)
        //如果大于k-1说明要开始找最大值了,而最大值就是队首
        if(i>=k-1){
            ans.push(nums[deque[0]])
        }
    }
    return ans
};

链表算法

leetcode 206 反转链表
//解法1 正向反转
var reverseList = function(head) {
    let pre=null
    let cur=head
    while(cur!=null){
        let next=cur.next
        cur.next=pre
        pre=cur
        cur=next
    }
    return pre
};
//解法2 递归反转
var reverseList = function(head) {
    if(head==null || head.next==null){
        return head
    }
    const newHead=reverseList(head.next)
    head.next.next=head
    head.next=null
    return newHead
};
leetcode 234 回文链表
//方法1 通过递归栈来比较
var isPalindrome = function(head) {
    let left=head
    function reverseList(right){
        if(right==null) return true
        let res=reverseList(right.next)
        res=res&&(right.val==left.val)
        left=left.next
        return res
    }
    return reverseList(head)
};
//方法2 通过反转一半链表
var isPalindrome = function(head) {
    let slow,fast
    slow=fast=head
    //找到链表中点
    while(fast!=null&&fast.next!=null){
        slow=slow.next
        fast=fast.next.next
    }
    //如果fast指针没有指向null,则说明链表长度为奇数,将slow往前走一步
    if(fast!=null){
        slow=slow.next
    }
    //反转链表
    function reverseList(head){
        if(head==null||head.next==null) return head
        let newHead=reverseList(head.next)
        head.next.next=head
        head.next=null
        return newHead
    }
    let newHead=reverseList(slow)
    //通过双指针比较反转后链表是否和
    while(newHead!=null){
        if(newHead.val!=head.val) return false
        newHead=newHead.next
        head=head.next
    }
    return true
};
leetcode 92 反转链表 II
//主要思路:找到要反转的链表前后然后切断 接着反转 再连接
var reverseBetween = function(head, left, right) {
    //虚头节点
    let dummyNode=new ListNode(-1,head)
    let preNode=dummyNode
    for(let i=0;i<left-1;i++){
        preNode=preNode.next
    }
    let rightNode=preNode
    for(let i=0;i<right-left+1;i++){
        rightNode=rightNode.next
    }
    let leftNode=preNode.next
    let nextNode=rightNode.next
    //切断
    preNode.next=null
    rightNode.next=null
    //反转
    let newNode =reverseList(leftNode)
    //连接
    preNode.next=newNode
    leftNode.next=nextNode
    //切记返回虚节点的下一个节点
    return dummyNode.next
};
leetcode 25 K 个一组翻转链表
var reverseKGroup = function(head, k) {
    //如果开头为null 则返回
    if(head==null) return null
    let a,b
    //[a,b)左闭右开区间内反转
    a=b=head
    //b走k步 如果遇到b为null则返回
    for(let i=0;i<k;i++){
        if(b==null) return head
        b=b.next
    }
    //链表反转后记录新节点
    let newNode=reverseList(a,b)
    //尾节点接递归
    a.next=reverseKGroup(b,k)
    return newNode
};
//正常反转链表 但是链表终点变成了targetNode
function reverseList(node,targetNode){
    let pre=null
    let cur=node
    while(cur!=targetNode){
        let next=cur.next
        cur.next=pre
        pre=cur
        cur=next
    }
    return pre
}

回溯算法之子集问题:

leecode 78 子集
let ans=[]
var subsets = function(nums) {
    ans=[]
    let track=[]
    backTrack(nums,0,track)
    return ans
};
function backTrack(nums,start,track){
    //小坑 需要用解构赋值,否则因为浅拷贝会遇到问题
    ans.push([...track])
    //通过start来控制不要重复
    for(let i=start;i<nums.length;i++){
        track.push(nums[i])
        backTrack(nums,i+1,track)
        track.pop()
    }
}
leetcode 46 全排列
let ans=[]
var permute = function(nums) {
    ans=[]
    let track=[]
    //开始回溯算法
    backTrack(nums,track)
    return ans 
};
function backTrack(nums,track){
    //终止条件为满足够长度
    if(track.length==nums.length){
        ans.push([...track])
        return
    }
    //递归
    for(let i=0;i<nums.length;i++){
        if(!track.includes(nums[i])){
            track.push(nums[i])
            backTrack(nums,track)
            track.pop()
        }
    }
}
leetcode 77 组合
let ans=[]
var combine = function(n, k) {
    ans=[]
    let track=[]
    backTrack(n,k,1,track)
    return ans
};
function backTrack(n,k,start,track){
    //满足条件,结束递归
    if(track.length==k){
        ans.push([...track])
        return 
    }
    //从start开始递归,为了保证不重复
    for(let i=start;i<=n;i++){
        track.push(i)
        backTrack(n,k,i+1,track)
        track.pop()
    }
}

回溯算法之常见题目

leetcode 22 括号生成
let ans=[]
var generateParenthesis = function(n) {
    //初始化!!!
    let track=[]
    ans=[]
    backTrack(track,n,n)
    return ans
};

function backTrack(track,left,right){
    //如果多用了括号返回
    if(left<0||right<0) return
    //如果先用右括号多于左括号 返回
    if(right<left) return
    //刚刚好则加入答案
    if(left==0&&right==0){
        ans.push(track.join(''))
        return
    }
    //加左括号,回溯 加右括号,回溯
    track.push('(')
    backTrack(track,left-1,right)
    track.pop()
    track.push(')')
    backTrack(track,left,right-1)
    track.pop()
}
leetcode 37 解答数独
var solveSudoku = function(board) {
    backTrack(board,0,0)
    return board
};
//因为只有一个解,所以return true来打断递归
function backTrack(board,row,col){
    //找到了
    if(row==9&&col==8) return true
    //需要去下一列填数字
    if(row>8) return backTrack(board,0,col+1)
    //如果已有数字则去填下一行
    if(board[row][col]!='.') return backTrack(board,row+1,col)
    for(let i=1;i<=9;i++){
        //可以填这个数字的话
        if(isValid(board,row,col,i)){
            board[row][col]=(i+'')
            //找到了就返回true 停止递归
            if(backTrack(board,row+1,col)){
                return true
            }
            board[row][col]='.'
        }
    }
    return false
}
//判断是否行、列、3x3内有填写过
function isValid(board,r,c,n){
    let rowStart=Math.floor(r/3)*3
    let colStart=Math.floor(c/3)*3
    for(let i=0;i<9;i++){
        if(board[r][i]==n) return false
        if(board[i][c]==n) return false
    }
    for(let i=rowStart;i<rowStart+3;i++){
        for(let j=colStart;j<colStart+3;j++){
            if(board[i][j]==n){
                return false
            }
        }
    }
    return true
}
leetcode 773 滑动谜题
//手动存下每个位置0可以移动的坐标
let neighbor=[[1,3],[0,4,2],[1,5],[0,4],[3,1,5],[2,4]]
var slidingPuzzle = function(board) {
    let queue=[]
    //用字符串来比较
    let target='123450'
    let init=[]
    let ans=0
    let visited=new Set()
    for(let i=0;i<2;i++){
        for(let j=0;j<3;j++){
            init.push(board[i][j])
        }
    }
    queue.push(init.join(''))
    while(queue.length!=0){
        ans++
        let size=queue.length
        for(let i=0;i<size;i++){
            //取出每个状态
            let status=queue.shift()
            if(status==target) return ans-1
            for(const nextStatus of getNext(status)){
                //如果没有访问过则标记然后加入队列
                if(!visited.has(nextStatus)){
                    visited.add(nextStatus)
                    queue.push(nextStatus)
                }
            }
        }
    }
    return -1
    
};
function getNext(status){
    let ret=[]
    let array=Array.from(status)
    const pos=status.indexOf('0')
    for(let i=0;i<neighbor[pos].length;i++){
        y=neighbor[pos][i]
        let temp=array[y]
        array[y]=array[pos]
        array[pos]=temp
        //交换位置然后加入数组,表示可以去的下一步状态
        ret.push(array.join(''))
        temp=array[y]
        array[y]=array[pos]
        array[pos]=temp
    }
    return ret
}

典题题目两数之和

leetcode 15 [三数之和](leetcode-cn.com/problems/tw…)
var threeSum = function(nums) {
    let ans=[]
    //小坑 需要自定义判断大小的函数,否则负数会不是从小到大
    nums.sort((a,b)=>{
        return a-b
    })
    for(let i=0;i<nums.length;i++){
        let rest=(0-nums[i])
        let ret=twoSum(nums,i+1,nums.length-1,rest)
        //如果有两数之和就加入答案
        if(ret.length!=0){
            for(let item of ret){
                ans.push([nums[i]].concat(item))
            }
        }
        //防止第一个数重复
        while(i<nums.length-1&&nums[i]==nums[i+1]) i++
    }
    return ans 
};
function twoSum(nums,left,right,target){
    let ret=[]
    while(left<right){
        let leftNum=nums[left]
        let rightNum=nums[right]
        let sum=leftNum+rightNum
        if(sum<target){
            left++
        }else if(sum>target){
            right--
        }
        else{
            ret.push([nums[left],nums[right]])
            //防止第二第三个数重复
            while(left<right&&leftNum==nums[left]) left++
            while(left<right&&rightNum==nums[right]) right--
        }
    }
    return ret 
}

高频面试题系列

高效寻找素数 算法小抄P351
function countPrime(n){
    //将数组置为都是素数
    let arrPrime= new Array(n).fill(true)
    //只需要找到sqrt(n)
    for(let i=2;i*i<n;i++){
        //如果i是素数 那么i*i之后的每个+i数都是合数 置位false
        if(Prime(i)){
            for(let j=i*i;j<n;j+=i){
                arrPrime[j]=false
            }
        }
    }
    //统计有多少个素数
    let ans=0
    for(let i=2;i<n;i++){
        if(arrPrime[i]){
            ans++
        }
    }
    return ans
}
function Prime(n){
    //只需要到sqrt(n)
    for(let i=2;i*i<=n;i++){
        if(n%i==0) return false
    }
    return true
}
leetcode 244 基本计算器
var calculate = function(s) {
    //操作符
    let ops=[1]
    //符号
    let sign=1
    //指针
    let i=0
    let n=s.length
    let ans=0
    while(i<n){
        if(s[i]==' '){
            i++
        }else if(s[i]=='+'){
            sign=ops[ops.length-1]
            i++
        }else if(s[i]=='-'){
            sign=-ops[ops.length-1]
            i++
        }else if(s[i]=='('){ //如果是(则将前面的sign推入操作符
            ops.push(sign)
            i++
        }else if(s[i]==')'){ //如果是)则将ops中操作符弹出一个
            ops.pop()
            i++
        }else{
            let num=0
            //如果是数字,循环取完然后统计
            while(i<n&& !(isNaN(Number(s[i]))) && s[i] !== ' '){
                num=10*num+Number(s[i])
                i++
            }
            ans+=sign*num
        }  
    }
    return ans
};
leetcode 372 超级次方
let base=1337
var superPow = function(a, b) {
    if(b.length==0) return 1
    let last=b.pop()
    //先取最后一个数来求a的last次方
    let part1=myPow(a,last)
    //再递归求剩下的
    let part2=myPow(superPow(a,b),10)
    //每次计算%base
    return (part1*part2)%base
};
function myPow(a,b){
    a%=base
    let ans=1
    for(let i=0;i<b;i++){
        ans*=a
        ans%=base
    }
    return ans 
}