力扣算法从小白开始

本文详细介绍了从基础算法如二分查找、快速排序,到复杂问题的解决策略如动态规划。讲解了如何实现链表、最小栈以及优化时间和空间的动态规划算法。此外,还探讨了利用位运算解决奇数次数元素的问题,并介绍了经典算法如三数之和的解法。

目前更新了:二分法,快排, js创建链表, 动态规划, 异或运算,三数之和 ,翻转链表(全部,) 局部,两个栈模拟队列, 经典递归 等

  1. FindIndex 找到数组中的第一个目标值并且返回目标值的下标 , 没有找到就返回-1
    判断后返回值,不用手动return
const index = nums.findIndex(item=>item===target)
  1. find 找到数组中第一个符合条件的 , 没有找到就返回 undefined find对空数组不起作用
  2. sort 会改变原数组
nums.sort((a,b)=>a-b) //升序
nums.sort((a,b)=>b-a) //降序

二分查找 二分详解

二分查找 : 适用于升序数组 , 判断条件是left < right ,不符合条件就返回-1

  1. 我解题的时候纠结数组分别是奇数和偶数的时候中间值mid怎么取 , 但是不必纠结这个问题 , 因为除以二的时候它们都是有可能出现的 ,
  2. 重点是判断边界问题 , 下面采用了左闭右闭的解法 , 所以判断循环的时候条件是 left <= right , 因为[left , right] 是有意义的
  3. 当我计算mid的时候使用了mid = ( left + right ) / 2 , 出来的结果错误的(可能是因为left+right超过了定义变量时的大小),然后我又改变了一种写法:mid = (right-left)/2+left ,计算出来的index居然有小数!!?? 尝试了一下,在js里面5/2=2.5....
  4. 正确的写法:const mid = Math.floor((right-left)/2)+left // 防止计算时溢出
let nums = [1,5,6,2,8,89,7,21,4,6,63,10]
const target = 8


//使用二分查找来找到 target 在 nums 中对应值的下标
//查找的条件是查找范围不为空
nums.sort((a,b)=>a-b)
console.log(nums)


var search = function(nums,target){
    let left = 0
    let right = nums.length-1
    while(left <= right){
        //每次判断之前都重置 mid
        // const mid = (left+right)/2
        const mid = Math.floor((right-left)/2)+left
        const num = nums[mid]
        if(num === target){
            return mid
        }else if(target > num){
            left = mid+1
        }else{
            right = mid-1
        }
    }
    return -1
}

const result = search(nums,target)
console.log(result);

这道异曲同工之妙,就是缩小空间

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。
如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。

var searchInsert = function(nums, target) {
    //法一
    /* let index = nums.findIndex(item=>item==target)
    if(index != -1){
        return index
    }else{
        let index2 = nums.findIndex(item=>item>target)
        if(index2==-1)return nums.length
        else return index2
    } */

    //二分法
    let left = 0
    let right = nums.length-1
    let flag = 0

    while(left <= right){
        let mid = Math.floor((right-left)/2)+left
        let num = nums[mid]
        if(target == num){
            return mid
        }else if(target > num){
            left = mid + 1 
           / /区间缩小到 left 和 right 相等的时候,从这里就出去了,
           / /此时left 在 原来 left = right 那个值的左边一位 , 
           / /刚好就是找不到值要插入的index
        }
        else
        {
            right = mid - 1
        }
    }
    return left
};

快排(双边循环 , 递归)

每次都将最左边的值作为基准值 , 然后从最左边的left 和 最右边的 right 开始查找 , 从right开始 , right找的是比基准值小的 , 找到之后left 就开始找 , 找的是比基准值大的 , 找完一轮后交换left 和right 的值, 最后left 和right 重合的时候 , 交换基准值和left 的值 ,然后使用递归 , 分别排序基准值左边的 和 右边的


let num = [5,8,6,3,9,2,1,7]

