前端——算法

前言

和数据结构一样,考算法题也备受面试官的青睐,很多时候我们可以根据题目的特点,甚至 可以和面试官讨论选择适合的方法编程,通常排序和查找是面试时考查算法的重点,二分查找,归并、快排要能做到随时准确,完整的写出它们的代码。
影响代码规范性的因素:书写、布局、命名
代码的完整性:边界判断一定要加上!

数组篇:

数组的基础知识

let arr1 = [1, 2, 10, 30]
let arr2 = [3, 4, 50, 90]
let arr3 = ['banana', 'apple', 'peach']
// arr1.concat(arr2) // [ 1, 2, 10, 30, 3, 4, 50, 90 ] 合并数组
// const a = arr1.entries() // Object [Array Iterator] {} 返回一个数组迭代对象,可用for of遍历
// arr1.fill('bob')  // [ 'bob', 'bob', 'bob', 'bob' ] 使用一个固定值来填充数组,改变原数组
// const flag = arr1.every(item => { return item >= 1 }) // true 数组内每一个元素都满足>=1返回true,有一个不满足就返回flase,并不再遍历,不改变原数组
// const flag = arr1.some(item => { return item > 11 }) // true 数组内只要有一个元素满足条件就返回true,不再遍历数组,都不满足返回false,不改变原数组
// const newArr = arr1.filter(item => { return item > 6 }) // [ 10, 30 ] 返回满足判断条件的元素组成的数组,不改变原数组
// const target = arr1.find(item => { return item > 99 }) // 10 返回满足条件的第一个元素,找到目标元素便不再遍历,无就返回undefind,不改变原数组
// const targetIndex = arr1.findIndex(item => {return item >= 10}) // 2 返回满足条件的元素的索引,无就返回-1
// const newArr = arr1.forEach(item =>{ if(item===2) return ;console.log('执行')}) // 执行 执行 执行 将数组内的每个元素都执行一次函数,这里的return相当于continue,跳出循环要使用try catch实现,改变元素组,没有返回值
try{
        array.forEach(function(item,index){
                if(item == "third"){
                        throw new Error("ending");//报错,就跳出循环
                }else{
                        console.log(item);
                }
        })
}catch(e){
        if(e.message == "ending"){
                console.log("结束了") ;
        }
}
// const newArr2 = Array.from('HBGSV') // [ 'H', 'B', 'G', 'S', 'V' ]将一个字符串转化为数组
// const newArr = new Set(arr1)
// const newArr2 = Array.from(newArr, x => x * 10) // [ 10, 20, 100, 300 ]将map结构转化为数组结构,第二个参数是一个函数,数组内的每一个数都会执行它
// const flag = arr1.includes(2) // true 看数组内是否包含2这个元素,返回布尔值
// const flag = arr1.indexOf(10) // 2 返回指定元素在数组内的索引位置,无就返回-1
// const flag = Array.isArray(arr1) // true 判断一个对象是否是一个数组
// const string1 = arr1.join() // 1,2,10,30 将一个数组转化为一个字符串,参数不写默认以逗号分隔
// arr1.keys() // Object [Array Iterator] {}返回一个实现Iterator接口的对象,需要调用.next() // { value: 0, done: false } .value: // 0
// const index = arr1.lastIndexOf(10) // 2 返回目标元素在数组里出现的最后一个位置
// const newArr = arr1.map(item => { return item * 2 }) // [ 2, 4, 20, 60 ] 返回处理过的数组,不改变原数组
// const popRes = arr1.pop() // 30 删除数组最后一个元素,并返回被删除的元素
// const newArrLength = arr1.push(1,2) // 6 向数组的末尾添加元素,并返回新数组的长度
// const arrTotal = arr1.reduce((total, current) => { return total + current }, 0) // 43 返回数组内所有元素计算的结果(从左到右) 第一个参数是一个函数,函数有两个参数,分别是:上一次相加的结果和当前正要加的值,另一个参数是相加的初始值
// const arrTotal = arr1.reduceRight((total, current) => { return total - current }, 0) // -43 功能和上一个一样,不同之处是它(从右到左)
// const newArr = arr1.reverse() // [ 30, 10, 2, 1 ] 颠倒数组中的顺序,返回颠倒顺序的数组,并改变原数组!!
// const newArr = arr3.sort() // [ 'apple', 'banana', 'peach' ] 对数组内的元素(字母)进行升序排序,但是数字排序会出错(下一个),返回排好顺序的数组,并改变原数组!!
// const newArr = arr1.sort((a, b) => { return a - b }) // [ 1, 2, 10, 30 ] 对数组内的数字执行排序时要传入一个函数,a-b为升序,b-a为降序,返回排好顺序的数组,并改变原数组!!
// const shiftRes = arr1.shift() // 1 删除并返回数组的第一个元素 改变原数组
// const newArrLength = arr1.unshift(99) // 5 向数组的开头添加元素,返回数组的新长度 改变原数组
// const newArr = arr1.slice(0, 2) // [ 1, 2 ] 两个参数分别是切的开始位置和结束位置,返回由选定的元素组成的新数组,不改变原数组
// array.splice(index,howmany,item1,.....,itemX) // index:删除或添加函数的位置;howmany:从index位置删除howmany个元素(必须),不写就直接删除index之后的所有元素(非必须);item1,.....:为添加的元素,不写就不添加(非必须),函数返回被删除的元素
// const newArr = arr1.splice(2,0,99) // [] 返回值为删除的元素所组成的数组,这里我们从数组为2的位置插入(删除)元素,删除0个元素,添加99这个元素,改变原素组!!
// const newString = arr1.toString() // 1,2,10,30 将数组转化为字符串,以逗号分隔数组元素,不改变原数组
// const newString = arr1.valueOf() // 返回元素本身[ 1, 2, 10, 30 ],运算是先调用valueOf再调用toString,日期对象的话会返回1970 年 1 月 1 日到当前对象的毫秒数

1、找出数组内重复的的数字

问题描述:找出数组内重复的的数字,有便返回重复的值,无便返回-1,例如[3,4,5,3,7,9,4],返回3或4均正确。
思路:利用对象的属性名是唯一的

function getRes() {
  const arr = [3, 4, 5, 3, 7, 9, 4]
  const len =  arr.length
  const obj = {}
  let resArr = []
  if (Array.isArray(arr) && len > 0) {
    for (let i = 0; i < len; i++) { 
      if (obj[arr[i]] === undefined) {
        obj[arr[i]] = 1
      }
      else {
        obj[arr[i]]++
      }
    }

    for (let key in obj) {
      if (obj[key] >= 2) {
        resArr.push(key)
      }
    }

    return resArr.length === 0 ? -1 : resArr[0]
  }
  else {
    return -1
  }
}

