算法渐渐知

算法复杂度,分为空间复杂度和时间复杂度
所谓空间复杂度就是程序执行时需要的内存空间 时间复杂度就是程序执行时需要的计算量(CPU)(时间)
算法思维分为:贪心,二分,动态规划

复杂度是一个数量级不是一个具体的数字
在这里插入图片描述
O(1)一次就够 :是指不循环操作

fun function(item={}){
    return item?.a + item.b
}

O(n):指的是有一个单循环

fn function(arr){
    For(let I =0;let i <arr.length;i++){}
} 

O(n^2):数据量的平方 指的是有双循环

fun function(arr){
    for(let i=0I<arr.length;i++){
        for(let j = 0;j<arr.length;j++){
}
}
}

O(logn):数据量的对数 指的是二分

比如说在一个顺序数组里面查找一个数字
先取中间一个数字,如果比要找的大,把前部分的再二分,再比较,再二分。。。

O(nlogn):数据量数据量的对象

就是先二分再循环

前端是重时间轻空间

将一个数组旋转k步

将数组的元素翻转到数组的前面,k是翻转几个元素
两种思路
1.循环k步,将数组的元素从尾部删除,插到头部
此方法的时间复杂度是O(n^2)
空间复杂度是O(1)

export const arrAction= (arr=[],num)=>{
    if(!arr.length||!num)return arr
    const step = Math.abs(num%arr.length)
 for(let i =0;i<step;i++){//for循环的复杂度是O(n)
     let n = arr.pop()
     n!=null&& arr.unshift(n)//数组是有顺序的,往数组前面添加那么后面的元素的顺序也会变动,所以unshift的时间复杂度是O(n)
 }
 return arr
}
arrAction([1,2,3,4,5,7],3) //【4,5,7,1,2,3】

2,将数组从后面截取k个,再取前面的,两者合在一起即可
此方法时间复杂度是O(1)
空间复杂度是O(n)

export const sliceAction=(arr=[],num)=>{
    if(!arr.length||!num)return arr
    const step = Math.abs(num%arr.length)
    const arr1 = arr.slice(-step)//slice是返回新数组,并没有修改原数组,所以slice的速度很快 所以复杂度是 O(1)
    const arr2 = arr.slice(0,arr.length-step)
    return [...arr1,arr2]
}
sliceAction([1,2,3,4,5,7],3)//【4,5,7,1,2,3】

3.在数组元素原有的索引上面去操作
在这里插入图片描述

这样修改之后他们的索引有一个规律:
比如说传进去的k是3,数组的后面三个元素的索引各自减去k+1就是新的索引,那其他的元素的索引就是各自加3,入上图所示。
但是此方法的时间复杂度是O(n)
空间复杂度是O(1)
以时间复杂度来说,远不如第二种

这两个算法的若论差异好坏,这里可以做一下性能测试

console.time('arrAction')
arrAction(arr1,90000)
console.timeEnd('arr1'); //8000ms

console.time('sliceAction')
arrAction(arr1,90000)
console.timeEnd('sliceAction'); //1ms

当然是第二种更优

检验算法的鲁棒性(单元测试)
这里使用的jest