function myquickSort(num,left,right){
    let flag = left
    if(left>=right)return;
    while(left<right){
        //找到符合的right
        while(right>left&&num[right]>=num[flag])
        right--
        //找到符合的left
        while(right>left&&num[left]<=num[flag])
        left++
        //交换两者的值
        if(left<right)
        [num[right],num[left]] = [num[left],num[right]] 
    }
    //交换基准值和rightleft重合的值
    [num[left],num[flag]] = [num[flag],num[left]] 
    return left;
}
 
function jisuan (num , left , right){
    if(left>=right)return;
    let flag = myquickSort(num,left,right)
    jisuan(num,left,flag-1)
    jisuan(num,flag+1,right)

}
jisuan(num,0,num.length-1)
console.log(num);

快排(单边循环 递归)

let num = [4,7,3,5,6,2,8,1]

//单边循环
let quickSort = (num, left, right) => {
    let flag = left
    let mark = left
    while(left<=right){
        if(num[left]<num[flag])
        {
            //这个时候mark就要往右移动一位 , 因为找到了一个小于flag 的数
            mark++
            [num[left],num[mark]] = [num[mark],num[left]]
        }
        left++
    }
    //交换基准数 和 mark 的值
    [num[flag],num[mark]] = [num[mark],num[flag]]
    //最后要返回基准数
    return mark
}


let jisuan = (num, left, right) => {
    //既然是递归 , 就要有结束条件
    if(left>=right)return;
    let flag = quickSort(num, left, right)
    jisuan(num, left, flag - 1)
    jisuan(num, flag + 1, right)
}

jisuan(num, 0, num.length - 1)
console.log(num);

快排 (非递归)


//当数据量很大的时候 , 递归快排会造成栈溢出 , 为了解决这个问题 , 我们使用js数组 来模拟栈 , 
//将待排序的[left,right]保存到数组中 , 循环取出进行快排 
// let num = [4, 7, 3, 5, 6, 2, 8, 1]
let num = [1,3,2,5,9,6,8,7]
//非递归实现快排
const quickSort = (num, left, right) => {
    let flag = left
    let mark = left
    while (left <= right) {
        if (num[left] < num[flag]) {
            //这个时候mark就要往右移动一位 , 因为找到了一个小于flag 的数
            mark++
            [num[left], num[mark]] = [num[mark], num[left]]
        }
        left++
    }
    //交换基准数 和 mark 的值
    [num[flag], num[mark]] = [num[mark], num[flag]]
    //最后要返回基准数
    return mark
}

//使用非递归的方式进行快排
const jisuan = (num,left,right)=>{
    let list = [[left,right]]
    while(list.length!=0){
        let now = list.pop()
        if(now[0]>=now[1])
         continue;
        let flag = quickSort(num,now[0],now[1])
        //flag-1 和 flag+1 避免了类似[1,3,2,5,9,6,8,7]这样flag一直卡在0的情况
        list.push([now[0],flag-1])
        list.push([flag+1,now[1]])
    }
}
jisuan(num,0,num.length-1)
console.log(num);

js 实现链表 , 并且判断链表是否循环


//用js来实现一个链表

//构造节点
class Node {
    constructor(data) {
        this.data = data
        this.next = null
    }
}

//创建链表
class LinkedPropotype {
    constructor() {
        this.head = null
    }
    //构造节点
    create(data) {
        let node = new Node(data)
        if (this.head == null) {
            this.head = node
        } else {
            let current = this.head
            while (current.next) {
                current = current.next
            }
            current.next = node
        }
    }

    //删除节点
    deletenode(index) {
        let p = this.head
        let k = p
        let len = 0
        while (p) {
            k = p
            p = p.next
            len++
        }
        if (index < 0 || index > len) {
            console.log('删除超出范围');
            return;
        }

        let q = this.head
        let l = q
        //删除头节点
        if (index == 0) {
            this.head = this.head.next
            return q.data
        }
        //删除中间节点
        else if (index < len) {
            while (index) {
                l = q
                q = q.next
                index--
            }
            let deletenum = q.data
            l.next = q.next
            return deletenum
        }
        //删除尾节点
        else {
            k.next = null
            return p
        }
    }

