面试常问的 JavaScript 数据结构与算法 (适用新手)

380 阅读15分钟

1 数组

字符串 和 数组 的基础方法

var str = 'hello, july'
var a

a = str.length//11
a = str[0]//h

a = str.concat('hi','cool')//hello, julyhicool

a = str[0]//h
a = str.indexOf('h')//0, 如果没有就是  -1
a = str.lastIndexOf('h')

a = str.slice(0,2)//he

a = Array.isArray(a)//true
a = str.toUpperCase()//HELLO, JULY
a = str.toLowerCase()//hello, july

//字符串变数组
a = str.split(',')//[ 'hello', ' july' ]
a = str.split('')//['h', 'e', 'l', 'l', 'o', ',', ' ', 'j', 'u', 'l', 'y']
//数组变字符串
a = a.join('')//hello, july
a = a.toString()//hello, july
 var n = [1,23,4,45,345,2,1]
 n.push(11)
 n.push(14,24)
 n.unshift(-2)//在首位添加
 n.pop()
 n.shift()//从首位删除
 console.log(n.splice(1,3))
 var n= b.concat(a,c)
 let ncopy = Array.of(...n)
 let one = Array(6).fill(1)
 console.log(num.reverse())
 console.log(num.sort())
 console.log(n.indexOf(2))
 console.log(n.includes(999))
 console.log(n.toString())
 var b= n.join('-')

数组去重(三种方法)

方法一:arr.forEach


function unique(arr) {
  //1 申明一个空数组存放
  const res = []
  //2 遍历原始数组
  arr.forEach(item =>{
    //3 判断 res数组中是否包含这个元素
    if (res.indexOf(item) === -1){
      //4 若干没有,就插入res中
      res.push(item)
    }
  })
  // 5 返回结果
  return res
}

方法二:arr.forEach+空对象

function unique2(arr) {
  //1 申明一个空数组存放
  const res = []
  //2 申明空对象,
  const obj = {}
  //3 遍历原始数组
  arr.forEach(item =>{
    //5
    if (obj[item] === undefined){
      //4 将arr中遍历的item作为下标存在obj{}中,比较会方便
      obj[item] = true
      res.push(item)
    }
  })
  //6 返回结果
  return res
}

方法三:集合Set,利用集合Set自身的去重功能

function unique3(arr) {
  //将数组转化为集合Set,这里利用集合Set自身的去重功能
  let set = new Set(arr)
  //将set展开 创建数组
  let array = [...set]
  return array
}

// 方法三(精简):
function unique4(arr) {
  return [... new Set(arr)]
}

console.log(unique4([1,2,2,4,3,2]))

数组合并 concat

function concat(arr,...args ){
  //1 申明空数组,将arr中数压到res中
  const res =  [...arr]
  //2 遍历数组
  args.forEach(item =>{
    //3判断item是否为数组
    if (Array.isArray(item)) {
      //4 ...item 是解构赋值,逐个将item数组中元素放入res
      res.push(...item)
    } else {
      res.push(item)
    }
  })
  //5 返回结果
  return res
}

console.log(concat(arr,[4,5,6],7,8,9))

数组切片 slice

/*
* @param {Array} arr
* @param {Number} start
* @param {Number} begin
*
* */
function slice(arr,start,end){
  //5 加入判断,arr长度是否为0
  if (arr.length === 0) {
    return []
  }
  // 6 判断 start,如果传了,就是start,要不就是0
  start = start || 0
  //7 如果start 越界,就返回空数组
  if (start >= arr.length){
    return []
  }
  //8 判断end,如果传了,end,要不默认就是数组长度
  end = end || arr.length
  //9 如果end小于start就将数组长度给end
  if (end < start){
    end = arr.length
  }

  //1申明一个空数组,slice返回是新数组
  const res = []
  //2 遍历对象
  for(var i = 0; i < arr.length; i++){
    //3 判断i在数组的位置是否合法
    if (i >= start & i < end) {
      //4 将下标对应的元素加入res[]
      res.push(arr[i])
    }
  }
  //10 返回结果
  return res
}



let arr = [1,2,3,4,5,6,7,8]
const res = slice(arr,3,6)
console.log(res)


// let res = arr.slice(1,4)//切片数组2-4

数组扁平化(两种方法)

(多维数组>一维数组)

方法一:使用递归(原数组上操作)

function flatten1(arr){
//  1申明一个空数组
  let res = []
  //2 遍历数组
  arr.forEach(item=>{
    //3 判断item是否是数组
    if (Array.isArray(item)) {
      //4 item是数组,递归
      //并且与res[]连接
      // 重新赋值
      res = res.concat(flatten1(item))
    } else {
      //5 如果item不是数组
      //重新赋值
      res = res.concat(item)
    }
  })
//6 返回结果
  return res
}

方法二,使用some,新数组上操作

function flatten2(arr){
  //1 申明数组
  let res = [...arr]
  //2 循环判断
  //some用了判断数组中是否有一个满足条件,就返回true
  while (res.some(item=>Array.isArray(item))){
    //res = [].concat([1,2,[3,4,[5,6]],7])
    //第一次循环完毕即[1,2,3,4,[5,6],7]
    //第二次循环完毕即[1,2,3,4,5,6,7]
    res = [].concat(...res)
  }
  //3 返回结果
  return res
}


let arr = [1,2,[3,4,[5,6]],7]
console.log(flatten2(arr))

数组分块(一维数组 > 二维数组)

function chunk(arr,size=1){
  //7 判断
  if (arr.length === 0){
    return []
  }
  //1 申明两个变量
  let res = []
  let tmp = []//先放tmp[],再放入res[]
  //2 遍历
  arr.forEach(item=>{
    //4 判断tmp长度是否为0
    if (tmp.length == 0) {
      //5 将tmp压入res[]中
      res.push(tmp)
    }
    //3 将元素压入tmp
    tmp.push(item)
    // 6 判断tmp数组中长度是否为size,如果是则清空
    if (tmp.length === size){
      tmp = []
    }
  })
  // 8 返回结果
  return res
}

console.log(chunk([1,2,3,4,5,6,7],4))

数组差集 :

API: filter,遍历,并过滤 对数组1遍历,对比数组2