import {arrAction,sliceAction} from "./arrAction.js"
// describe测试描述
describe('数组旋转',()=>{
    it('是否相等',()=>{
        let res = arrAction([1,2,3,4,5,7],3)
        expect(res).toEqual([4,5,7,1,2,3]) //断言
    })
    it('数组为空',()=>{
        let res = arrAction([],3)
        expect(res).toEqual([]) //断言
    })
      it('num是负值',()=>{
        let res = arrAction([[1,2,3,4,5,7],-3)
        expect(res).toEqual([4,5,7,1,2,3]) //断言
    })
    it('num不是数字',()=>{
        let res = arrAction([1,2,3,4,5,7],'abc')
        expect(res).toEqual([1,2,3,4,5,7]) //断言
    })
})

// 运行单元测试用例
 npm ject 'src/...写的测试用例的url'

判断字符串是否括号匹配

  • 一个字符串s可能包括{},[],()
  • 判断s是否是括号匹配的
  • 如(a{b}c)匹配,而{a(b或{a(b}s)就不匹配
const matchFunc = (left,right){
    if(left==='{' && right === '}') return true
    if(left==='[' && right === ']') return true
    if(left==='(' && right === ')') return true
    return false
}
const matchBracket = (str){
    let leftStr = "{[("
    let rightStr = ")]}"
    let stack = []
    if(str.length==0)return true
    for(let i = 0;i<str.length;i++){
        let st = str[i]
        if(leftStr.includes(st)){
            stack.push(st)
        }else if(rightStr.includes(st)){
            let top = stack[stack.length-1]
            if(matchFunc(top,st)){
                stack.pop()
            }else{
                return false
            }
        }
    }
    return stack.length == 0
}

// 功能测试
const str = "a{b(2[f])}"
console.info(matchBracket(str));

// 单元测试
describe('括号匹配',()=>{
    it('正常情况',()=>{
        const str = 'a{b[c(e)]}'
        const res = matchBracket(str)
        expect(res).toBe(res) //toBe 就是boolean的判断
    })
    it('不匹配',()=>{
        const str = 'a{b[c{e)]}'
        const res = matchBracket(str)
        expect(res).toBe(res) //toBe 就是boolean的判断
    })
    it('空字符',()=>{
        const str = ''
        const res = matchBracket(str)
        expect(res).toBe(res) //toBe 就是boolean的判断
    })
})

两个栈模拟一个队列

  • 需要具有添加(入队) 删除头部的元素(出队),和获取长度的功能
    栈:是先进后出,
    队列是先进先出
    代码:
export class MyQueue {
    private stack1 = []
    private stack2 = []
    // add就是入队
    add(n){
      this.statck1.push(n)
    }
    
    // 出队
    // 三步
    // 队列的形式先进先出,但是栈是先进后出,所以这里通过两个栈做模拟这种先进先出
    // 原则:保持先进先出
    delete(){
        let res
        let stack1 = this.stack1
        let stack2 = this.stack2
        // 先将stack1中的值放到stack2中
        while(stack1.length){
            let t = stack1.pop()
            if(t!==null){
                stack2.push(t)
            }
        }

        res = stack2.pop() //得到删除的元素
        while(stack2.length){
            let s = stack2.pop()
            if(s!==null){
                stack1.push(s)
            }
        }
        return res||null
    }
    get length(){
        return this.stack1.length
    }
}


// 功能测试
const q = new MyQueue()
q.add(1)
q.add(2)
q.add(3)
console.log(q.length);
console.info(q.delete());


// 单元测试

describe('两个栈一个队列',()=>{
    it('add And length',()=>{
        const q = new MyQueue()
        expect(q.length).toBe(0)
        q.add(100)
        q.add(200)
        q.add(300)
        expect(q.length).toBe(3)
    })
    it('delete',()=>{
        const q = new MyQueue()
        expect(q.delete()).toBe(0)
        q.add(100)
        q.add(200)
        q.add(300)
        expect(q.delete()).toBe(2)
    })
})

反转单向链表

链表和数组的区别
链表和数组都是有序的结构

链表是查询慢O(n) 新增删除快O(1)
数组是查询快O(1) 新增删除慢O(n)

数组是连续存储的,查询快是因为它有下标
链表是零散存储的结构,不需要像数组一样去前后处理空间
首先根据数组创建单向链表
// 根据数组创建单向链表
 export function createLinkList(arr){
    let length = arr.length
    if(length == 0) throw new Error('Array is empty')
    let curNode = {
        value:arr[length-1]
    }
    if(length ==1)return curNode
    for(let i = length-2;i>=0;i--){
        curNode = {
            value:arr[i],
            next:curNode
        }
    }
    return curNode
    
}

// 功能测试
// let res = createLinkList([1,2,3,4])
// console.info(res);
反转单向链表
export function flipList(node){
    let curNode = null
    let preNode = null
    let nextNode = node
    while(nextNode){
        if(curNode&&!preNode){
            // 防止循环引用  改变方向之后第一个就没有next了
            delete curNode.next
        } 
        // 反转指针
        if(curNode&&preNode){ //中间的内容  因为最后一个是没有next的,所以最后一个是走不到当前这一步的
            curNode.next = preNode
        }

        // 整体向后移动指针
        curNode = nextNode
        nextNode = nextNode?.next
        preNode = curNode
    }
    // 最后一个补充:当nextNode空时  此时curNode尚未设置next
    curNode.next = preNode
    return curNode
}

// 功能测试
// let res = createLinkList([1,2,3,4])
//const info = flipList(res)
//console.info(info)


//单元测试
describe('反转单向链表',()=>{
    it('单个元素',()=>{
        const node = flipList({value:100})
        expect(node).toEqual({value:100})
    })
    it("多个元素",()=>{
        const node = createLinkList([100,200,300])
        const node1 = flipList(node)
        expect(node1).toEqual({
            value:300,
            next:{
                value:200,
                next:{
                    value:100
                }
            }
        })
    })
})

链表实现队列

链表和数组,哪个实现队列更快
队列是一种逻辑结构,
链表和数组是一种物理结构
数组实现队列是有性能问题
链表是非连续存储,add,delete都很快,
数组是连续存储,push很快,shift很慢
所以链表实现队列更快一些


// 链表实现队列
export class myQueue{
    private head = null
    private tail = null
    private len = 0   //维护length,遍历获取length,时间复杂度太高
    // 入队操作
    add(n){
        const newNode = {
            value:n,
            next:null
        }
        // 处理head
        if(this.head == null){
            head = newNode
        }
        // 处理tail
       let tailNode = this.tail
       if(tailNode){
           tailNode.next = newNode
       }
       this.tail = newNode
       this.len ++
    }
    // 出队,在head位置
    delete(){
        const headNode = this.head
        if(this.len<=0)return null
        if(headNode == null) return null
        // 取值
        const value = headNode.value
        this.head = headNode.next
        this.len --
        return value
    }
    get length(){
        return this.len
    }
}

// 功能测试
const q = new myQueue
q.add(100)
q.add(200)
q.add(300)
console.log('length',q.length)


// 单元 测试
descript('链表实现队列',()=>{
    it('add and length',()=>{
        const a = new myQueue()
        a.add(100)
        a.add(200)
        a.add(300)
        expect(a.length).toBe(3) //除对象之外的断言用toBe
    })
    it('delete',()=>{
        const a = new myQueue()
        expect( a.delete()).toBeNull() //toBeNull断言为null
        a.add(100)
        a.add(200)
        a.add(300)
        expect( a.delete()).toBe(3) //除对象之外的断言用toBe
       
        expect( a.delete()).toBe(2) //除对象之外的断言用toBe
        a.delete()
        expect( a.delete()).toBe(1) //除对象之外的断言用toBe
        a.delete()
        expect( a.delete()).toBeNull() //toBeNull断言为null
    })
})

性能测试


const q = new MyQueue()
console.time('queue with list')
for(let i = 0; i<10 *1000;i++){
    q.add(i) //入队
}
for(let i = 0; i<10 *1000;i++){
    q.delete()//出队
}
console.timeEnd('queue with list') //17ms

const q2 = []
console.time('queue with Array')
for(let i = 0; i<10 *1000;i++){
    q2.push(i) //入队
}
for(let i = 0; i<10 *1000;i++){
    q.shift() //出队
}
console.timeEnd('queue with Array') //420ms
  • 数据结构的选择要比算法优化更重要
  • 时间复杂度的敏感性,比如length不能遍历查找

二分查找

// 循环查找
export function binarySearch1(arr, target) {
  let length = arr.length
  if (length == 0) return -1
  let startIndex = 0
  let endIndex = length - 1
  while (startIndex <= endIndex) {
    let midIndex = Math.floor((startIndex + endIndex) / 2)
    let midValue = arr[midIndex]
    if (target > midValue) {
      startIndex = midIndex + 1
    } else if (target < midValue) {
      endIndex = startIndex - 1
    } else {
      return midIndex
    }
  }
  return -1
}

// 递归查找
export function binarySearch2(arr, target, startIndex, endIndex) {
  let length = arr.length
  if (length == 0) return -1
  if (startIndex > endIndex) return -1
  if (!startIndex) startIndex = 0
  if (!endIndex) endIndex = length - 1
  let midIndex = Math.floor((startIndex + endIndex) / 2)
  let midValue = arr[midIndex]

  if (midValue > target) {
    return binarySearch2(arr, target, startIndex, midIndex - 1)
  } else if (midValue < target) {
    return binarySearch2(arr, target, midIndex + 1, endIndex)
  } else {
    return midIndex
  }
}
// 功能测试
const arr = [10,20,30]
console.info(binarySearch2(arr,20)) //1
// 单元测试
describe('二分查找',()=>{
    it('正常情况',()=>{
        let arr = [1,2,3,4]
       let index = binarySearch1(arr,3)
       expect(index).toBe(2)
    })
    it('空数组',()=>{
        let arr = []
       let index = binarySearch1(arr,3)
        expect(index).toBe(-1)
    })
    it('找不到target',()=>{
        let arr = [1,2,3,4,5]
       let index = binarySearch1(arr,6)
        expect(index).toBe(-1)
    })
})

二分循环与递归的性能比较
console.time('binarySearch1')
for(let i =0;i<100*1000;i++){
  binarySearch1(arr,target)
}
console.timeEnd('binarySearch1') //17ms

console.time('binarySearch2')
for(let i =0;i<100*1000;i++){
  binarySearch2(arr,target)
}
console.timeEnd('binarySearch2') //34ms

虽然他们数量级是一样的,时间复杂度都是是(Ologn),但是因为
循环是一个函数 ,递归要频繁调用函数, 因为函数的调用也会消耗时间。
所以所以非递归性能更好一点
但是相对来说 递归逻辑更清晰

  • 所以凡是有序,都可以二分,
  • 凡是二分,时间复杂度都包含O(logn)

二分查找查找两数之和

function findNum(arr, target) {
  let res = []
  let length = arr.length
  let i = 0
  let j = length-1
  if(length === 0)return res
  
  while (i < j) {
    let n1 = arr[i]
    let n2 = arr[j]
    let n = n1 + n2
    if (n > target) {
      j--
    } else if (n < target) {
      i++
    } else {
      res.push(n1)
      res.push(n2)
      break
    }
  }
  return res
}

二叉树前序中序后序遍历

const treeNode = {
  value:10,
  left:{
    value:5,
    left:null,
    right:null,
  },
  right:{
    value:7,
    left:{
      value:0,
      left:null,
      right:null
    },
    right:{
      value:0,
      left:null,
      right:null
    }
  }
}

// 二叉树前序遍历
function preOrderTraverse(node){
  if(node == null)return
 console.log(node.value);
 preOrderTraverse(node.left)
 preOrderTraverse(node.right)
}

// 二叉树中序遍历
function midOrderTraverse(node){
  if(node == null)return
  preOrderTraverse(node.left)
  console.log(node.value);
  preOrderTraverse(node.right)
 }

 // 二叉树后序遍历
function afterOrderTraverse(node){
  if(node == null)return
  preOrderTraverse(node.left)
  preOrderTraverse(node.right)
  console.log(node.value);
 }

动态规划

何为动态规划

  • 就是把一个大问题,拆分成小问题,逐级向下拆解
  • 用递归的思路去分析问题 再改为循环来实现
  • 算法三大思维:贪心,二分,动态规划

递归实现

getMath(num){
      if(num<=0)return 0
      if(num ==1)return 1
      return this.getMath(num-1)+this.getMath(num-2)
    }
this.getMath(4) //3

使用递归的话时间复杂度是2的n次方,时间复杂度太高
这里可以使用循环降低时间复杂度为n的平方

  let funcAction = (num)=>{
      if(num<=0)return 0
      if(num ==1)return 1
      let n1 = 1
      let n2 = 0
      let res =0
      for(let i =2;i<=num;i++){
        res = n1 + n2
        n2 = n1
        n1 = res
      }
      return res
    }
    console.log("funcAction",funcAction(4));  //3
青蛙跳台阶

要跳到1级台阶,就一种方式:f(1) = 1
要跳到2级台阶,就二种方式:f(2) = 2
要跳到n级台阶,f(n) = f(n-1)+f(n-2)

将数组中的0转移到数组末尾

在原有的数组中操作,不能重建新数组

  let moveZero=(arr)=>{
    let length = arr.length
    if(length == 0)return 
    let zeroLength = 0
    for(let i =0;i<length-zeroLength;i++){
      if(arr[i]==0){
        arr.push(0)
        arr.splice(i,1)
        i--;
        zeroLength++
      }
    }
    return arr
  }
  const arr = [1,2,4,0,6,0,3,0,5]
  console.log('3333',moveZero(arr));

双指针实现
- 定义j指向第一个0,i指向j后面第一个非0
- 交换j和i的值 向后移动
- 只遍历一次 所以时间复杂度是O(n)

 const moveZero(arr){
      const length = arr.length
      if(length === 0) return 
      let i;
      let j = -1 //让j指向第一个0
      for(i=0;i<length;i++){
        if(arr[i]==0){
          if(j<0){
            j = i
          }
        }
        if(arr[i]>0&&j>=0){
          let n = arr[i]
          arr[i] = arr[j]
          arr[j] = n
          j++
        }
      }
    }

求一个连续字符中最多的字符是什么,以及个数(如’aaabbbcccnnnnn’)

双指针思路
- 定义指针i 和 j,j不动,i继续移动
- 如果i和j一直相等 则i一直继续移动
- 直到i和j的值不相等,记录这个字符的长度和字符,并让j追上i,  继续进行第一步

时间复杂度是O(n)

    const findStrMaxCountChar(str)=>{
      let length = str.length
      if(!length)return
      let res = {
        char:"",
        length:0
      }
      let i =0;
      let j =0;
      let tempLenth =0;
      for(;i<length;i++){
        if(str[i]== str[j]){
          tempLenth ++
        }
        // 如果i对应的字符和j对应的字符不相等或者到了末尾
        if(str[i]!=str[j]||i==length-1){
          if(tempLenth>res.length){
            res.char = str[j]
            res.length = tempLenth
          }
          tempLenth =0//reset
          if(i<length-1){
            j = i //让j追上i
            i-- //细节,不然会错过当前这个字符对比
          }
        }
      }
    }

数组快速排序

固定算法,固定思路

找到中间位置midValue
遍历数组,小于midValue放在left,否则放到right
继续递归 最后concat拼接 返回
时间复杂度是O(n*logn)

    const sortArr = (arr)=>{
      let length = arr.length
      if(!length)return 0
      let leftArr = []
      let rightArr = []
      let midIndex = Math.floor(length/2)
      let midValue = arr.splice(midIndex,1)[0]
      
      for(let i=0;i<arr.length;i++){
        let n = arr[i]
        if(n>midValue){
          leftArr.push(n)
        }else{
          rightArr.push(n)
        }
      }
      return sortArr(leftArr).concat(
        [midValue],
        sortArr(rightArr)
      )
    }

如果是使用slice
更推荐使用slice,不修改原数组

  const sortArr = (arr) => {
      let length = arr.length
      if (!length) return 0
      let leftArr = []
      let rightArr = []
      let midIndex = Math.floor(length / 2)
      let midValue = arr.slice(midIndex, midIndex + 1)[0]
        //O(n)
      for (let i = 0; i < length; i++) {
        if (i !== midIndex) {
          let n = arr[i]
          log(n)
          if (n > midValue) {
            leftArr.push(n)
          } else {
            rightArr.push(n)
          }
        }
      }
      return sortArr(leftArr).concat([midValue], sortArr(rightArr))
    }

求1-100中的回文

回文就是指正着读和反着读一模一样

  • 思路1: - 使用数组反转、比较
    数字转换为字符串 再转换为数组
    数组reverse,再join为字符串
    前后字符串做对比
 findPalindromeNumber(max) {
      let res = []
      if (max <= 0) return res
      for (let i = 1; i <= max; i++) {
        const s = i.toString()
        if (s == s.split('').reverse().join('')) {
          res.push(i)
        }
      }
      return res
    },
  • 思路二字符串头尾比较
    数字转字符串
    字符串头尾进行比较
    也可以用栈 像括号匹配,但要注意奇偶数
 findPalindromeNumber2(max) {
      let res = []
      if (max <= 0) return res
      for (let i = 1; i <= max; i++) {
        const s = i.toString()
        const length = s.length
        let flag = true
        let startIndex = 0
        let endIndex = length - 1
        while (startIndex < endIndex) {
          if (s[startIndex] !== s[endIndex]) {
            flag = false
            break
          } else {
            startIndex++
            endIndex--
          }
        }
        if (flag) res.push(i)
      }
      return res
    },
  • 思路三 生成翻转数
    就是数字不变成字符串了,也不变成数组了,直接把数字生成翻转数
    使用%和Math.floor生成翻转数
    前后数字进行对比
 findPalindromeNumber3(max) {
      let res = []
      if (max <= 0) return res
      for (let i = 1; i <= max; i++) {
        let n = i
        let rev = 0
        // 翻转数字   比如123
        while (n > 0) {
          rev = rev * 10 + (n % 10)//321
          n = Math.floor(n / 10) //0
        }
        if (i == rev) res.push(i)
      }
      return res
    },

    console.log('====================================')
    console.log(this.findPalindromeNumber3(100)) //[1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99]
    console.log('====================================')

思路1 看似时间复杂度是O(n),但是数组转换和翻转都需要时间,所以会更慢
思路2和思路3相比,操作数字更快,(因为电脑的原因就是计算机)
用栈不合适 因为用栈一般是用数组实现,会慢

所以尽量不要转换数据结构,尤其数组这种有序结构
尽量不要用内置API, 如reverse,不好识别复杂度
数字操作最快,其次是字符串

高效的字符串前缀匹配

  • 比如说针对一个字符串
    针对第一个字母对应26个字母,第二个对应26个字母
    然后往下拆分继续26个

  • 以hash的形式编写数据结构
    对象的结构
    或者map的结构
    对象或hash通过key查询,时间复杂度是O(1)

  • 遍历数组,时间复杂度至少是O(n)起步,(n是数组长度)
    而改为树 时间复杂度降低到O(m) (m是单词的长度)
    PS:哈希表(对象)通过key查询,时间复杂度是O(1)

  • 考虑优化原始数据结构
    有明确范围的数据(比如26个英文字母),考虑使用哈希表(对象)
    以空间换时间,定义数据结构最重要

  • 算法决定了数据的下限,数据结构决定了数据的上限

数字千分位格式化

 - 将数字千分位格式化  输出字符串
  如输入12050100  输出字符串12,050,100
  (注意:逆序判断)

  思路
  - 转换为数组  reverse,每三位拆分
  - 使用正则表达式
  - 使用字符串拆分

  - 计算机的东西能简单就别复杂
  什么简单? 数字简单
  就是说如果能通过数字,你就别转成字符串
  如果能通过操作字符串实现,就别用数组

  如果能通过手写API实现,就不要用语法糖或者正则表达式

  使用数组  转换影响性能
  使用正则表达式  性能性差
  使用字符串  性能较好

** 使用数组**

  format1(num) {
      let n = Math.floor(num) //只操作整数
      let s = num.toString()
      let arr = s.split('').reverse()
      return arr.reduce((prev, current, index) => {
        if (index % 3 == 0) {
          return current + ',' + prev
        } else {
          return current + prev
        }
      }, '')
    },

使用字符传

  format2(num) {
      let n = Math.floor(num)
      let s = n.toString()
      let str = ''
      let length = s.length -1  //下面的图解读这一句
      for (let i = length - 1; i >= 0; i--) {
        const j = length - i
        if (j % 3 == 0) {
          if (i == 0) {
            str = s[i] + str
          } else {
            str = ',' + s[i] + str
          }
        } else {
          str = s[i] + str
        }
      }
      return str
    },

let length = s.length -1
在这里插入图片描述
在这里插入图片描述

切换字母大小写

  输入一个字符串  切换其中字母的大小写
  如,输入字符串123abc,输出123ABC
  思路: 正则表达式
  ASCII码的方式

** 正则的方式**


    switchLetterCase(s) {
      let res = ''
      let length = s.length
      if (!length) return res
      let reg1 = /[a-z]/
      let reg2 = /[A-Z]/
      for (let i = 0; i < length; i++) {
        let n = s[i]
        if (reg1.test(n)) {
          res += n.toUpperCase()
        } else if (reg2.test(n)) {
          res += n.toLowerCase()
        } else {
          res += n
        }
      }
      return res
    },

ASCII码的方式

 switchLetterCase2(s) {
      let res = ''
      let length = s.length
      if (!length) return res
      for (let i = 0; i < length; i++) {
        let c = s[i]
        let n = c.charCodeAt(0)
        if (n >= 65 && n <= 90) {
          res += c.toLowerCase()
        } else if (n >= 97 && n <= 122) {
          res += c.toUpperCase()
        } else {
          res += c
        }
      }
      return res
    },

为什么0.1 + 0.2 !== 0.3

  • 计算机使用二进制存储数据
  • 整数转换二进制没有误差,但是小数是有误差的,无法完全准确的二进制表示,是无尽的小数
  • 项目中怎么避免呢 使用mathjs三方库,如果经常用小数的话

删除链表中连续相同值的节点

function ListNode (val, next = null) {

this.val = val

this.next = next

}

const getListFromArray = a => {

let dummy = new ListNode()

let pre = dummy

a.forEach(x => (pre = pre.next = new ListNode(x)))

return dummy.next

}
const getArrayFromList = node => {

let a = []

while(node){
a.push(node.val)
node = node.next

}
return a

}

function deleteDuplicates (head) {

// 空指针或者只有一个节点不需要处理
if (head == null || head.next === null) return head

let dummy = new ListNode()

let oldLinkCurrent = head

let newLinkCurrent = dummy

while (oldLinkCurrent) {

let next = oldLinkCurrent.next

if (next && oldLinkCurrent.val == next.val) {

while (next && oldLinkCurrent.val == next.val) {

next = next.next

}
oldLinkCurrent = next

} else {

newLinkCurrent = newLinkCurrent.next = oldLinkCurrent

oldLinkCurrent = oldLinkCurrent.next

}
}
logList(dummy.next)
return dummy.next

}
console.log(deleteDuplicates(getListFromArray([1,2,3,3,4,4,5]))); // {value:1,next:{value:2,next:{value:5}}}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值