    //查找节点
    searchnode(index){
        let p = this.head
        let k = p
        let len = 0
        while (p) {
            k = p
            p = p.next
            len++
        }
        if (index < 0 || index > len) {
            console.log('查找超出范围');
            return;
        }
        p = this.head
        while(index){
            index--
            p = p.next
        }
        return p
    }

    //修改节点
    editnode(index,data){
        let p = this.head
        let k = p
        let len = 0
        while (p) {
            k = p
            p = p.next
            len++
        }
        if (index < 0 || index > len) {
            console.log('修改超出范围');
            return;
        }
        p = this.head
        while(index){
            index--
            p = p.next
        }
        p.data = data
    }

}

let linknode = new LinkedPropotype()
linknode.create(1)
linknode.create(2)
linknode.create(3)
linknode.create(4)
linknode.create(5)
linknode.create(6)
linknode.create(7)


//下面三部将链表改造为循环链表
var C = linknode.searchnode(2)
var G = linknode.searchnode(6)
G.next = C

//使用快慢指针来判断有没有环
let isCircle = (link)=>{
    let p = link.head
    let q = link.head

    //快指针走完就没有必要再进行判断了
    while(q&&q.next){
        p = p.next
        q = q.next.next
        if(p === q){
            console.log('此链表循环');
            return;
        }
    }
    console.log('此链表不循环');
}

isCircle(linknode)

最小栈的实现

设计一个栈 A, 让出栈 , 入栈 , 输出栈内的最小值的时间复杂度都是O(1)

思路: 再设计一个栈 B 存入A第一个元素 , 每次A入栈的时候 , 跟B栈顶元素作比较 , 如果入栈元素小于B栈顶元素 , 则B也入栈此元素 , A出栈的时候 , 跟B栈顶元素做比较 , 如果相等 , B出栈,这样 ,B栈顶永远都是A中的最小元素
//设计一个栈 , 让出栈 , 入栈 , 输出栈内的最小值的时间复杂度都是O(1)
let arr = [4,7,3,8,2,5]

let len = arr.length 
let len1 = arr.length 
var min
let A = []
let B = []
//让元素都入栈 , 栈A和栈B都造好了

while(len){
    if(len == len1)
    {
        min = arr[0]
        B.push(min)
    }

    let num = arr.shift()
    A.push(num)

    //每一次进栈的时候都和min作比较 , 小的放入栈B
    
    if(num<B[B.length-1]){
        B.push(num)
    }
    len--
}

//出栈 , 出栈的与 B 最小的相等 B也出栈
let deleteA = ()=>{
    let num = A.pop()
    if(num==B[B.length-1])
    B.pop()
}

//入栈 , 入栈的与 B 最小的相等 B也入栈
let pushA = (data)=>{
    A.push(data)
    if(data<B[B.length-1])
    B.push(data)
}

//输出栈A中最小的值
let pushmin = ()=>{
    return B[B.length-1]
}

console.log(A);
console.log(pushmin());//输出A中最小的
pushA(1)
console.log(A);
console.log(pushmin());//输出A中最小的
deleteA()
console.log(A);
console.log(pushmin());//输出A中最小的

js实现动态规划 , 优化时间 , 优化空间

详情见小灰的算法之旅 , 以下是我个人的见解
题目:
很久很久以前,有一位国王拥有5座金矿,每座金矿的黄金储量不同,
需要参与挖掘的工人人数也不同。例如有的金矿储量是500kg黄金,需 要5个工人来挖掘;有的金矿储量是200kg黄金,需要3个工人来挖 掘…… 如果参与挖矿的工人的总数是10。每座金矿要么全挖,要么不挖,不能 派出一半人挖取一半的金矿。要求用程序求出,要想得到尽可能多的黄金,应该选择挖取哪几座金矿?
在这里插入图片描述

一开始的思路:

首先我们想到的是 : 求出每个金矿的性价比 , 从高到低依次挖出金矿
挖矿的性价比 : 350/3 > 500/5 > 200/3=300/4=400/5
那么我们十个人

  • 先用 3 个人挖 350 的金矿 ,
  • 再用 5 个人去挖 500 的金矿 ,
  • 最后剩下 2 个人不能再挖矿
  • 此时我们的黄金数量是 850