function diff(arr1,arr2=[]){
  //2 判断参数
  if (arr1.length === 0) {
    return  []
  }
  //3 判断参数
  if (arr2.length === 0) {
    return  arr1.slice()//slice 返回新数组
  }
  // 1 filter,遍历,并过滤
  const res = arr1.filter(item => !arr2.includes(item))
  return res
}

console.log(diff([1,2,4,5],[5,6,8]))

删除数组中单个元素(原数组 or 新数组)

原数组

splice改变数组长度,当减掉一个元素后,后面的元素都会前移,因此需要相应减少i的值

function removeWithoutCopy(arr, item) {
  for(var i = 0; i < arr.length; i++){
    if (item == arr[i]){
   // splice改变数组长度,当减掉一个元素后,后面的元素都会前移,因此需要相应减少i的值
      arr.splice(i,1)
      i--;
    }
  }
  return arr
}

新数组(两个方法)filter

方法一:filter

function remove2(arr, item) {
  return arr.filter(function (a) {
    return a != item
  })
}

方法二:暴力破解

function remove1(arr, item) {
  var arr1 = []
  for(var i = 0; i < arr.length; i++){
    if (arr[i] != item) {
      //如果arr[i]不等于item,就加入数组a
      arr1.push(arr[i])
    }
  }
  return arr1
}

删除数组中部分元素

API:pull(array,...value)

function pull(arr,...args) {
  //1 申明空数组 ,保存删除的元素
  const res = []
  // 2 遍历arr
  for(var i = 0; i < arr.length; i++){    //3 判断i 元素在不在arg数组中
    if(args.includes(arr[i])){
      //4将arr[i]传入res[]
      res.push(arr[i])
      //5 删除arr[i]
      arr.splice(i,1)//splice可以改变原始数组
      //6 下标自减
      i--
    }
  }
  // 7 返回
  return res

}

function pullAll(arr,values) {
  return pull(arr,...values)
}

arr = [1,2,3,4,5,6]
// console.log(pull(arr ,1,4,5))
// console.log(arr)
console.log(pullAll(arr,[2,3,4]))
console.log(arr)

得到数组部分元素

filter 改变原始数组,产生新数组

function drop(arr,size) {
  //filter 过滤原始数组,产生新数组
  return arr.filter((value ,index)=>{
    return index >= size
  })
}

function dropRights(arr,size){
  return arr.filter((value ,index)=>{
    return  index < arr.length-size
  })
}
console.log(drop([1,24,5,6],2))
console.log(dropRights([1,24,5,6],2))

获取数组中重复的数和次数

目标参数若是字符串,可通过split()方法转换为数组

var str="qwertyuiopasdfghjklzxcvbnmqazwsxaswazaaa";
var arr=str.split("");  // 转换为数组

function moreValue() {
  if (!arr) return false;
  if (arr.length === 1) return 1;
  let result = {}
  let maxNum = 0;//元素出现的次数
  let maxValue = null;//最大的对应的maxValue值
  // 循环对象,取出value值最大的对应的maxValue值
  for(let i = 0; i < arr.length;i++) {
    let val = arr[i]
    // 先循环数组,把元素作为key值,元素出现的次数为value值,塞进result对象中
    //循环数组塞进对象的操作用三元表达式判断,合并为一个for循环
    result[val] === undefined ? result[val] = 1 : result[val]++;
    if(result[val] > maxNum) {
      maxNum = result[val];
      maxValue = val
    }
  }
  return maxValue +','+ maxNum;
}