console.log(getRes()) // 3

2、构建乘积数组

问题描述:给定一个数组A[0,1,…,n-1],请构建一个数组B[0,1,…,n-1],
其中B中的元素B[i]=A[0]A[1]…*A[i-1]A[i+1]A[n-1](说白了:除掉i之后,数组[i]左边右边的数的乘积)。
不能使用除法。(注意:规定B[0] = A[1] * A[2] * … * A[n-1],
B[n-1] = A[0] * A[1] * … * A[n-2];)
对于A长度为1的情况,B无意义,故而无法构建,因此该情况不会存在。

思路:先将前i个数的乘积求出来(前缀乘),再将后i个数的乘积求出来(后缀乘积),最后将前缀*后缀既得B[i]。


function getRes() {
  let arr = [1, 2, 3, 4, 5] // 输入数组
  
  let preArr = []  // 前缀乘积
  preArr[0] = 1
  for (let i = 1; i < arr.length; i++) {
    preArr[i] = preArr[i - 1] * arr[i - 1]
  }

  let sufArr = [] // 后缀乘积
  sufArr[arr.length - 1] = 1
  for (let j = arr.length - 2; j >= 0; j--) {
    sufArr[j] = sufArr[j + 1] * arr[j + 1]
  }

  let resArr = [] // 结果数组
  for (let i = 0; i < arr.length; i++) {
    resArr[i] = preArr[i] * sufArr[i]
  }

  return resArr
}

console.log(getRes())

3、二维数组中的查找

问题描述:在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
[
[1,2,8,9],
[2,4,9,12],
[4,7,10,13],
[6,8,11,15]
]
给定 target = 7,返回 true。

给定 target = 3,返回 false。
思路:从右上角(左下角也可以,因为这样我们没走一步都能淘汰一行或一列,但是右下角和左上角每走一步却不能淘汰一行或一列效率不高)开始查找,先往左走,遇到比target大的数就继续左走,遇到比target小的数就往下走,就能找到target,循环完还找不到就说明没有,返回return。最后ES6中有将多维数组转化为一维数组的方法,arr.flat(数组的维度),例如[[1, 2, 8, 9], [2, 4, 9, 12, [1,1,1]]]. flat( 2 ) // [1, 2, 8, 9, 2,4, 9, 12, 1, 1,1] ,对二维没有思路的话,转为一维就很简单了,如果面试官能让这样解的话。。。

function Find(target, array) {
  if (Array.isArray(array) && array.length > 0 && Array.isArray(array[0]) && array[0].length > 0) {
    let x = array.length // 获取二维数组的行数
    let y = array[0].length // 获取二维数组的列数
    // 从二维数组的右上角开始查找target
    let i = array[0].length - 1 // 列位置
    let j = 0 // 行位置
    while (i >= 0 && j <= x - 1) {
      if (target < array[i][j]) { // 如果目标值前一个数小,往左走
        i--
      }
      else if (target > array[i][j]) { // 如果目标值比下一个数大,往下走
        j++
      }
      else { // 不大也不小了,说明相等了,找到目标值,返回true
        return true
      }
    }
    return false // 循环完没找到,说明没有,return false
  }
}
const res = Find(15, [
  [1, 2, 8, 9],
  [2, 4, 9, 12],
  [4, 5, 10, 13],
  [6, 8, 11, 15]
])
console.log(res) // true

4、两数之和

问题描述:给出一个整数数组,请在数组中找出两个加起来等于目标值的数,
你给出的函数twoSum 需要返回这两个数字的下标(index1,index2),需要满足 index1 小于index2.。注意:下标是从1开始的
假设给出的数组中只存在唯一解
例如:
给出的数组为 {20, 70, 110, 150},目标值为90
输出 index1=1, index2=2

思路:最开始肯定会使用循环暴力解,两个for循环很快解出来了,但是去看最优解才知道,这道题可以使用hash表的方式解出来,每一次循环的时候都将数组中的值和索引值放到hash表中,target依次减去数组中的数,如果得到的结果恰巧hash表中已经存进去了(就是6-2 = 4 ;4在hash表中还没有将2存进去,但是下次轮询 6-4 = 2 ,2刚好在上一轮被放进去了,就有,得出结果数组 ),那么就找到了,返回hash表中的索引和当前遍历的索引即可。

function twoSum(numbers, target) {
let len =  numbers.length 
if (Array.isArray(numbers) && len > 0){
let resArr = new Map()
  let res = undefined
  for (let i = 0; i < len; i++) {
    res = target - numbers[i]
    if (resArr.has(res)) {
      return [resArr.get(res) + 1, i + 1]
    }
    resArr.set(numbers[i],i)
  }
  }
}
twoSum([3, 2, 4], 6) // [ 2, 3 ]

5、将数组中的0放到数组的末尾

问题描述:将数组中的0放到数组的末尾
例如:输入:[ 9, 10, 0, 1, 2, 2, 3, 0,12] 输出:[ 9, 10, 1, 2, 2, 3, 12, 0, 0 ]
思想:用两个指针,例如是指针A与指针B,指针A跟随for循环正常指到数组中的每一项,而指针B当指针A指的不是0才跟着往下指,一旦B指到数组中的0就停止走动,当指针A指到下一个不是0的数值时,就和指针B指到的值交换,指针A直接赋值为0,渐渐把不是0的值放在数组的前面,0放到数组的后面

function twoSum(nums) {
  let tem = 0
  for (let i = 0; i < nums.length; i++) {
    if (nums[i] !== 0) { // 当tem指到0了tem就不移动,让i++
      if (nums[tem] == 0) {
        nums[tem] = nums[i]
        nums[i] = 0
      }
      tem++ // 当指到数组中不是0的数tem才继续移动
    }
  }
  return nums
}

twoSum([9, 10, 1, 0, 2, 2, 0, 3, 12])

6、斐波那契数列