这是否就是十个人能挖的最大黄金数量了呢 ? 答案不是

因为我们 用 5个人 去挖 500 的金矿 , 再用5个人去挖400的金矿 , 此时十个人刚好用完 , 并且黄金的数量是900

那么我们怎么才能获得黄金的最大数量呢 ? 这里就要引入动态规划来实现

动态规划就是把简单的问题转换成规模较小的子问题,再从简单的子问题自底向上一步一步递推,最终得到问题的最优解 , 这样说你可能还是云里雾里
其实我们算出做出每一个举动过后的结果 , 再和其它举动的结果做出比较 , 就可以计算出最好的结果 , 举个列子 :

p:[5,5,3,4,3], //金矿开采需要的工人数量
g:[500,400,350,300,200]//金矿储量

第一个500金矿我们可以挖也可以不挖

  • : 黄金数量+500 , 工人数量 - 5
  •  接下来我们就用剩下的工人去挖下一个金矿 , 此时我们已经获得了500黄金
    
  • 不挖 : 黄金数量 + 0 , 工人数量 - 0
  •  接下来我们就直接用不变的工人数量去挖下一个金矿
    

到第二个400金矿的时候

  • : 黄金数量+400 , 工人数量 - 5
  •    此时已经没有工人 ,  要加上第一次挖的500金矿 , 最终的黄金数量是900
    
  • 不挖 : 黄金数量 + 0 , 工人数量 - 0
  •   到现在为止我们的工人数量和黄金数量都没有变化
    

一直这样遍历下去 , 直到工人数量为 零 或者 可以挖的黄金数量为 零 , 返回我们挖出的黄金数量
在这里插入图片描述

由题可知 , 我们创建几个变量 :

w:工人的数量 ,n:金矿的数量 ,
gold:金矿和人数之间的关系(为了之后的思路比较清晰 , 我把这两个装到一个对象里面)

例如 : gold.g[0] 和 gold.p[0] 之间的关系就是 当金矿储量为 500 时需要 5 个人挖

w = 10 //工人的数量
n = 5  //金矿的数量 
let gold = {
    		p:[5,5,3,4,3],	//金矿开采需要的工人数量
    		g:[500,400,350,300,200]//金矿储量
}

一 . js实现动态规划

我们还要实现状态转移方程 :

  • 金矿数量或者工人数量为时 : F(n,w) = 0
  • 工人的数量不足以挖当前的金矿时 , 去挖下一个金矿 : F(n-1 , w)
  • 当前工人数量足以挖当前的金矿时 : (有两种选择)
    - 挖: F(w-gold.p[n-1],n-1,gold)+gold.g[n-1]
    - 不挖: F(w,n-1,gold)
  • 返回第三种情况的最大值 Math.max(F(w,n-1,gold),F(w-gold.p[n-1],n-1,gold)+gold.g[n-1])
let w = 10//工人数量
let n = 5 //可选金矿数量
let gold = {
    p:[5,5,3,4,3],//金矿开采需要的工人数量
    g:[500,400,350,300,200]//金矿储量
}

let F = (w,n,gold)=>{
    //金矿没了或者是人没了都返回 0 
    if(w==0||n==0)
    return 0

    //当前人数不够挖当前的金矿 , 就去看看下一个金矿 , 人数不变
    if(w<gold.p[n-1])
    return F(w,n-1,gold)

    //返回挖矿或者不挖矿的最优解
    return Math.max(F(w,n-1,gold),F(w-gold.p[n-1],n-1,gold)+gold.g[n-1])
}


console.log(F(w,n,gold));

就这样我们计算出了每一次挖和不挖获得的黄金数量 , 得到了最大的黄金数量 , 但是这样的时间复杂度非常高O(2^n)

二 . 优化时间的动态规划算法 ( 自底向上求解 )

算出第一个金矿的所有