13.调整数组顺序使奇数位于偶数前面(Write Code

  • 题目描述:

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。

  • 解释:

按照题目要求可以直接一次循环,将奇数和偶数分开放进不同的数组中,最后在进行concat拼接即可。

  • 代码实现:
function reOrderArray(array) {
    //定义result1数组用于存放奇数
    let result1 = [];
    //定义result2数组用于存放偶数
    let result2 = [];
    //缓存长度
    let len = array.length;
    for (let i = 0; i < len; i++) {
        //判断如果是奇数存进result1
        if (array[i] % 2 === 1) {
            result1.push(array[i]);
        } else {
            // 奇数存进result2
            result2.push(array[i]);
        }
    }
    //拼接奇数+偶数
    return result1.concat(result2);
}

输入二维数组和一个整数,判断二维数组中是否含有该整数

const arr = [1,5,9;3,8,12;6,9,19]; function hasNumber(array,target){}

解法 1:暴力法 遍历数组中的所有元素,找到是否存在。

时间复杂度是 O(N^2),空间复杂度是 O(1)

function Find(target, array) {
    const rowNum = array.length;
    if (!rowNum) {
        return false;
    }
    const colNum = array[0].length;
    for (let i = 0; i < rowNum; i++) {
        for (let j = 0; j < colNum; j++) {
            if (array[i][j] === target) return true;
        }
    }

    return false;
}

解法 2:观察数组规律

按照题目要求,数组的特点是:每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。考虑以下数组:

1 2 3

4 5 6

7 8 9

在其中寻找 5 是否存在。过程如下:

从右上角开始遍历当前元素小于目标元素(3 < 5),根据数组特点,当前行中最大元素也小于目标元素,因此进入下一行当前元素大于目标元素(6 > 5),根据数组特点,行数不变,尝试向前一列查找找到 5

时间复杂度是 O(M+N),空间复杂度是 O(1)。其中 M 和 N 分别代表行数和列数。

function Find(target, array) {
    const rowNum = array.length;
    if (!rowNum) {
        return false;
    }
    const colNum = array[0].length;
    if (!colNum) {
        return false;
    }

    let row = 0,
        col = colNum - 1;
    while (row < rowNum && col >= 0) {
        if (array[row][col] === target) {
            return true;
        } else if (array[row][col] > target) {
            --col;
        } else {
            ++row;
        }
    }

    return false;
}

两个数组的交集和并集

  1. 对 Array 进行扩展
//数组功能扩展
//数组迭代函数
Array.prototype.each = function(fn){
 fn = fn || Function.K;
 var a = [];
 var args = Array.prototype.slice.call(arguments, 1);
 for(var i = 0; i < this.length; i++){
 var res = fn.apply(this,[this[i],i].concat(args));
 if(res != null) a.push(res);
 }
 return a;
};
//数组是否包含指定元素
Array.prototype.contains = function(suArr){
 for(var i = 0; i < this.length; i ++){
 	if(this[i] == suArr){
	 	return true;
	 }
 }
 return false;
}
//不重复元素构成的数组
Array.prototype.uniquelize = function(){
 var ra = new Array();
 for(var i = 0; i < this.length; i ++){
	 if(!ra.contains(this[i])){
	 	ra.push(this[i]);
	 }
 }
 return ra;
};
//两个数组的交集
Array.intersect = function(a, b){
 return a.uniquelize().each(function(o){return b.contains(o) ? o : null});
};
//两个数组的差集
Array.minus = function(a, b){
 return a.uniquelize().each(function(o){return b.contains(o) ? null : o});
};
//两个数组的补集
Array.complement = function(a, b){
 return Array.minus(Array.union(a, b),Array.intersect(a, b));
};
//两个数组并集
Array.union = function(a, b){
 return a.concat(b).uniquelize();
};

2 filter 它用于把Array的某些元素过滤掉,然后返回剩下的元素。

和map()类似,Array的filter()也接收一个函数。和map()不同的是,filter()把传入的函数依次作用于每个元素,然后根据返回值是true还是false决定保留还是丢弃该元素。

回调函数

filter()接收的回调函数,其实可以有多个参数。通常我们仅使用第一个参数,表示Array的某个元素。回调函数还可以接收另外两个参数,表示元素的位置和数组本身:

var a = [1,2,3,4,5]
var b = [2,4,6,8,10]
//交集
var intersect = a.filter(function(v){ return b.indexOf(v) > -1 })
//差集
var minus = a.filter(function(v){ return b.indexOf(v) == -1 })
//补集
var complement = a.filter(function(v){ return !(b.indexOf(v) > -1) })
  .concat(b.filter(function(v){ return !(a.indexOf(v) > -1)}))
//并集
var unionSet = a.concat(b.filter(function(v){ return !(a.indexOf(v) > -1)}));

console.log("a与b的交集:", intersect);
console.log("a与b的差集:", minus);
console.log("a与b的补集:", complement);
console.log("a与b的并集:", unionSet);

合并两个数组并排序

  • 合并两个数组并排序
var a =[2,5,8,9];
var b=[7,9,7,9]
var c = a.concat(b).sort(function(a,b){return a-b }//升序
//回调函数里面返回值如果是:参数1-参数2;升序;  

  • 两个有序数组,合并后重新排序js实现

利用两个数组已经排好序这个条件,设置两个指针,分别指向两个数组

当其中一个小于另外一个就将小的数push到新数组中,后移指针

相等的话则全push到新数组中,两个指针全后移

直到一个指针指到数组结尾,将另一个数组直接加入到新数组中即可

let sortArr = (arr1, arr2) => {
  let [i,j] = [0,0];
  let newArr = [];
 
  while (i < arr1.length && j < arr2.length) {
    if (arr1[i] < arr2[j]) {
      newArr.push(arr1[i]);
      i++;
    } else if (arr1[i] > arr2[j]) {
      newArr.push(arr2[j]);
      j++;
    } else if (arr1[i] === arr2[j]) {
      newArr.push(arr1[i]);
      newArr.push(arr2[j]);
      i++, j++;
    }
  }
 
  // 将指针未移到末尾的部分取出,拼到新数组后面
  if (i < arr1.length) {
    return newArr.concat(arr1.splice(i));
  } else if (j < arr2.length) {
    return newArr.concat(arr2.splice(j));
  } else {
    return newArr;
  }
};
 
let arr1 = [1, 2, 3, 4, 6, 9, 11];
let arr2 = [2, 4, 6, 8, 12, 19, 33, 45];
console.log(sortArr(arr1, arr2));

数组的反转

截屏2021-06-13 10.12.41.png

截屏2021-06-13 10.12.23.png

//反转数组
function reverse(arr) {
  for (let i = 0; i < arr.length/2; i++) {
    [arr[i],arr[arr.length-1-i]]=[arr[arr.length-1-i],arr[i]]
  }
  return arr
}

函数柯里化 实现sum函数(闭包):

sum(1)(2)() 输出 3; sum(1)(2)(3)(4)() 输出10;

指的是将一个接受多个参数的函数 变为 接受一个参数返回一个函数的固定形式,这样便于再次调用,例如 实现add(1)(2)(3)(4)=10; 、 add(1)(1,2,3)(2)=9;

function sum() {
  const _args = [...arguments];
  function fn() {
    _args.push(...arguments);
    return fn;
  }
  fn.toString = function() {
    return _args.reduce((sum, cur) => sum + cur);
  }
  return fn;
}

解析url参数


function GetRequest(url) {
  if (typeof url == "undefined") {
    var urla = decodeURI(location.search)//获取url中"?"符后的字符串
  } else {
    var urla  = "?" + url.split("?")[1]
  }
  var request = new Object()
  if (urla.indexOf("?") != -1){
    var str = urla.substr(1)
    strs = str.split("&")
    for (let i = 0; i < strs.length; i++) {
      request[strs[i].split("=")[0]] = decodeURI(strs[i].split("=")[1])
    }
  }
  return request
}

var parms_2 = GetRequest('http://htmlJsTest/getrequest.html?uid=admin&rid=1&fid=2&name=小明');
console.log(parms_2); // {"uid":"admin","rid":"1","fid":"2","name":"小明"}

字符串数组中的最长公共前缀


function longestCommonPrefix(string) {
  if (!string.length){
    return ''
  }
  if (string.length === 1) {
    return string[0]
  }
  string.sort()
  let first = string[0]
  let end = string[string.length -1]
  let result = ''
  for (let i = 0; i < first.length; i++) {
    if (first[i] === end[i]) {
      result += first[i]
    } else {
      break
    }
  }
  return result
}

var a = ["abca","abc","abca","abc","abcc"]
console.log(longestCommonPrefix(a))

找最大值(5种方法)

var arr = [23,5,6,2,1]
// ES6
var a = Math.max(...arr)

// ES5
var a = Math.max.apply(null,arr)

//sort
a= arr.sort((num1,num2) =>{
  return num1 - num2 >0
})
 arr[0]

//reduce
a = arr.reduce((num1,num2 )=>{
  return num1>num2 ? num1:num2
})

// for
let max= arr[0]
for (let i = 0; i < arr.length -1; i++) {
  max = max > arr[i+1]?max:arr[i+1]
}

console.log(a)

1.二维数组中的查找(Write Code

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

  • 解释:

题目的条件就是说判断二维数组中是否含有该整数。1.我们可以把数组拍平,一层循环遍历。2.也可以直接双层循环暴力解法,时间复杂度O(n^2)。当然上面两种方法都可以通过。3.但是这个题规定了每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。我们从右上角开始,如果一行中最大的值(最后一个)小于目标值,就没必要遍历这行中其他的值啦,这样可以少遍历很多值。

  • 代码实现:

1.数组拍平循环

function Find(target, array) {
    // 定义一个flag标志
    var flag;
    // 将二维数组拍平
    while (array.some(item => Array.isArray(item))) {
        array.forEach(item => {
            array = [].concat(...array)
        })
    }
    // 循环数组,如果找到相等的,赋值给flag
    array.forEach(item => {
        if (item == target) {
            flag = target;
        }
    })
    // 判断数组中是否含有该整数(flag有值的话,代表有)
    if (flag) return true;
    return false;
}
复制代码

2.双层循环

function Find(target, array) {
    // 定义len1表示二维数组中的一维数组的个数
    let len1 = array.length; 
    // 定义len2表示每个一维数组的长度(长度相同)
    let len2 = array[0].length; 
    // 双层循环
    for (var i = 0; i < len1; i++) {
        for (var j = 0; j < len2; j++) {
            if (array[i][j] === target) {
                return true;
            }
        }
    }
    return false;
}
复制代码

3.简单一次循环

function Find(target, array) {
    // 定义len1表示二维数组中的一维数组的个数
    let len1 = array.length;
    // 定义len2表示每个一维数组的长度(长度相同)
    let len2 = array[0].length;
    // 定义i来控制行,j控制列
    let i = 0,j = len2 - 1;
    while (i < len1 && j >= 0) {
        // 如果相等,返回true
        if (array[i][j] == target) {
            return true;
        } else if (array[i][j] > target) {// 如果大于目标值,沿当前行往前走
            j--;
        } else {
            // 如果小于目标值,说明这一行都小,需要换到下一行
            i++;
        }
    }
    return false;
}
复制代码

2.替换空格(Write Code

  • 题目描述:

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。

  • 解释:

这个第一感觉就是直接用正则表达式将空格替换即可。或者使用str.split(" ").join("%20")。循环遍历也是可以的。

  • 代码实现:
function replaceSpace(str) {
    // 正则表达式'\s'匹配空格用%20替换即可
    return str.replace(/\s/g, '%20');
}
复制代码

6.旋转数组的最小数字(Write Code

  • 题目描述:

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

  • 解释:

拿到这个题我的第一个想法是直接sort从小到大排序,然后取出第一项就是最小值,这种方法确实也通过啦,但是可能稍微有些偏离题意。题中显示本题知识点:二分。于是就用二分法从新实现了一下。

题中输入一个非递减排序的数组的“一个旋转”。例如:非递减数组[2,3,5,6,7]。旋转完 [3,5,6,7,2],[5,6,7,2,3],[6,7,2,3,5],[7,2,3,5,6]。这四个都可以当作本题的输入。我们发现:旋转后的数组可以划分为两个有序的子区间,前面区间的元素都大于等于后面的元素,我们要找的是两个子区间的分界。前面一定比后面的大,所以我们用二分法看看中间值是在大的部分,还是在小的部分,如果在大的部分就抛弃掉中间值左边的,在小的部分就抛弃掉中间值右边的,抛弃掉的这部分一定大于等于最小值,不符合所以舍去,看代码。

  • 代码实现:
function minNumberInRotateArray(rotateArray) {
    // 定义数组的左右边界
    let left = 0,right = rotateArray.length - 1;
    while (left < right) {
        // 为了防止溢出问题,我们用left+(right-left)/2来取出中间值
        const mid = Math.floor(left + (right - left) / 2);
        // 如果中间值大于最右边的,说明中间值在大的区间部分,舍去中间值左边的
        if (rotateArray[mid] > rotateArray[right]) {
            left = mid + 1;
        } // 如果中间值小于最右边的,说明中间值在小的区间部分,舍去中间值右边的
        else if (rotateArray[mid] < rotateArray[right]) {
            right = mid;
        } // 如果相等,就right--或者left++,从新更新区间
         else {
            right--;
        }
    }
    // 此时left就是最小值
    return rotateArray[left];
}

11.二进制中1的个数(Write Code

  • 题目描述:

输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。

例如:输入:10 输出:2

  • 解释:

与、或和异或的运算规律:

与(&)或(|)异或(^)
0 & 0 = 00 | 0 = 00 ^ 0 = 0
0 & 1 = 00 | 1 = 11 ^ 0 = 1
1 & 0 = 01 | 0 = 10 ^ 1 = 1
1 & 1 = 11 | 1 = 11 ^ 1 = 0

比如n = 10的二进制是1010,n-1 = 9的二进制为1001,n&(n-1)= 1000。 可以看出n的最低位的1被与没啦。依次n&(n-1)的往下走,每次都可以与没最低位的1。所以,要消除整数n最低位的1,可以使用 n = n & (n-1),直至为0。

  • 代码实现:
function NumberOf1(n)
{
    // 定义一个count用于计数
     let count = 0;
     // 当n不为0的时候,一直循环
     while(n!=0){
     	// count+1
        count++
        // 开始与没最低位的1
        n = n&(n-1);
     }
     // 返回次数
     return count
}
复制代码

时间差

function getDiffDate(date) {
  let date1 = new Date(date)
  let date2 = new Date()
  const diffDate = (date2.getTime()-date1.getTime())/(24*60*60*1000)
  return Math.floor(diffDate)
}
console.log(getDiffDate('2021-09-11'))

2 Array.prototype.XXX

map,计算,返回新数组

const arr = [1,2,3,4]
const map1 = arr.map(x=> x*2)
console.log(map1)//[ 2, 4, 6, 8 ]

原理:

function map(arr,callback) {
  // 1 申明一个空数组
  let res = []
  //2 遍历数组
  for (let i = 0; i <arr.length ; i++) {
    //3 执行回调
    res.push(callback(arr[i],i))
  }
  // 4 返回结果
  return res
}


const arr = [1,2,3,4]

// map 函数的调用
const res = map(arr,(item,index)=> {
  return item * 2
})

console.log(res)//[ 2, 4, 6, 8 ]

reduce 回调、暂存器、返回

const arr = [1,2,3,4]

let res = arr.reduce(function (res,value) {
//这里res 是暂存器,默认是0开始
  return res + value
},0)

console.log(res)//10

封装原理:

function reduce(arr,callback,initValue) {
  //申明变量
  let res = initValue
  //循环遍历
  for (let i = 0; i <arr.length ; i++) {
    //执行回调,赋值给res,即暂存器
    res = callback(res, arr[i])
  }
  //返回结果
  return res
}

const arr = [1,2,3,4]
// reduce 函数的调用
let res = reduce(arr, function (res,value) {
  return res + value
},10)

console.log(res)//20

filter 过滤,返回新数组

const arr = [1,2,3,4]
// 返回原数组中奇数
const res = arr.filter(item=> item % 2 === 1)

console.log(res)//[ 1, 3 ]

原理:

function filter(arr,callback) {
  // 1 申明一个空数组
  let res = []
  //2 遍历数组
  for (let i = 0; i <arr.length ; i++) {
    //3 执行回调
    let a = (callback(arr[i],i))
    //4 判断回调函数是否是真
    if (a) {
      res.push(arr[i])
    }
  }
  // 4 返回结果
  return res
}


const arr = [1,2,3,4]
// 返回原数组中奇数
const res = filter(arr,item=> item % 2 === 1)

console.log(res)//[ 1, 3 ]

every 每一个满足则true,否则,false

const arr = [1,2,3,4]
const res = arr.every(item=> item > 2)
console.log(res)//false

原理:

function every(arr,callback) {
  //1 遍历数组
  for (let i = 0; i <arr.length ; i++) {
    //2 执行回调
    let res =callback(arr[i],i)
    //3 判断回调条件是否满足(逆向判断)
    if (!res){
      //返回false
      return false
    }
  }
  // 4 满足判断条件,返回true
  return true
}


const arr = [1,2,3,4]

const res = every(arr,(item,index)=> {
  return item > 2
})
console.log(res)//false

some 有一个满足则true,否则,false

const arr = [1,2,3,4]
const res = arr.some(item=> item > 2)
console.log(res)//true

原理:

function some(arr,callback) {
  //1 遍历数组
  for (let i = 0; i <arr.length ; i++) {
    //2 执行回调
    let res =callback(arr[i],i)
    //3 判断回调条件是否有一个满足
    if (res){
      //返回true
      return true
    }
  }
  // 4 一个都满足判断条件,返回 false
  return false
}


const arr = [1,2,3,4]

const res = some(arr,(item,index)=> {
  return item > 2
})
console.log(res)//true

find 找到数组中第一个true的值,否则undefined

const arr = [1,2,3,4]
const res = arr.find( item=> item > 2)
console.log(res)//3

原理

function find(arr,callback) {
  //1 遍历数组
  for (let i = 0; i <arr.length ; i++) {
    //2 执行回调
    let res =callback(arr[i],i)
    //3 判断
    if (res){
      //返回当前正在遍历的元素
      return arr[i]
    }
  }
  // 4 没有满足判断条件,返回undefined
  return undefined
}


const arr = [1,2,3,4]

const res = find(arr,(item,index)=> {
  return item > 2
})
console.log(res)//3

findIndex 与find类似,返回为true的索引,否则返回-1

const arr = [1,7,2,9,4]
const res = arr.findIndex(item=> item > 6)
console.log(res)//1

原理:

function findIndex(arr,callback) {
  //1 遍历数组
  for (let i = 0; i <arr.length ; i++) {
    //2 执行回调
    let res =callback(arr[i],i)
    //3 判断
    if (res){
      //返回当前正在遍历的元素下标
      return i
    }
  }
  // 4 没有满足判断条件,返回-1
  return -1
}


const arr = [1,7,2,9,4]

const res = findIndex(arr,(item,index)=> {
  return item > 2
})
console.log(res)//1

3 函数相关

call ,改变this的指向

function call(Fn,obj,...args) {
  //4 判断
  if (obj === undefined || obj === null){
    obj = globalThis//全局对象
  }
  //1 为obj 添加临时方法
  obj.temp = Fn//this 指向都是一样的
  // 2 调用temp方法
  let res = obj.temp(...args)
  //3 删除temp方法
  delete obj.temp
  // 返回执行结果
  return res
}


//申明一个函数
function add(a,b) {
  console.log(this)
  return a + b + this.c
}
//申明一个对象
let obj = {
  c:200
}
//添加全局属性
// window.c = 123

//执行call函数
console.log(call(add,obj,10,20))
// console.log(call(add,null,1,2))

apply,改变this的指向,传入参数是数组

function apply(Fn,obj,args) {
  //4 判断
  if (obj === undefined || obj === null){
    obj = globalThis//全局对象
  }
  //1 为obj 添加临时方法
  obj.temp = Fn
  // 2 调用temp方法
  let res = obj.temp(...args)
  //3 删除temp方法
  delete obj.temp
  // 返回执行结果
  return res
}
//申明一个函数
function add(a,b) {
  console.log(this)
  return a + b + this.c
}
//申明一个对象
let obj = {
  c:200
}
//添加全局属性
// window.c = 123

//执行函数
console.log(apply(add,obj,[10,20]))
// console.log(apply(add,null,[1,2]))

bind

类似call, bind不执行函数,创建新函数

function bind(Fn,obj,...args) {
  // 返回新函数
  return function (...args2) {
    //执行call 函数
    return call(Fn, obj,...args,...args2)
  }
}

//申明一个函数
function add(a,b) {
  console.log(this)
  return a + b + this.c
}
//申明一个对象
let obj = {
  c:200
}
//添加全局属性
// window.c = 123

//执行函数

let fn = bind(add,obj,3,4)
console.log(fn(1,2))

Promise 方法

静态方法: Promise .resolve() Promise.reject() Promise.all() Promise.race() (1) Promise.resolve()

static resolve(value) {
  if (value instanceof Promise) {
    // 如果是Promise实例,直接返回
    return value;
  } else {
    // 如果不是Promise实例,返回一个新的Promise对象,状态为FULFILLED
    return new Promise((resolve, reject) => resolve(value));
  }
}

(2) Promise.reject()

static reject(reason) {
  return new Promise((resolve, reject) => {
    reject(reason);
  })
}

(3) promiss.all Promise.all是支持链式调用的,本质上就是返回了一个Promise实例,通过resolve和reject来改变实例状态。

Promise.all可以将多个Promise实例包装成一个新的Promise实例。同时,成功和失败的返回值是不同的,成功的时候返回的是一个结果数组,而失败的时候则返回最先被reject失败状态的值。

【返回一个promise对象,只有当所有promise都成功时返回的promise状态才成功,需要注意的点是:

1所有的promise状态变为FULFILLED,返回的promise状态才变为FULFILLED。

2一个promise状态变为REJECTED,返回的promise状态就变为REJECTED。

3数组成员不一定都是promise,需要使用Promise.resolve()处理。】

Promise.myAll = function(promiseArr) {
  return new Promise((resolve, reject) => {
    const ans = [];
    let index = 0;
    for (let i = 0; i < promiseArr.length; i++) {
      promiseArr[i]
      .then(res => {
        ans[i] = res;
        index++;
        // 如果全部执行完,返回promise的状态就可以改变了
        if (index === promiseArr.length) {
          resolve(ans);
        }
      })
      .catch(err => reject(err));
    }
  })
}

(4) promiss.race Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。

Promise.race = function(promiseArr) {
  return new Promise((resolve, reject) => {
    promiseArr.forEach(p => {
      // 如果不是Promise实例需要转化为Promise实例
      Promise.resolve(p).then(
        val => resolve(val),
        err => reject(err),
      )
    })
  })
}

4 递归

1. 斐波那契

function Fibonacci(n) {
  if (n==1 || n == 2){
    return 1
  }
  return Fibonacci(n-1)+Fibonacci(n-2)
}

console.log(Fibonacci(30))
//非递归方法
function Fibonacci(num){
    var n1 = 1, n2 = 1 , n= 1
    for(var i = 2; i < num; i++){
      n = n1 + n2
      n1 = n2
      n2 = n
 }
 return n
}

8.跳台阶(Write Code

  • 题目描述:

一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。

  • 解释:

青蛙跳台阶这种经典题,和斐波那契数列一样,一般可以使用递归来解答。但是递归的解法可能会爆栈,所以为了以防爆栈,可以定义cache进行缓存。(也可以用循环来代替递归的方式正向求解)

  • 代码实现:
// 为了以防爆栈,定义cache进行缓存
let cache = {}

function jumpFloor(number) {
    // 当为1或者2时,直接返回number(递归出口)
    if (number === 1 || number === 2) {
        return number;
    }
    // 如果已经计算过了,直接从缓存中取出返回即可
    if (cache[number]) {
        return cache[number]
    }
    // 递归调用,进行缓存
    cache[number] = jumpFloor(number - 1) + jumpFloor(number - 2);
    // 返回当前结果
    return cache[number];
}

9.变态跳台阶(Write Code

  • 题目描述:

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

  • 解释:

变态跳台阶和跳台阶这两个问题本质上是一个问题。区别在于跳台阶问题的第10层的算法为第9层+第8层,而变态跳台阶的第10层的算法为第9层+第8层+第7层+ ··· +第1层+1。我们简单写一下是这样的:1 2 4 8 16 32 64 …

  • 代码实现:
function jumpFloorII(number)
{
    if (number == 1) {
      return 1;
    }
    return jumpFloorII(number - 1) * 2;
}

10.矩阵覆盖(Write Code

  • 题目描述:

我们可以用2 * 1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2 * 1的小矩形无重叠地覆盖一个2 * n的大矩形,总共有多少种方法?

比如n=3时,2*3的矩形块有3种覆盖方法。

  • 解释:

这个题还是斐波那契数列的一个变形。当我们第一次看见这个题无从下手的时候,感觉还是找好规律最重要。拿2 * n的大矩形,和n个2 * 1的小矩形来举例。当n小于2的时候只有n中摆放方法。当n>2的时候,可以看出等于前两次的相加。

  • 代码实现:
function rectCover(number) {
	// 计算特殊情况
    if(number == 0 || number == 1 || number == 2) return number;
    // 定义最初始的值
    let m = 1;
    let n = 2;
    // 一直循环,更新n和m
    while (--number){
      n = m + n;
      m = n - m;
    }
    // 返回结果
    return m;
  }

5.用两个栈实现队列(Write Code

  • 题目描述:

用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

  • 解释:

队列和栈二者都可以用数组模拟,区别就是栈是先进后出,队列是先进先出。我们定义两个栈stack1和stack2,在stack1中正常push元素,当从数组中出元素的时候需要用stack2栈来辅助:将stack1中的元素弹出,push进stack2中,在将元素从stack2中弹出就可以实现队列出的操作。要坚持,出队操作只能依赖 stack2 来完成。

  • 代码实现:
//定义两个栈
var stack1 = [],
    stack2 = [];
//push的话,正常push
function push(node) {
    stack1.push(node);
}
//模拟队列的pop,是需要pop出栈的最底部元素,
//所以把stack1中的队列先pop出来,在push进stack2队列,就可以颠倒过来啦
function pop() {
    // 当stack2里面没有值的时候
    if (stack2.length <= 0) {
        //当stack1里面有值的时候,就循环
        while (stack1.length !== 0) {
            //将stack1中的值pop出来,push进stack2中
            stack2.push(stack1.pop());
        }
    }
    //模拟队列的pop出来的值,都要从stack2中pop出来
    return stack2.pop();
}

20.包含min函数的栈(Write Code

  • 题目描述:

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。

  • 解释:

题中说,找到栈中所含的最小元素,正常用一个栈来实现的话需要循环一遍找出最小值,时间复杂度为o(n),但是由于要求时间复杂度应为O(1),所以需要借用两个栈来实现。stack1是正常维持的栈,stack2作为它的辅助栈,我们维持stack2的最后一个节点是栈中的最小值,也就是维持他是一个递减的栈,具体实现如下。

  • 代码实现:
// 定义两个栈
var stack1 = [],stack2 = [];

function push(node) {
    //把值push进stack1中
    stack1.push(node);
    //如果stack2为空或者此时节点小于stack2最小值(也就是最后一个节点)把它push进stack2
    //我们维持stack2的最后一个节点是最小值,也就是一个递减的栈。
    if (stack2.length === 0 || node < stack2[stack2.length - 1]) {
        stack2.push(node);
    }
}

function pop() {
    //在stack1中直接pop出去。如果pop的这个值,恰好是stack2中的最小值,那么stack2也需要pop出去。
    if (stack1.pop() === stack2[stack2.length - 1]) {
        stack2.pop();
    }
}

function top() {
    //取出stack1中的最后一个值即可
    return stack1[stack1.length - 1];
}

function min() {
    //因为我们维持的stack2是一个递减的栈,且最后一个值就是最小值所以取出即可。
    return stack2[stack2.length - 1];
}

21.栈的压入、弹出序列(Write Code

  • 题目描述:

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列压到哪一个啦,只要里面有值他就可以弹出。所以定义辅助栈,对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

  • 解释:

判断一个序列是否为一个栈的弹出顺序。这里面的问题就是栈可以在任意时间弹出,不管压栈序列压到哪一个啦,只要里面有值他就可以弹出。所以定义辅助栈,遍历入栈数组,将每个元素依次压入辅助栈,每遍历一个元素,就和出栈数组的第一个元素比较,如果相等则代表这个是目前弹出的元素,将刚刚压入辅助栈的元素弹出,更新出栈数组的第一个元素(往后移一位),继续遍历,最后看辅助栈的长度是否为0。

  • 代码实现:
function IsPopOrder(pushV, popV) {
    // 如果pushV或popV为空的话,直接返回
    if (!pushV || !popV || pushV.length === 0 || popV.length === 0) {
        return
    }
    // 定义一个辅助栈
    let stack = [];
    // 定义一个索引,用于在popV中移动
    let index = 0;
    // 循环栈的压入顺序的数组
    for (let i = 0; i < pushV.length; i++) {
        // 将值push进辅助栈中
        stack.push(pushV[i]);
        // 当辅助栈不为空,且最后一个值等于popV的第一个值的时候
        while (stack.length !== 0 && stack[stack.length - 1] === popV[index]) {
            // 将辅助栈中的最后一个值弹出
            stack.pop();
            // index+1,在popV中向后移动一个
            index++;
        }
    }
    // 如果辅助栈为空,说明是OK的
    return stack.length === 0;
}

链表

15.反转链表(Write Code

  • 题目描述:

输入一个链表,反转链表后,输出新链表的表头。

  • 解释:

反转链表就是在操作链表的顺序,就是改变节点之间指针的关系。定义指针:pre(前驱)、cur(当前节点)、next(后继),我们改变当前节点的next指针,指向前驱节点(cur.next = pre),所有指针的都这样改变,一步一步往下走即可。

  • 代码实现:
function ReverseList(pHead) {
    // 定义一个节点为null,用作前驱节点
    let pre = null;
    //定义指针,指向当前节点
    let cur = pHead;
    //循环链表
    while (cur) {
        //定义next,存储当前节点的后继节点
        let next = cur.next;
        //当前指针的下一个节点指向前驱节点(反转)
        cur.next = pre;
        //往下走,pre走到当前节点
        pre = cur;
        //往下走,当前节点走到上面存储的后继节点
        cur = next;
    }
    //最后pre节点指向原链表的最后一个节点
    return pre;
}

3.从尾到头打印链表(Write Code

  • 题目描述:

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。

  • 解释:

这个题要求从尾到头打印,可以正常循环链表,将链表的每一个节点从数组的头部压入(unshift)即可。

  • 代码实现:
function printListFromTailToHead(head) {
  // 定义指针指向头节点
  let cur = head;
  // 定义数组用来存放结果
  let result = [];
  // 循环链表,直到最后一个节点为止
  while(cur){
      // 从数组的头部压入
      result.unshift(cur.val);
      // 向下走
      cur = cur.next;
  }
  // 返回结果
  return result;
}

14.链表中倒数第k个结点(Write Code

  • 题目描述:

输入一个链表,输出该链表中倒数第k个结点。

  • 解释:

用快慢指针来求解。定义两个指针fast和solw,先让fast指针向前走K步,然后快慢指针一起走,直到fast指针走到最后一个节点,这个时候slow指针的后继节点就是这个倒数第K个节点,删除即可。

  • 代码实现:
function FindKthToTail(head, k) {
  // 判断链表为空的情况
  if (!head) return null;
  //设置第一个结点的前驱结点,保证所有的结点都能有一个前驱结点
  let dummy = new ListNode(0);
  dummy.next = head
  //定义两个指针,快慢指针
  let fast = dummy;
  let slow = dummy;
  //让快指针先走k步
  while (k !== 0 && fast != null) {
      fast = fast.next;
      k--;
  }
  //如果k>0,说明k太大,倒数第k个节点不存在
  if (k > 0 || !fast) return null;
  //快慢指针一起走,直到fast指针走到最后一个节点
  while (fast.next) {
      fast = fast.next;
      slow = slow.next;
  }
  //此时慢指针的后继节点就是倒数第k个节点
  return slow.next;
}

16.合并两个排序的链表(Write Code

  • 题目描述:

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

  • 解释:

我们定义一个指针头节点,然后依次比较两个链表当前节点值的大小,将较小的“穿”到新定义的这个指针中,一直往下走,直到都走完,返回新链表起始节点。

  • 代码实现:
function Merge(pHead1, pHead2) {
    // 定义头结点,确保链表可以被访问到
    let head = new ListNode(0);
    // 定义指针,指向头节点
    let cur = head;
    // 当两个链表都存在时进行循环
    while (pHead1 && pHead2) {
        // 如果pHead1的值较小
        if (pHead1.val < pHead2.val) {
            // 将pHead1的值作为下一个节点
            cur.next = pHead1;
            // pHead2链表向下走
            pHead1 = pHead1.next;
        } else {
            // 当pHead2值较小时,将pHead2的值作为下一个节点
            cur.next = pHead2;
            // pHead2链表向下走
            pHead2 = pHead2.next;
        }
        // 每循环一次,指针往下走一个节点
        cur = cur.next;
    }
    // 处理链表不等长的情况,哪个链表还有值就放在新链表的后面
    cur.next = (pHead1 !== null ? pHead1 : pHead2);
    // 返回新链表起始节点
    return head.next;
}

4.重建二叉树(Write Code

  • 题目描述:

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

  • 解释:

用前序遍历和中序遍历来构建二叉树。我们从前序遍历中可以得知第一个节点为当前二叉树的根节点,在中序遍历中找到当前的根节点,他们的左边的所有节点为左子树,右边的所有节点为右子树,由此一次递归下去。简单梳理一下步骤:

  • 在前序遍历中获取根节点

  • 在中序遍历中根据刚刚在前序遍历中获取的根节点找到当前二叉树的左子树和右子树

  • 递归,依次在左右子树中这样寻找,直至还原整个二叉树

  • 代码实现:

function reConstructBinaryTree(pre, vin) {
    // 当有一个长度为0时,返回null
    if (!pre.length || !vin.length) {
        return null;
    }
    // 根据前序遍历遍历的第一个节点为跟节点,创造二叉树
    const rootVal = pre[0];
    const node = new TreeNode(rootVal);

    let i; // 定义i,计算出根节点在中序遍历结果中的下标
    for (i = 0; i < vin.length; i++) {
        // 找出在中序遍历结果中的下标
        if (vin[i] === rootVal) {
            break;
        }
    }
    // 以下标为分割线,把前序遍历和中序遍历的结果进行分割。按照这样递归下去。例如:
    //       1
    //    2     3
    // 4     5
    // 前序:1 2 4 5 3
    // 中序:4 2 5 1 3
    // [2,4,5]为左节点,[3]为右节点(以i为分割线进行分割的)
    node.left = reConstructBinaryTree(pre.slice(1, i + 1), vin.slice(0, i));
    node.right = reConstructBinaryTree(pre.slice(i + 1), vin.slice(i + 1));
    // 返回结果
    return node;
}

17.树的子结构(Write Code

  • 题目描述:

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

  • 解释:

判断B是不是A的子结构,首先B的头节点,要和A中的某个节点相等,且这个节点的左子树和右子树也要和B的都保持一样,我们用递归来进行实现。

  • 代码实现:
function HasSubtree(pRoot1, pRoot2) {
    //如果有一个树为空的话,返回false
    if (!pRoot1 || !pRoot2) {
        return false;
    }
    //定义函数判断是否为子结构
    function same(root1, root2) {
        //先判断root2再判断root1,防止都为空的时候(递归的出口)
        //如果root2为空了说明是root1的子结构
        if (!root2) return true;
        //如果root1为空了说明不对,返回false
        if (!root1) return false;
        //判断当前两个节点是否相等,递归当前节点的左节点,递归当前节点的右节点
        return root1.val == root2.val && same(root1.left, root2.left) && same(root1.right, root2.right)
    }
    //将传入的树传入same函数进行对比 || “大树”的左节点和小树对比 || “大树”的右节点和小树对比 (有一个OK即可)
    return same(pRoot1, pRoot2) || HasSubtree(pRoot1.left, pRoot2) || HasSubtree(pRoot1.right, pRoot2)
}

18.二叉树的镜像(Write Code

  • 题目描述:

操作给定的二叉树,将其变换为源二叉树的镜像。

  • 解释:

二叉树的镜像实际为反转二叉树。将每一个二叉树的左右子树都发生了互换。用递归的方式遍历树的每一个节点,互换左右子树。

  • 代码实现:
function Mirror(root) {
    //递归的出口,如果不存在了,到底了,返回null
    if (!root) {
        return
    }
    //递归交换左孩子的子结点
    let left = Mirror(root.left);
    //递归交换右孩子的子结点
    let right = Mirror(root.right);
    //交换当前遍历的两个节点
    root.left = right;
    root.right = left;
    return root;
}

22.从上往下打印二叉树(Write Code

  • 题目描述:

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

  • 解释:

二叉树的层序遍历,一般用BFS+队列直接可以解决。(有一些层序遍历的变型题,也是BFS+队列+...来求得的)

  • 代码实现:
//BFS+队列
function PrintFromTopToBottom(root) {
    //处理边界,判断为空直接返回
    if (!root) return [];
    //定义存储结果的数组
    let result = [];
    //定义一个队列
    let queue = [];
    //将根节点push进去
    queue.push(root);
    // 当队列不为空时,反复执行
    while (queue.length) {
        //缓存长度
        let len = queue.length;
        for (let i = 0; i < len; i++) {
            //将第一个弹出
            let cur = queue.shift();
            //将当前节点推入结果数组
            result.push(cur.val);
            //如果有左节点,推到后面
            if (cur.left) {
                queue.push(cur.left);
            }
            //如果有右节点,推到后面
            if (cur.right) {
                queue.push(cur.right);
            }
        }
    }
    //返回结果数组
    return result;
}

19.顺时针打印矩阵(Write Code

  • 题目描述:

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

  • 解释:

因为是矩阵,我们取到当前矩形左上角坐标和右下角坐标,然后从大圈到小圈一层一层的循环。(这里循环时要注意一下边界的处理情况)

  • 代码实现:
function printMatrix(matrix) {
    // 空值校验
    if (!matrix.length) return []
    if (!matrix[0].length) return []

    let height = matrix.length;
    let width = matrix[0].length;
    let result = [];
    // 当前矩形左上角坐标(startX,startY),右下角坐标(endX,endY)
    let startX = 0,
        startY = 0,
        endX = width - 1,
        endY = height - 1;
    // 一层一层的循环,从大圈到小圈
    while (endX >= startX && endY >= startY) {
        for (let i = startX; i <= endX; i++) {
            result.push(matrix[startY][i]);
        }
        for (let j = startY + 1; j <= endY; j++) {
            result.push(matrix[j][endX]);
        }
        for (let i = endX - 1; i >= startX; i--) {
            if (endY === startY) return result
            result.push(matrix[endY][i]);
        }
        for (let j = endY - 1; j >= startY + 1; j--) {
            if (startX === endX) return result
            result.push(matrix[j][startX]);
        }
        startX++;
        startY++;
        endX--;
        endY--;
    }
    return result;
}