前端数据结构与算法

29 阅读2分钟

一、分治

分治就是把一个复杂的问题分成两个或更多的相同或相似的子问题,再把子问题分成更小的子问题,直到最后子问题可以简单的直接求解,原问题的解即子问题的解的合并。

二分查找

// 二分查找
function binarySearch (array, value) {
  let left = 0
  let right = array.length
  let mid
  while (left < right) {
    mid = (left + right) >> 1
    if (array[mid] === value) return mid
    if (array[mid] > value) right--
    if (array[mid] < value) left++
  }
  return 'findError'
}

快速幂

// 快速幂,n>0且为整数,m的n次方可以看成是m的n/2次方,递归调用
function fastPow (m, n) {
  if (n === 0) return 1
  if (n % 2 === 1) { // n为奇数
    return fastPow(m, n - 1) * m
  } else { // n为偶数
    return fastPow(m * m, n / 2)
  }
}
 

二、贪心算法

贪心算法解决的一类问题就是将复杂的问题拆解成小问题,通过局部最优解组合成为最终解!

// 输入:nums = [10,9,2,5,3,7,101,18]
// 输出:4
// 解释:最长递增子序列是 [2,3,7,101],因此长度为 4
// 贪心+二分,时间复杂度O(n*logn)
let lengthOfLIS = function (nums) {
  let len = 1; let n = nums.length
  if (n === 0) return 0
  let d = [] // d为最优解的栈
  d[len] = nums[0]
  for (let i = 1; i < n; ++i) {
    if (nums[i] > d[len]) { // 下个数比栈顶数大就入栈
      d[++len] = nums[i]
    } else {
      let left = 1; let right = len; let index = 0 // 如果找不到说明所有的数都比 nums[i] 大,此时要更新 d[1],所以这里将 index 设为 0
      while (left <= right) { // 二分法,取中位数mid,然后迭代更新左右边界left和right
        let mid = (left + right) >> 1
        if (d[mid] < nums[i]) { // 找到最适合塞当前数的index
          index = mid
          left = mid + 1
        } else {
          right = mid - 1
        }
      }
      d[index + 1] = nums[i]
    }
  }
  return len
}

三、动态规划

动态规划在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。

动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。

斐波那契数列问题

function fibonacci3(n) {
    let n1 = 1
    let n2 = 1
    let sum = 1
    for(let i = 3; i <= n; i += 1) {
        sum = n1 + n2
        n1 = n2
        n2 = sum
    }
    return sum
 }

二分查找会话时间下标

// const sessionList = [{sessionId: 123, svrTime: 1622615330243, ...},{sessionId: 321, svrTime: 1622615349837, ...},...]
// const searchRes = {sessionId: 123, svrTime: 1622615349837, ...}
function search(searchKey, sessionList){
  let midNum = sessionList.length >> 1
  let mid = sessionList[midNum]
  if(mid.svrTime === searchKey.svrTime) return mid
  if(midNum === 0) return null
  if(mid.svrTime > searchKey.svrTime){
  	search(searchKey,sessionList.slice(0,midNum))
  }else{
  	search(searchKey,sessionList.slice(midNum))
  }
}

动态规划求解最长子序列

// 输入:nums = [10,9,2,5,3,7,101,18]
// 输出:4
// 解释:最长递增子序列是 [2,3,7,101],因此长度为 4
// 动态规划,时间复杂度O(n^2)
let lengthOfLIS = function(nums, dp = [1]) {
    for (let i = 1; i < nums.length; i++){
        dp[i] = 1
        for (let j = 0; j < i; j++) {
            nums[i] > nums[j] && (dp[i] = Math.max(dp[i], dp[j] + 1)) //计算当前元素节点存在最长递增子序列的长度
        }
    }
    return Math.max(...dp)
}

背包问题用动态规划求解

// weight 物品重量集合
// value 物品价值集合
// maxW 背包重量
// n 物品数量
function packing (weight, value, maxW, n) {
  let dp = [] // 行放重量,列放价值
  for (let i = 0; i <= maxW; i++) {
    dp[i] = []
    for (let j = 0; j < n; j++) {
      // 初始状态,背包为0
      if (i === 0) {
        dp[i][j] = 0
        continue
      }
      // 当前物品重量大于当前背包重量
      if (weight[j] > i) {
        dp[i][j] = dp[i][j - 1] || 0
        continue
      }
      // 递推公式
      let curValueMax = dp[i - weight[j]][j - 1] || 0 + value[j]
      dp[i][j] = Math.max(curValueMax, dp[i][j - 1] || 0)
    }
  }
  return dp
}