let w = 10//工人数量
let n = 5 //可选金矿数量
let gold = {
    p:[5,5,3,4,3],//金矿开采需要的工人数量
    g:[500,400,350,300,200]//金矿储量
}

// 写一个二维数组
let arr = Array(gold.g.length).fill(0).map(x=>Array(w).fill(0))
let getBestGoldMiningV2 = (arr)=>{
    //数组每一格都根据条件进行状态转移方程
    arr.forEach((item,index)=>{
        //把第一行的状态方程补满
        if(index==0){
            item.forEach((item2,index2)=>{
                if(gold.p[index]<=index2+1){
                    arr[index][index2] = gold.g[index]
                }
            })
        }
        else
        item.forEach((item2,index2)=>{
            //人数不够就去看看上一个
            if(gold.p[index]>index2+1){
                arr[index][index2] = arr[index-1][index2]
            }
            //返回挖和不挖之间的最大值(每一行的值都可以状态转移到上一行去)
            else
            {
            let j = index2-gold.p[index]==-1?0:index2-gold.p[index]
            arr[index][index2] = 
            Math.max(arr[index-1][index2],arr[index-1][j]+gold.g[index])
            }
        })
    })

    //返回最后一格
    return arr[gold.g.length-1][w-1]
}
console.log(getBestGoldMiningV2(arr));

时间没问题了, 让我们来看看空间

三 . 优化空间的算法
//上面代码的时间已经是最优的了 , 但是空间仍有优化的空间
//因为我们在填写其中一行的空格的时候 , 都是根据上一行的空格来获取最大收益的 
//所以我们只需要一行数组就可以了 , 但是在计算的时候要从右往左计算 ,
//避免这一行的值把上一行的值覆盖

let arr2 = Array(w).fill(0)
//初始化第一行的数据
arr2.forEach((item,index)=>{
    if(gold.p[0]<=index+1)
    arr2[index] = gold.g[0]
})
let getBestGoldMiningV3 = (arr2)=>{

    gold.g.forEach((item,index)=>{
        let person = gold.p[index]

        if(index!=0)
        //从右边开始循环arr2
        for(let i = w-1;i>=0;i--){
            let j = i-gold.p[index]==-1?0:i-gold.p[index]
            if(i+1>=person){
                arr2[i] = Math.max(arr2[i],arr2[j]+gold.g[index])
            }
        }
    })

    return arr2[w-1]
}

console.log(getBestGoldMiningV3(arr2));

又是一道动态规划的问题

在这里插入图片描述
一开始直接使用的是动态规划 , 导致栈满

//在这里计算
let path = (m,n,i,j)=>{
    let count = 0
    //向右或者向下走完了只能有一种走法了
    if(i==m||j==n)return 1
    //当两个都没有走完
    return path(m,n,i+1,j)+path(m,n,i,j+1)+count
}

var uniquePaths = function(m, n) {
    //在这里使用path
    return path(m,n,1,1)
};

使用记忆化搜索结合动态规划的思想

//使用数组结合动态规划的思想 , 解决递归栈满的情况
var uniquePaths = function(m, n) {
    //创建一个数组
    let arr = new Array(m).fill(0).map(()=>item = new Array(n).fill(0))
    //初始化数组 , 走到最底部或者最右边都只剩下一种走法
    for(let i =0 ;i<m;i++){
        arr[i][0] = 1
    }
    for(let j =0 ;j<n;j++){
        arr[0][j] = 1
    }
    
    for(let i = 1;i<m;i++){
        for(let j = 1 ;j<n;j++){
            arr[i][j] = arr[i-1][j] + arr[i][j-1]
        }
    }

    return arr[m-1][n-1]

};

找出arr数组中 出现次数 为奇数次的数 , 有两种奇数的数

let arr = [4,1,2,2,3,1,4,5]

//如果只有一个出现次数是奇数的数的话 , 我们只需要对整个数组进行异或运算 , 算出来的结果就是奇数
//如果有两个出现次数是奇数的数的话 , 我们就需要先对整个数组进行异或运算 ,
//根据运算结果把元素组分成两个部分 , 然后再依次进行异或运算