问题描述:大家都知道斐波那契数列(除了前三项,之后项都等于前两项之和,现在要求输入一个整数n,请你输出斐波那契数列的第n项
思路:可以用递归实现,优点:代码可读性高,缺点:时间复杂度和空间复杂度高,重复计算部分较多;所以改用循环加三数实现

function getRes(n) {
  if (n < 2) {
    return n
  }
  let first = 1;
  let second = 1;
  let res = 0;
  for (let i = 2; i < n; i++) {
    res = first + second
    first = second
    second = res
  }
  return res
}

6.1青蛙跳台阶问题

问题描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。
分析:
当n=1时结果为1
当n=2时结果为2
当n=3时结果为3
当n=4时结果为5
当n=5时结果为8

可以得到规律当n>=3时有 f(3) = f(2) + f(1)即f(n) = f(n-1) + f(n-2);也就是斐波那契数列

function qwJump(n) {
    if (n <= 1) {
        return 1
    }
    if (n === 2) {
        return 2
    }
    let res = 0
    let t1 = 1
    let t2 = 2
    for (let i = 3; i <= n; i++) {
        res = (t1 + t2) % 1000000007
        t1 = t2
        t2 = res
    }
    return res
}

矩形覆盖算法:
用21的小矩形去覆盖2n的大矩形,要求无重叠
f(8) = f(7)+f(6) 任然是斐波那契数列。

6.2青蛙变态跳台阶问题(动态规划)

问题描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶,也可以跳上3阶台阶…也可以跳上n阶台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。
思路

  • n=1 有1种跳法
  • n=2 有2种跳法
  • n=3 有4种跳法
  • n=4 有8种跳法
  • n=5 有16种跳法

  • 由规律可知,n = 2*(n-1),所以我们可以用到动态规划:
function jump2(n) {
    const dp = Array.from(new Array(n + 1), () => 0);
    dp[2] = 2;
    for (let i = 3; i < n + 1; i++) {
        dp[i] = 2 * dp[i - 1];
    }
    return dp[n];
}
console.log(jump2(4))  // 8

7、把数组排成最小的数

问题描述:输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组[3,32,321],则打印出这三个数字能排成的最小数字为321323。
思路:利用两个for循环,一个指向数组的前一个数,一个指向数组的后一个数,由前一个数和后一个数组成一个字符串,比较这两个字符串的大小,如果前一个比后一个大,那就利用第三变量换位置,循环结束,就得出了结果

function getRes(numbers) {
  let len = numbers.length
  let t = 0
  let str = ''
  for (let i = 0; i < len; i++) {
    for (let j = i + 1; j < len; j++) {
      let s1 = `${numbers[i]}${numbers[j]}`
      let s2 = `${numbers[j]}${numbers[i]}`
      if (parseInt(s1) > parseInt(s2)) {
        t = numbers[i]
        numbers[i] = numbers[j]
        numbers[j] = t
      }
    }
  }
  for (let i = 0; i < len; i++) {
    str += `${numbers[i]}`
  }
  return str
}

9、三数之和

问题描述:给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。
例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
思路:先将给定数组进行排序,让它从小到大排列,这样方便当前sum<target时移动left指针,sum>taget时移动right指针,三数之和(利用排序加指针)的思想和两数之和(利用hash)一样,都是利用a+b = c,c -b= a,就在数组里面找a就可以了。

function threeSum(nums) {
  let num2 = nums.sort((a, b) => { return a - b })
  let resArr = []
  let len = nums.length
  for (let i = 0; i < len-2; i++) {
    let target = -num2[i]
    let left = i + 1
    let right = len - 1
    // let target = left + right
    if (num2[i] === num2[i - 1]) {  // 前面出现过的数,那结果肯定有了,不用再去遍历了
      continue;
    }
    while (left < right) {
      let sum = num2[left] + num2[right]

      if (sum < target) {
        left++
      }
      else if (sum > target) {
        right--
      }
      else {
        resArr.push([num2[i], num2[left], num2[right]])
        while (num2[left] === num2[left + 1]) {  // 排除重复的结果
          left++
        }
        left++
        while (num2[right] === num2[right - 1]) {
          right--
        }
        right--
      }
    }
  }
  return resArr
};

console.log(threeSum([-1, 0, 1, 2, -1, -4]))
输出:[ [ -1, -1, 2 ], [ -1, 0, 1 ] ]

10、找出旋转数组的最小元素

用一般的顺序查找,时间复杂度为O(n),使用二分查找时间复杂度为O(logn)

function minNumberInRotateArray(rotateArray){
    const length = rotateArray.length;
    if (!length) {
        return 0;
    }
 
    let left = 0, right = length - 1;
    while (left < right) {
        let mid = Math.floor((left + right) / 2);
 
        // 子数组有序
        if (rotateArray[left] < rotateArray[right]) {
            return rotateArray[left];
        }
 
        // 左子数组有序,最小值在右边
        // 那么mid肯定不可能是最小值(因为rotateArray[mid]大于rotateArray[left])
        if (rotateArray[left] < rotateArray[mid]) {
            left = mid + 1;
            // 右子数组有序,最小值在左边
            // 这里right=mid因为最小值可能就是rotateArray[mid]
        } else if (rotateArray[mid] < rotateArray[right]) {
            right = mid;
        } else {
            // 无法判断,缩小下范围
            ++left;
        }
    }
 
    return rotateArray[left];
}

console.log('minNumberInRotateArray: ', minNumberInRotateArray([3,4,5,1,2]));  // 1

11、剪绳子(贪心)

题目描述:给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
思路:把绳子剪为长度为3能保证剪出来的绳子长度相乘最大,但当绳子的长度为7时,剪成34比33*1大,所以当能裁出一个4的时候,就把4裁出,其余都裁成3

function cutRope(number){
    if(number===2){
        return 1
    }
    if(number===3){
        return 2
    }
    let x=number%3; // 取出余数
    let y=parseInt(number/3); // number有多少个3
    if(x===0){ // 没有余数时
        return Math.pow(3,y) // 将取出来的所有3相乘
    }else if(x===1){ // 余数为1时,说明可以拿出一个3构成一个4
        return 2*2*Math.pow(3,y-1)
    }else{ // 当余数为2时,直接将2乘进来即可
        return 2*Math.pow(3,y)
    }
}

12、实现chunk函数

问题描述:将一个数组分割成多个数组,其中每个数组的单元数目由 length 决定。最后一个数组的单元数目可能会少于 length 个。
示例:
const a = [‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’, ‘g’, ‘h’];
chunk(a, 4);
结果:
[[‘a’, ‘b’, ‘c’, ‘d’],[‘e’, ‘f’, ‘g’, ‘h’]]

function chunk(arr,size){		
    var arr1=[];		
    for(var i=0;i<arr.length;i=i+size){		 
        var arr2=arr;          
        arr1.push(arr2.slice(i,i+size));		
    }		
    return arr1;	
}

字符串篇:

字符串的基础知识

let str = "HELLO " 
let str2 = "WORLD" 
// 字符串涉及到位置的,都是从0开始数
// const res = str.charAt(2)  // L ,返回指定位置的字符串,从0开始数
// const res = str.concat(str2)  // HELLO WORLD,返回连接好的字符串,不改变原字符串
// const res = str.indexOf('LO') // 3 返回某个字符串在原字符串中出现的第一个位置,没有就返回-1,从0开始数,区分大小写
// const res = str.includes('HEL') // true,返回布尔值,查看字符串中是否包含子串
// const res = str.replace('LO','lo') // HELlo ,将字符串替换成指定字符串,如果没找到相应字符串就返回原字符串(HELLO),返回已替换好的字符串,不改变原字符串
// 'qe.r'.replace(/\w/g, '@') //replace支持正则,将'qe.r'中的字母数字下划线都替换成@
// const res = str.slice(2,4) // LL ,将字符串按指定位置进行切片,前闭后开,slice(start, end) 
// const res = str.split('') //[ 'H', 'E', 'L', 'L', 'O', ' ' ], 将字符串分隔为字符串数组,空字符串 ("") 用作参数,那么 str中的每个字符之间都会被分割。参数的意思是从参数指定的地方分割
// const res = str.startsWith('HE') // true,查看字符串是否以指定字符串开头
// const res = str.toLowerCase(str) // hello  将字符串转为小写
// const res = str.toUpperCase(str) // HELLO 将字符串转为大写
// const res = str.trim(str) // 去除字符串两边的空格

1、判断一个字符串是否是回文字符串

问题描述:判断一个字符串是否是回文字符串
解决方案一、使用api:将字符串转化为数组(split),使用数组的(reverse)将数组中的元素逆序排练,将逆序排列的数组再转化为字符串(join)和原字符比较,相等就是回文,不相等就不是回文。

function getRes(inputString) {
  if (typeof inputString !== 'string') {
    return false
  }
  else {
    return inputString.split('').reverse().join('') === inputString
  }
}

解决方案二、不使用api:使用两个指针,一个从头部开始移动,一个从尾部开始移动,每移动一次就判断当前位置是否相等,只要不相等就返回false,循环完了没有返回false那就是回文字符串了

function getRes(inputString) {
  if (typeof inputString !== 'string') {
    return false
  }
  let i = 0, j = inputString.length - 1
  while (i < j) {
    if (inputString[i] !== inputString[j]) {
      return false
    }
    i++
    j--
  }
  return true
}

2、无重复字符的最长子串

描述:给定一个字符串,找出其中不含有重复字符的长子串的长度
输入:“abcabcbb” ,输出:3
思路:一个for循环,将字符串的字母依次加入到第三方数组,在加入之前,判断一下数组中是否已经有了这个字母,如果没有就直接加入,如果有了这个字符串,就从数组的最开头开始删除这个数组,到当前这个字母位置结束,再将当前字母加入到数组,使用Math.max()保留下数组最长的时候,最后返回max即可。

 if (typeof inputString !== 'string') {
    return false
  }
  let arr = [], max = 0
  for (let i = 0; i < inputString.length; i++) {
    let index = arr.indexOf(inputString[i])
    if (index !== -1) {
      arr.splice(0, index + 1)
    }
    arr.push(inputString.charAt(i))
    max = Math.max(arr.length, max)
  }
  return max
}