let arr = [4,1,2,2,3,1,4,5]
//进行异或运算的函数
let findLostNum = (arr)=>{
    let result = arr.sort((a,b)=>a-b).reduce((a,b)=>{
        return a^b
    })
    return result
}


//把数组分成两个部分再进行异或运算
let apartArr = (arr)=>{
    if(findLostNum(arr)==0)
    return '测试案例不符合题意'
    //先对整个数组进行异或运算
    let xor = findLostNum(arr).toString(2)
    let flag = xor.split('').reverse().join('').indexOf(1)
    //让mark 指向数组的第一个数
    let mark = 0
    //根据xor的结果把arr分开
    arr.forEach((item,index)=>{
        let flag2 = item.toString(2).split('').reverse().join('').charAt(flag) || 0
        //找到符合条件的值就让mark++
        if(1==flag2){
            //交换mark 和 flag2 当前指向的值
            [arr[mark],arr[index]]=[arr[index],arr[mark]]
            mark++
        }
        
    })
    //现在进行分组寻找
    let left_result = findLostNum(arr.splice(0,mark))
    let right_result = findLostNum(arr)

    return `${left_result} , ${right_result}`

}


console.log(apartArr(arr));

我发现用位运算可以更快并且更加准确的找到用来区分的数字 , 并且在区分数组的时候就进行异或运算

let arr = [4,1,2,2,333,1,4,23333]
//进行异或运算的函数
let findLostNum = (arr)=>{
    let result = arr.sort((a,b)=>a-b).reduce((a,b)=>{
        return a^b
    })
    return result
}


//把数组分成两个部分再进行异或运算
let apartArr = (arr)=>{
    if(findLostNum(arr)==0)
    return '测试案例不符合题意'
    //先对整个数组进行异或运算
    let xor = findLostNum(arr).toString(2)

    //接下来用位运算来找到不同的部分
    let separator = 1
    while (0==(xor&separator)){
        separator<<=1
    }
    //现在separator找到了第一个二进制中相同位置不同的数


    //让mark 指向数组的第一个数
    let result = new Array(2)
    //根据xor的结果把arr分开
    arr.forEach((item)=>{
        //找到符合条件的值就让mark++
        if((item&separator)==0){
            result[0]^=item
        }
        else{
            result[1]^=item
        }        
    })


    return result

}


console.log(apartArr(arr));

三数之和 , 经典的题目了 , 定下一个数 , 转换成二数之和

/**
 * @param {number[]} nums
 * @return {number[][]}
 */


//核心思想就是将三数之和转换为两数之和 , 将a+b+c=0  转换为 b+c = -a 然后利用左右指针找到符合条件的b 和 c 
//在这到题里面还要注意重复的结果要跳过

let quickSort = (arr,i,j)=>{
    if(i>=j)
    return
    let left = i
    let mark = left 
    while(left<=j){
        if(arr[left]<arr[i]){
            mark++
            [arr[left],arr[mark]]=[arr[mark],arr[left]]
        }
        left++
    }
    [arr[i],arr[mark]]=[arr[mark],arr[i]]

    quickSort(arr,i,mark-1)
    quickSort(arr,mark+1,j)
}


 var threeSum = function(nums) {
    if(nums.length<3)
    return []
    //对数组进行排序(使用快排)
    quickSort(nums,0,nums.length-1)
    let arr = []

    for(let i=0;i<nums.length;i++){
        if(nums[i]>0)
        break

        let left = i+1
        let right = nums.length-1

        //去重
        if(i>0&&nums[i]==nums[i-1])
        continue
        
        //left < right就一直循环
        while(left<right){
            let sum = nums[i]+nums[left]+nums[right]
            //找到了等于零的情况 , 一起移动
            if(sum===0)
            {
            arr.push([nums[i],nums[left],nums[right]])

            //在移动左右指针的时候先对左右指针进行判断 ,相同的就跳过
            while(left<right&&nums[left]==nums[left+1])
            left++
            while(left<right&&nums[right]==nums[right+1])
            right--

            left++
            right--
            }else if(sum<0){
                left++
            }else
            right--
        }

    }
    return arr
};
let num = [-1,0,1,2,-1,-4]
console.log(threeSum(num));