3、字符流中第一个不重复的数字

问题描述:请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
后台会用以下方式调用Insert 和 FirstAppearingOnce 函数
思路:将字符串转化为数组,利用对象key的唯一性,将字符串的每一项都作为obj的key放入对象中,第一次出现赋值为0,第二次乃至以后都还出现就赋值为1,最后遍历对象,输出对象值为一的key即可。

function getRes(ch) {
  let strArr = ch.split('')
  let obj = {}
  for (let i = 0; i < strArr.length; i++) {
    if (obj[strArr[i]] === undefined) {
      obj[strArr[i]] = 0
    }
    else {
      obj[strArr[i]] = 1
    }
  }
  for (key in obj) {
    if (obj[key] === 0) {
      return key
    }
  }
  return '#'
}

4、替换空格

题目描述:请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
思路:1、将字符串转化为由空格划分的数组,将数组再转化为利用%20划分的字符串:

function getRes(s) {
	return s.split(" ").join("%20")
}

2、创建一个新的字符串,将目标字符串一一添加到新的字符串,遇到空格就替换为%20:

function getRes(s) {
  let newS = ''
  for (let i = 0; i < s.length; i++) {
    if (s[i] !== " ") {
      newS += s[i]
    }
    else {
      newS += '%20'
    }
  }
  return newS
}

5、版本号比较

function toNum(a, b) {
    let aArr = a.split("")
    let bBrr = b.split("")
    let len = Math.max(a.length, b.length)
    bBrr.map((item, index, arr) => {
        if (item == '.') {
            arr.splice(index, 1)
        }
    })
    aArr.map((item, index, arr) => {
        if (item == '.') {
            arr.splice(index, 1)
        }
    })
    for (let i = 0; i < len; i++) {
        if (/[a-z]/.test(aArr[i]) && /[a-z]/.test(bBrr[i])) {
            if (aArr[i] == bBrr[i]) {
                continue
            }
            else if (aArr[i] > bBrr[i]) {
                console.log(`${a}是最新的版本`)
                return
            }
            else {
                console.log(`${b}是最新的版本`)
                return
            }
        }
        else {
            if (aArr[i] == bBrr[i]) {
                continue
            }
            else if (aArr[i] > bBrr[i]) {
                console.log(`${a}是最新的版本`)
                return
            }
            else {
                console.log(`${b}是最新的版本`)
                return
            }
        }
    }
}

var a = "v2.23a.3"; b = "v2.23b.8";

toNum(a, b);  // v2.23b.8是最新的版本

链表篇

1、从尾到头打印链表

问题描述:输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
输入:{67,0,24,58},返回值:[58,24,0,67]
思路:使用while循环将链表中的值一一加入到数组中,每加一个就让链表的指针下移一个,最后数组调用reverse方法,倒序输出。

function printListFromTailToHead(head)
{
	let node = head;
	let arr = []
	let i = 0
	while(node){
		arr[i] = node.val
		i++
		node = node.next
	}
	return arr.reverse()
	// 数组的倒序也可以如下:
	let resArr = []
	for(let j = 0; j < i; j++){
		resArr[j] = arr[i-j-1]
	}
}

2、反转链表

问题描述:输入一个链表,反转链表后,输出新链表的表头。
思路:我们需要用到三个指针,实现链表的反转,有两点很重要,一、pre指向null,curr和next都指向链表的第一个元素,二、转化的顺序不能变,1、先用next占住下一个元素,因为curr的next指向前一个元素它和后一个元素的连接就断,再也无法找到下一个元素,所以需要占位,2、将curr的next指向pre(null),完成指向后,移动pre(pre = curr),移动curr(curr = next),最后next和curr都指向null,而pre刚好指向链表的表头

function ReverseList(pHead)
{
	let pre = null;
	let curr = pHead;
	let next = pHead
	while(curr){
		next = curr.next
		curr.next = pre
		pre = curr
		curr = next
	}
	return pre
}

3、判断给定的链表中是否有环

思路:判定链表中是否有环,关键在于这个链表是不是可以一直循环走下去。
那么可以设置两个指针,快指针和慢指针,一个走两步,一个走一步。如果链表中有环,那么它们迟早是会相遇的,而且复杂度恒定为O(1)。如果链表为null,单节点,双节点,都不会有环,直接返回false就行了。

/*
 * function ListNode(x){
 *   this.val = x;
 *   this.next = null;
 * }
 */

/**
 * 
 * @param head ListNode类 
 * @return bool布尔型
 */
function hasCycle( head ) {
    // write code here
    let fast = head;
    let slow = head;
    while (fast!= null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        if(fast === slow) {
            return true;
        }
    }
    return false;
}

树篇

递归

树的算法一般来说都是需要递归,递归需要栈的支持,每调用一次函数都要创建一个 函数执行上下文压入栈 ,拿阶乘的例子来说:
在这里插入图片描述
当递归满足return条件的时候,就不需要往下执行了,所以factorial(0)会弹出栈,把结果1传递给下面的执行上下文,接着factorial(1)执行,弹出栈,把结果1传递给factorial(2),着factorial(2)执行,弹出栈,把结果1传递给factorial(3)…
在这里插入图片描述
所以递归就是不撞南墙不回头,撞到南墙了由第一个撞到的开始依次回头。

0、驼峰转下划线

问题描述:
let a = { aB: { aBc: 1 }, aBC: 2 }
let b = [{ aB: 1 }, { a: { aBcD: 1 } }]
转化成:
let a = { a_b: { a_bc: 1 }, a_b_c: 2 }
let b = [{ a_b: 1 }, { a: { a_bc_d: 1 } }]

let a = { aB: { aBc: 1 }, aBC: 2 }
let b = [{ aB: 1 }, { a: { aBcD: 1 } }]

function get_(a) {
    let newobj = {}
    for (key in a) {
        if (a[key] instanceof Object) {
            newobj[key.replace(/([A-Z])/g, "_$1").toLowerCase()] = get_(a[key]) // 递归这一定要重新赋值!
        }
        else {
            newobj[key.replace(/([A-Z])/g, "_$1").toLowerCase()] = a[key] // key.replace(/([A-Z])/g, "_$1" 将key中的大写字母前加上_
        }
    }
    return newobj
}

function getRes(a) {
    let newArr = []
    if (a instanceof Object && !(a instanceof Array)) { // 注意a instanceof Object包括数组
        return get_(a) // 对象直接返回
    }
    else if(a instanceof Array) {
        for (item of a) {
            newArr.push(get_(item))
        }
        return newArr
    }
}
console.log('getRes: ', getRes(b));

1、重建二叉数

问题描述:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
思路:先序遍历的第一个元素就是目标树的根节点,由根节点获取到中序遍历中根节点所在的索引,由索引在中序遍历中切出左右子树,剩下来的思路都是一样的,递归即可

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

function TreeNode(x) {
    this.val = x
    this.left = null
    this.right = null
}

function reConstructBinaryTree(pre, vin) {
    if (pre.length === 0 || vin.length === 0) {
        return null
    }

    let index = vin.indexOf(pre[0])

    let vinLeft = vin.slice(0, index) // 中序遍历左子树
    let vinRight = vin.slice(index + 1) // 中序遍历右子树

    let preLeft = pre.slice(1,index+1) // 先序遍历左子树
    let preRight = pre.slice(index+1) // 先序遍历右子树

    let node = new TreeNode(pre[0])
    node.left = reConstructBinaryTree(preLeft, vinLeft)
    node.right = reConstructBinaryTree(preRight, vinRight)
    return node
}

2、二叉树的下一个节点

问题描述:给定一个二叉树其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的next指针
在这里插入图片描述

思路:1、有右子树的,那么下个结点就是右子树最左边的点;(eg:D,B,E,A,C,G) 2、没有右子树的,也可以分成两类,a)是父节点左孩子(eg:N,I,L) ,那么父节点就是下一个节点 ; b)是父节点的右孩子(eg:H,J,K,M)找他的父节点的父节点的父节点…直到当前结点是其父节点的左孩子位置。如果没有eg:M,那么他就是尾节点。

function TreeLinkNode(x) {
    this.val = x;
    this.left = null;
    this.right = null;
    this.next = null;
}
function GetNext(pNode) {
    // // write code here
    if(pNode == null) return pNode;
    if(pNode.right !== null){
        //如果有右子树
        pNode = pNode.right;
        while(pNode.left != null){
            pNode = pNode.left;
        }
        return pNode;
    }
    while(pNode.next !== null){
        if(pNode === pNode.next.left){
        // 2.1 该节点为父节点的左子节点,则下一个节点为其父节点
            return pNode.next;
        }
       // 2.2 该节点为父节点的右子节点,则沿着父节点向上遍历,知道找到一个节点的父节点的左子节点为该节点,则该节点的父节点下一个节点
        pNode = pNode.next;
    }
    return null;
    }

3、二叉搜索树的后序遍历

题目描述:输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出true,否则输出false。假设输入的数组的任意两个数字都互不相同。例如输入 [4,8,6,12,16,14,10] 输出:true ; 输入[7,4,6,5] 输出:false
思路:二叉搜索树的后序遍历中最后 一个元素一定是树的根节点,我们在数组中找到第一个比它大的元素,这个元素的左边一定是其左子树,右边是其右子树,左子树中的每一个元素一定比根节点要小,右子树中的每一个元素一定比根节点要大,这两个条件一旦有一个不满足就return false,左子树中也是一样的,左子树的最后一个元素一定是左子树的根节点,又需要在左子树中找到第一个比它大的元素,元素的左边一定是其左子树,右边是其右子树…右子树也一样,所以我们用递归来解。