最接近的三数之和

/**
 * @param {number[]} nums
 * @param {number} target
 * @return {number}
 */

var threeSumClosest = function (nums, target) {
    //如果数组只有三个数 , 直接退出
    if (nums.length == 3)
    return nums[0] + nums[1] + nums[2]
    //第一层循环定下一个数
    let result
    //排序
    nums.sort((a, b) => a - b)
    //这个是最开始的和与target的差
    let dis = Math.abs(nums[0] + nums[1] + nums[nums.length - 1] - target)

    for (let i = 0; i < nums.length; i++) {

        let left = i + 1
        let right = nums.length - 1

        //使用双指针来找
        while (left < right) {

            let sum = nums[i] + nums[left] + nums[right]


            if (sum > target) {
                right--
                if (Math.abs(target - sum) <= dis) {
                    result = sum
                    dis = Math.abs(target - sum)
                }

            }
            else if (sum < target) {
                left++
                if (Math.abs(target - sum) <= dis) {
                    result = sum
                    dis = Math.abs(target - sum)
                }
            }
            else {
                result = target
                return result
            }
        }

    }


    return result
};

console.log(threeSumClosest([1, 1, 1, 1], 0));

全部翻转链表

function ReverseList(pHead)
{
    if(pHead==null)return pHead
    let p = pHead
    let pre = null
    while(p){
        let t = p.next
        p.next = pre
        pre = p
        p = t
    }
    return pre
    
}

翻转部分链表

请添加图片描述

解法一 , 截取局部翻转再拼接

  1. 要构造一个虚拟头节点 , 如果从头节点开始翻转的话就比较方便(m=1的情况) , 当初写好了主体代码在这里卡了很久
  2. 将要翻转的部分链表作为一个整体链表翻转了之后再拼接回去
function reverseBetween( head ,  m ,  n ) {
    //把要翻转的链表当成一个完整的链表 , 翻转之后再拼接
    //因为头节点也可能被翻转 , 所以要构造一个虚拟节点
    let dummyNode = new ListNode(-1) 
    dummyNode.next = head
    
    let pre = dummyNode
    for(let i = 0;i<m-1;i++){
        pre=pre.next
    }
    let rightNode=pre
    for(let i=0;i<n-m+1;i++){
        rightNode = rightNode.next
    }
    //截取一个子链表
    let leftNode = pre.next
    let cur = rightNode.next
    
    //切断连接
    pre.next = null
    rightNode.next=null
    
    //翻转子链表
    reverseLinkedList(leftNode)
    
    //接回原来的链表
    pre.next = rightNode
    leftNode.next=cur
    return dummyNode.next
    
}
function reverseLinkedList(head){
    let pre = new ListNode(-1)
    pre.next = head
    let p = pre.next
    while(p){
        let next = p.next
        p.next=pre
        pre=p
        p=next
    }
}

解法二: 直接交换

只用遍历一遍要交换的范围 , 将遍历到的节点直接提到交换范围的最前面

function reverseBetween( head ,  m ,  n ) {
    
    //局部翻转的时候首先创建一个虚拟节点
    let dummyNode = new ListNode(-1)
    dummyNode.next = head
    let pre = dummyNode
    
    //把pre放到要反转的节点的前一个节点
    for(let i =0;i<m-1;i++){
        pre = pre.next
    }
    let cur = pre.next
    
    for(let i=0;i<n-m;i++){
        let cur_next = cur.next
        cur.next = cur_next.next
        cur_next.next=pre.next
        pre.next=cur_next
    }
    return dummyNode.next
    
}

两个栈模拟队列

栈1 保存输入的数据 , 再倒腾到栈2中 , 由栈2输出 , 关键就是 栈1 中的数据必须要等到 栈2 中的数据空了才可以倒腾过去