function getRes(arr) {
    if (arr <= 1) {
        return true
    }

    let rootItem = arr[arr.length - 1] // 找到中序遍历的根节点
    let index = 0, leftRoot, rightRoot, leftTree, rightTree, res = true

    for (let i = 0; i < arr.length; i++) {
        if (arr[i] > rootItem) { // 找到第一个比根节点大的值,用它的切出左右子树
            index = arr[i] 
            break
        }
    }

    leftRoot = arr.indexOf(index) - 1 // 左子树的根节点
    rightRoot = arr[arr.length - 2] // 右子树的根节点
    leftTree = arr.slice(0, leftRoot + 1)  // 根据根节点切出左子树
    rightTree = arr.slice(leftRoot + 1, arr.length - 1) // 根据根节点切出右子树

    if (leftTree.length > 0) { 
        for (let i = 0; i < leftTree.length; i++) {
            if (leftTree[i] > rootItem) { // 查看是否左子树中的每个值都比根节点小,比根节点大就不满足条件,return false
                res = false
            }
        }
    }
    if (rightTree.length > 0) {
        for (let i = 0; i < rightTree.length; i++) {
            if (rightTree[i] < rootItem) { // 查看是否右子树中的每个值都比根节点大,比根节点小就不满足条件,return false
                res = false
            }
        }
    }
    getRes(leftTree) // 递归切出来的左子树
    getRes(rightTree) // 递归切出来的右子树
    return res
}

console.log('getRes(ch): ', getRes([4, 8, 6, 12, 16, 14, 10]));  // true

层次遍历二叉树

问题描述:如图所示为二叉树的层次遍历,即按照箭头所指方向,按照1、2、3、4的层次顺序,对二叉树中各个结点进行访问
在这里插入图片描述
递归解决:

var levelOrder = function(root) {
  if(!root)return []
  let res=[]
  let aux=[root]
  while(aux.length>0){
    let len=aux.length
    let vals=[]
    for(let i=0;i<len;i++){
      let node=aux.shift()
      vals.push(node.val)
      if(node.left)aux.push(node.left)
      if(node.right)aux.push(node.right)
    }
    res.push(vals)
  }
  return res
};

非递归解决:

var levelOrder = function(root) {
  if(!root)return []
  let res=[]
  let aux=[root]
  while(aux.length>0){
    let len=aux.length
    let vals=[]
    for(let i=0;i<len;i++){
      let node=aux.shift()
      vals.push(node.val)
      if(node.left)aux.push(node.left)
      if(node.right)aux.push(node.right)
    }
    res.push(vals)
  }
  return res
};

栈篇

1、两个栈实现队列

题目描述:用两个栈来实现一个队列,分别完成在队列尾部插入整数(push)和在队列头部删除整数(pop)的功能。 队列中的元素为int类型。保证操作合法,即保证pop操作时队列内已有元素。
思路:栈先入后出、队列先入先出,两个栈stack1、stack2,stack1用来入栈,stack2用来出栈,stack2中的元素都来自于stack1弹栈的元素,只要stack2中还有元素,每次要求出栈时就让stack2弹栈,stack2无元素了才从stack1的弹栈来入栈。

let stack1 =[]
let stack2 = []

function push(node) {
     // write code here
    stack1.push(node)
}
function pop() {
     // write code here
    if (stack1.length==0 && stack2.length==0) {
        return null
    }
    else if(stack2.length){
        return stack2.pop() 
    }
    else{
        while(stack1.length){
            stack2.push(stack1.pop())
        }
    }
    return stack2.pop()
}

查找篇

1、顺序查找

就是一般的for循环查找,时间复杂度O(n)

function sequenceSearch(arr, num) {
    const len = arr.length
    for (let i = 0; i < len; i++) {
        if(arr[i] == num){
            return i
        }
    }
    return null
}
console.log(sequenceSearch([1,4,7,9],7))  // 2

2、二分查找

思路:left和right分别指向数组的第一个和最后一个元素,mid指向中间元素,当target小于中间元素时,right = mid+1,当target大于中间元素时left = mid+1,当中间元素 = target时,返回target,三个条件都不满足,说明数组中没有这个元素,返回undefined
每一次查找,范围都会缩小到前一次的一半,时间复杂度为O(logn)

function getRes(rotateArray, target) {
  let len = rotateArray.length
  let left = 0
  let right = len - 1
  while (left <= right) {
    let mid = Math.floor((left + right) / 2)
    if (target > rotateArray[mid]) {
      left = mid + 1
    }
    else if (target < rotateArray[mid]) {
      right = mid - 1
    }
    else if (target = rotateArray[mid]) {
      return mid
    }
    else {
      return undefined
    }
  }
}

3、哈希表查找

题目:字符流中第一个不重复的数字
详情见 上述 字符串篇 第三题

4、二叉排序树查找

二叉排序树查找算法对应的数据结构是二叉搜索树,算法示例见上述 树篇的第三题

排序篇

1、冒泡排序

时间复杂度: 平均时间复杂度O(nn) 、最好情况O(n)、最差情况O(nn)
空间复杂度: O(1)
稳定性: 稳定

function swap(i,j,arr){
    let temp = 0
    temp = arr[i]
    arr[i] = arr[j]
    arr[j] = temp
}

function bubbleSort(arr) {
    let len = arr.length
    for (let i = 0; i < len; i++) {
        for (let j = i + 1; j < len; j++) {
            if (arr[i] > arr[j]) {
                swap(i,j,arr)
            }
        }
    }
    return arr
}
console.log(bubbleSort([5, 2, 7, 9, 1])) // [ 1, 2, 5, 7, 9 ]

2、插入排序

插入排序(依次取出数组中的元素,让被取出的元素和前面已排好顺序的元素一一作比较,遇到比它大的就再往前比较,直到遇到比他小的元素,就将当前元素插入到他遇到的第一个比他小的元素的前面)
空间上:它只需要一个记录的辅助空间,因此关键看时间复杂度。
最好情况:也就是排序表本身就是有序的,则只需要进行n-1次比较,由于每次都是arr[i] > arr[i+1],没移动记录,时间复杂度为O(n).
最坏情况:是排序表逆序,需要比较 (n+2)(n-1)/2次,移动(n+4)(n-1)/2次。如果排序记录是随机的,根据概率相同的原则,平均比较和移动的次数约为O(n²)/4次。
时间复杂度为O(n²),比冒泡排序和简单选择排序算法性能好一些。

function insertSort(arr) {
    let len = arr.length
    for (let i = 1; i < len; i++) {
        let currentValue = arr[i] // 获取数组中的当前元素
        let j = i - 1 // 指到当前元素的前一个元素
        while (j >= 0 && arr[j] > currentValue) { // 当前一个元素大于当前元素时
            if (arr[j] >= currentValue) { // 如果前一个元素大于当前元素
                arr[j + 1] = arr[j] // 将前一个元素往后移一位,因为当前元素本来就被currentValue保存下来了,i的位置基本可以认为是空的
            }
            j--
        }
        arr[j + 1] = currentValue // while循环结束,j指到比当前元素小的位置,我们就把当前元素插到它的前面
    }
    return arr
}

console.log('insertSort(ch): ', insertSort([4, 8, 6, 12, 16, 4, 14, 10])); //  [  4,  4,  6,  8, 10, 12, 14, 16]

3、快速排序

快速排序(而在最坏情况下,即数组已经有序或大致有序的情况下,每一次划分都是以数组最后一个元素作为标准,每次划分只能减少一个元素,快速排序将不幸退化为冒泡排序,最坏情况为O(n²)。快速排序和归并排序一样,每一层的总时间复杂度都是O(n),因为需要对每一个元素遍历一次。在实际应用中,快速排序的平均时间复杂度为O(nlogn)。空间复杂度:快排这种递归算法需要栈的支持,栈的大小最多为n所以是O(n))
关键点1:划分子区间,每一次的子区间长度是上一次的两倍,所以merge_sort递归需要执行logn次
关键点2:对于每一个区间,处理的时候,都需要遍历一次区间中的每一个元素,每一层的总时间复杂度都是O(n)

function quickSort(arr) {
    if (arr.length <= 1) { // 如果数组中只有一个值或者是空数组,直接将其返回,这是递归的条件
        return arr
    }
    let currentIndex = Math.floor(arr.length / 2) // 获取数组最中间的数
    let currentItem = arr.splice(currentIndex, 1) // 将最中间的数取出并在数组中将最中间的数删除
    let leftArr = [], rightArr = [] 
   // 关键点2:对于每一个区间,处理的时候,都需要遍历一次区间中的每一个元素,每一层的总时间复杂度都是O(n)
    arr.forEach(item => {
        if (item < currentItem) {
            leftArr.push(item) // 将数组中比中间值小的都放在中间值的左边
        }
        else {
            rightArr.push(item) // 将数组中比中间值大的都放在中间值的左边
        }
    })

    // 左数组和右数组操作一样,递归再进行上面的部分
    return quickSort(leftArr).concat(currentItem).concat(quickSort(rightArr))
    // 关键点1:划分子区间,每一次的子区间长度是上一次的两倍,所以merge_sort递归需要执行logn次
}

console.log(quickSort([5, 2, 7, 9, 1]))  // [ 1, 2, 5, 7, 9 ]

4、归并排序

首先将数组一分为二一分为二…直到分到一个数组中只有一个元素,然后在将元素比较完放入一个数组,由递归的特性传给下一个递归函数,最后合并起来
平均时间复杂度:O(nlogn)
最佳时间复杂度:O(n)
最差时间复杂度:O(nlogn)
空间复杂度:O(n)
排序方式:In-place
稳定性:稳定

function merge_sort(arr) { // 将数组由最中间的数作为基准一分为二,递归执行,最终将一个大数组分为由一个元素组成的数组
    if (arr.length == 1){
         return arr
    }
    
    var mid = Math.floor(arr.length / 2)
    var leftArr = arr.slice(0, mid)
    var rightArr = arr.slice(mid)
// 关键点1:划分子区间,每一次的子区间长度是上一次的两倍,所以merge_sort递归需要执行logn次
    return Merger(merge_sort(leftArr), merge_sort(rightArr)); //合并左右部分
    // 关键点2:Merger方法每次执行的时间复杂度为O(n),具体看下方
}

function Merger(leftArr, rightArr) {
    var leftLen = leftArr && leftArr.length;
    var rightLen = rightArr && rightArr.length;
    var resArr = [];
    var i = 0, j = 0;

    while (i < leftLen && j < rightLen) {
        if (leftArr[i] < rightArr[j]) {
            resArr.push(leftArr[i++]);
        }
        else {
            resArr.push(rightArr[j++]);
        }
    }
    while (i < leftLen) {
        resArr.push(leftArr[i++]);

    }
    while (j < rightLen) {
        resArr.push(rightArr[j++]);
    }
    console.log("将数组", leftArr, '和', rightArr, '合并为', resArr)
    return resArr;
    递归过程:
    // 将数组 [ 8 ] 和 [ 2 ] 合并为 [ 2, 8 ]
    // 将数组 [ 1 ] 和 [ 2, 8 ] 合并为 [ 1, 2, 8 ]
    // 将数组 [ 4 ] 和 [ 9 ] 合并为 [ 4, 9 ]
    // 将数组 [ 6 ] 和 [ 4, 9 ] 合并为 [ 4, 6, 9 ]
    // 将数组 [ 1, 2, 8 ] 和 [ 4, 6, 9 ] 合并为 [ 1, 2, 4, 6, 8, 9 ]
}
console.log('merge_sort(ch): ', merge_sort([1,8,2,6,4,9])); //  [  4,  4,  6,  8, 10, 12, 14, 16]

回溯算法篇

1、矩阵中的路径

问题描述:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。[[a,b,c,e],[s,f,c,s],[a,d,e,e]],“abcced”
思想:任选一个位置作为路径的起点,就选(0,0),以下的每一步都可以在矩阵中上、右、下、左的移动一格,找不到就return false,找到了就把布尔值矩阵的对应位置设置为true,再在这个上开始上、右、下、左;在移动之后,需要做边界判断:1、判断这个位置是否还在矩阵内;2、是否已经被走过了;当找到字符串中对应的数字时,在矩阵的位置它又可以上、右、下、左的移动一格来找下一个与目标字符串匹配的字符,移动的操作方法都是一样的,所以这里我们使用递归实现,路径可被看作一个栈,由于重复的路径不能重复的加入格子,所以还需要定义一个与字符矩阵大小相同的布尔值矩阵,用来标识路径是否进入了每一个格子。

// [[a,b,c,e],[s,f,c,s],[a,d,e,e]],"abcced"
let m, n // m行 n列
let p = [[-1, 0], [1, 0], [0, 1], [0, -1]] // 向左 右 下 上 的方向走
let isUsed // 判断格子是否被走过,走过就为true,没走过为false
function hasPath(matrix, word) {
    m = matrix.length
    n = matrix[0].length
    isUsed = new Array(m).fill(false).map(p => new Array(n).fill(false)) // 创建一个m行 n列的矩阵
    // 从[0,0]开始找, 循环多次找到首个字母的初始位置
    for (let i = 0; i < m; i++) {
        for (let j = 0; j < n; j++) {
            if (findWords(matrix, word, 0, i, j)) {
                return true
            }
        }
    }
    return false;
}