let arr1 = []
let arr2 = []
function push(node)
{
    //新的元素进入栈1
    arr1.push(node)
    //再把栈1中的元素入栈2
    if(arr2.length==0)
    allin(arr1,arr2)
}
function pop()
{
    //每次出栈之前先把栈2内容出完再去找栈1中的内容
    if(arr2.length==0)
    allin(arr1,arr2)
    return arr2.pop()
}
function allin(arr1,arr2){
    let len = arr1.length
    while(len){
        len--
        arr2.push(arr1.pop())
    }
}
module.exports = {
    push : push,
    pop : pop
};

经典递归 : 没有重复项数字的全排列

回溯法:一种通过探索所有可能的候选解来找出所有的解的算法。如果候选解被确认不是一个解(或者至少不是最后一个解),回溯算法会通过在上一步进行一些变化抛弃该解,即回溯并且再次尝试。

/**
 * 
 * @param num int整型一维数组 
 * @return int整型二维数组
 */

//这道题就是不停的选出剩下的所有可能的数字 , 直到选到最后一个数
//再把这些选出来的可能都放进一个数组里面

function permute( num ) {
    // 这是最后要返回的数组
    let res = []
    //这是每次可能的数组
    let arr = []
    //把arr填满 , 再在里面进行数字的交换和判断
    for(let i of num){
        arr.push(i)
    }
    //递归遍历所有的arr的可能
    backtrack(num,res,arr,0)
    return res
}

function backtrack(num,res,arr,first){
    //结束条件是遍历到最后一个数字 , 那么这种arr的可能就完了
    if(first===num.length)
    res.push(arr)
    
    for(let i=first;i<num.length;i++){
        //开始做出所有的可能
        swap(num,first,i)
        //继续递归 , 直到找到当前的arr
        backtrack(num,res,arr,first+1)
        //为了方便回溯 , 还原数组
        swap(num,first,i)
    }
}
function swap(num,first,i){
    [first]=[i]
}

module.exports = {
    permute : permute
};

链表中的节点每k个一组翻转

将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表
如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样
你不能更改节点中的值,只能更改节点本身。

数据范围: 0≤n≤2000\ 0 \le n \le 2000 0≤n≤2000 , 1≤k≤20001 \le k \le 20001≤k≤2000 ,链表中每个元素都满足 0≤val≤10000 \le val \le 10000≤val≤1000
要求空间复杂度 O(1)O(1) O(1),时间复杂度 O(n)O(n)O(n)
例如:
给定的链表是 1→2→3→4→51\to2\to3\to4\to51→2→3→4→5
对于 k=2k = 2k=2 , 你应该返回 2→1→4→3→52\to 1\to 4\to 3\to 52→1→4→3→5
对于 k=3k = 3k=3 , 你应该返回 3→2→1→4→53\to2 \to1 \to 4\to 53→2→1→4→5

思路: 遍历数组 , 每次找到k个节点 就截取下来 , 再翻转 , 翻转了之后再拼接上去
function reverseKGroup( head ,  k ) {
    //创建一个虚拟头节点方便后面拼接
    let dummyNode = new ListNode(-1)
    dummyNode.next = head
    let p = head
    //还需要p的前一个节点
    let start = dummyNode
    let end
    //记录每一次的大小
    let count = 0
    while(p){
        count++
        let res
        let l = null
        //当这是k的倍数时就需要截取翻转拼接
        if(count%k===0){
            end = p.next
            //截取
            p.next = null
            //现在这个res是翻转完了的链表
            res = reverse(start.next)
            //接下来就是把它拼接回去
            start.next = res
            l= res
            while(l.next){
                l =l.next
            }
            l.next = end
            //当前的p就是下一次翻转的前一个节点
            start = l
        }
        l==null?p=p.next:p=l.next

    }
    return dummyNode.next
    
    //包装的翻转的函数
    function reverse(head){
        let q = null
        let p = head
        let next
        while(p!=null){
            next  = p.next
            p.next = q
            q = p
            p = next
        }
        return q
    }
    
}

module.exports = {
    reverseKGroup : reverseKGroup
};
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值