function findWords(matrix, word, index, startX, startY) {
    if (index === word.length - 1) {
        return matrix[startX][startY]  === word[index]
    }

    if (matrix[startX][startY]  == word[index]) { // 在矩阵中找到了words中的字母
        isUsed[startX][startY] = true // 表明这个格子已经找过了
        for (let i = 0; i < 4; i++) {  // 从矩阵中已经找到words中的字母的位置开始从左 右 上 下的方向又开始寻找words中的下一个字母
            let nextX = startX + p[i][0] 
            let nextY = startY + p[i][1]
            if (isInArea(nextX, nextY) && !isUsed[nextX][nextY]) { // 判断是否走超出了格子 && 判断这个格子是否被走过
                if(findWords(matrix, word, index+1, nextX, nextY)){ // index+1 因为index的位置的words字母已经被找到了,相同的方法比较矩阵中值和words中的字母是否相同
                    return true
                }
            }
        }
    }
    isUsed[startX][startY] = false
    return false // 在矩阵中没找到与words中相同的值就return false
}

function isInArea(x, y) {
    return x >= 0 && y >= 0 && x < m && y < n
}

console.log('hasPath: ', hasPath([
    ['a', 'b', 'c', 'e'],
    ['s', 'f', 'c', 's'],
    ['a', 'd', 'e', 'e']], "abcced"));
// true

2、机器人的运动范围

地上有一个rows行和cols列的方格。坐标从 [0,0] 到 [rows-1,cols-1]。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于threshold的格子。 例如,当threshold为18时,机器人能够进入方格[35,37],因为3+5+3+7 = 18。但是,它不能进入方格[35,38],因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
思想:让机器人从0,0位置开始走,它的下一步可以向上、下、左、右走,但是在它向下走之前,我们需要判断;1、它当前所在的位置在矩阵之内 2、他所在的矩阵格子是否满足行坐标和列坐标的数位之和大于threshold3、是否已经是走过的格子,最后一个条件我们可以创建一个rows行,cols列的矩阵,最开始里面放的都是false,当它的某格被走过,我们就那格置为true,所以true就代表已经被走过。

function movingCount(threshold, rows, cols) {
    if (threshold < 0 || rows < 0 || cols < 0) {
        return 0
    }
    let isVisited = new Array(rows).fill(false).map(item => new Array(cols).fill(false))
    let count = movingCountCore(threshold, rows, cols, 0, 0, isVisited)
    return count
}

function movingCountCore(threshold, rows, cols, startX, startY, isVisited) {
    let count = 0
    if (checkBoard(rows, cols, startX, startY) && getDigitSum(startX, startY) <= threshold && !isVisited[startX][startY]) {
        isVisited[startX][startY] = true
        count = 1 + movingCountCore(threshold, rows, cols, startX - 1, startY, isVisited)
            + movingCountCore(threshold, rows, cols, startX, startY - 1, isVisited)
            + movingCountCore(threshold, rows, cols, startX + 1, startY, isVisited)
            + movingCountCore(threshold, rows, cols, startX, startY + 1, isVisited)
    }
    return count
}

function checkBoard(rows, cols, startX, startY) {
    if (startX >= 0 && startX < rows && startY >= 0 && startY < cols) {
        return true
    }
    return false
}

function getDigitSum(startX, startY) {
    let sum = 0
    let str = startX + '' + startY
    for (let i = 0; i < str.length; i++) {
        sum += str.charAt(i) / 1
    }
    return sum
}
console.log(movingCount(5, 10, 10)); // 21

高质量的代码篇

1、数值的整数次方

题目描述:给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。保证base和exponent不同时为0。不得使用库函数,同时不需要考虑大数问题,也不用考虑小数点后面0的位数。
注意:这道题虽然简单,往往简单的题才不能掉以轻心,要考虑完整的边界判断,整数,负数和零都要考虑进去,简单的题不考虑边界给面试官的影响就很差。

function Power(base, exponent) {
    if (base === 0) {
        return 0
    }
    else if (exponent === 0) {
        return 1
    }
    else {
        let index = 0
        let res = 1
        let numType = true
        if (exponent < 0) {
            numType = false
            exponent = -exponent
        }
        while (index < exponent) {
            res *= base
            index++
        }
        return numType ? res : 1 / res
    }
}
console.log('Power: ', Power(2, 3)); // 8 

码字不易,都是自己总结的,点赞鼓励下啦~
持续更新中…

  • 10
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
RSA算法是一种非对称加密算法,常用于数据加密和数字签名等。在前端使用js实现RSA算法加密,后端使用Java实现RSA算法解密,具体步骤如下: 前端(js)实现: 1. 生成RSA密钥对,代码如下: ```javascript function generateRSAKey() { var crypt = new JSEncrypt({ default_key_size: 1024 }); crypt.getKey(); return crypt; } ``` 2. 使用公钥加密数据,代码如下: ```javascript function encryptData(data, publicKey) { var crypt = new JSEncrypt(); crypt.setKey(publicKey); var encryptedData = crypt.encrypt(data); return encryptedData; } ``` 后端(Java)实现: 1. 使用私钥解密数据,代码如下: ```java public static String decryptData(String data, String privateKey) throws Exception { byte[] dataBytes = Base64.decodeBase64(data); byte[] keyBytes = Base64.decodeBase64(privateKey); PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PrivateKey privateK = keyFactory.generatePrivate(pkcs8KeySpec); Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.DECRYPT_MODE, privateK); byte[] decryptedData = cipher.doFinal(dataBytes); return new String(decryptedData); } ``` 2. 生成RSA密钥对,代码如下: ```java public static Map<String, String> generateRSAKey() throws Exception { KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA"); keyPairGen.initialize(1024); KeyPair keyPair = keyPairGen.generateKeyPair(); RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); Map<String, String> keyMap = new HashMap<String, String>(); keyMap.put("publicKey", Base64.encodeBase64String(publicKey.getEncoded())); keyMap.put("privateKey", Base64.encodeBase64String(privateKey.getEncoded())); return keyMap; } ``` 注意:在前端和后端使用RSA算法加密和解密的时候,需要使用相同的密钥对(即公钥和私钥)。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值