JavaScript笔试刷题小结(二)


继续整理最近的刷题记录📝

1. 剑指Offer 39. 数组中出现次数过半的数字

书中提到了两种复杂度均为O(n)的解法:
1)基于Partition函数:
如果这个数字存在,那它必然是数组排序后的中位数,基于随机快排中的Partition的思想对数组进行操作,如果选中的数字index = n/2,恰好它是中位数;index>n/2,中位数在其左边;index<n/2,中位数在其右边
cc觉得js可以如此简单粗暴:

var majorityElement = function(nums){
  nums.sort()
  return nums[Math.floor(nums.length/2)]
}

leetcode执行结果
2)根据数组特点
如果该数字存在,则它的次数绝对比其他元素多;
遍历数组,同时统计当前元素的次数,出现相同元素+1,出现其他元素-1,最后返回times>0的元素,即为所求数字

var marjorityElement = function(nums){
  let res = nums[0]
  let times = 1
  for(let i = 1; i < nums.length; ++i){
    if(times === 0){
      res = nums[i]
      times = 1
    }else if(nums[i] === res){
      times ++
    }else{
      times --
    }
  }
  return res
}

leetcode执行结果

2. 剑指Offer 59-Ⅱ. 队列中的最大值

这个题目,要不是出现在每日挑战,我真的不会做,出题人思路清奇?尤其是看了评论区发现大家都看不懂,得~~~没有技术含量,尤其对于js和python来说

var MaxQueue = function() {
    this.arr = []
};

/**
 * @return {number}
 */
MaxQueue.prototype.max_value = function() {
    return (this.arr.length > 0) ? Math.max(...this.arr) : -1
};

/** 
 * @param {number} value
 * @return {void}
 */
MaxQueue.prototype.push_back = function(value) {
  if(value !== null){
    this.arr.push(value)
  }
};

/**
 * @return {number}
 */
MaxQueue.prototype.pop_front = function() {
    if(this.arr.length > 0)
      return this.arr.shift()
    return -1
};

3. 剑指Offer 42. 连续子数组的最大和

使用动态规划计算,当前位置处的最大和等于上一个位置上的最大和与与最大和加当前位置值之和中的较大值,状态转移方程式:
状态转移方程

var maxSubArray = function(nums) {
    let res = nums[0]
    let maxSum = 0
    nums.map((item) =>{
        maxSum = Math.max(maxSum + item, item)
        res = Math.max(maxSum, res)
    })
    return res
};

4. 剑指Offer 40. 最小的k个函数

对于js和python来说,这道题调包当然非常简单,截取排序数组的前k个即可

1)直接排序做法(简单、直观、时间O(n*logN) ---- 空间O(logN)):

var getLeastNumbers = function(arr, k){
  return arr.sort((a,b) => a-b).slice(0,k)
}

但是实际上,我们获取前k个数字的时候,并不需要对整个数组进行排序,尤其是在 k << length 的情况下,是非常消耗性能的

2) 使用最大堆(手动写堆略复杂、时间O(K) ---- 空间O(n*logK))

整体的算法流程:

  • 创建大小为k的最大堆
  • 将数组的前k个元素放入堆中
  • 从下标k继续开始依次遍历数组的剩余元素
    (如果元素小于堆顶元素,那么取出堆顶元素,将当前元素入堆;如果元素大于/等于堆顶元素,不做操作)
function swap(arr, i, j){
  [arr[i], arr[j]] = [arr[j], arr[i]]
}
class maxHeap{
  constructor(arr = []){
    this.container == []
    if(Array.isArray(arr)){
      arr.forEach(this.insert.bind(this))
    }
  }

  insert(data){
    const {container} = this
    container.push(data)
    let index = container.length - 1
    while(index){
      let parent = Math.floor((index-1)/2)
      if(container[index] <= container[parent]){
        break
      }
      swap(container, index, parent)
      index = parent
    }
  }
  extract(){
    const { container } = this
    if(!container.length){
      return null
    }
    swap(container, 0, container.length-1)
    const res = container.pop()
    const length = container.length
    let index = 0
    let exchange = index * 2 + 1
    while(exchange < length){
      let right = index * 2 + 2
      if(right < length && container[right] > container[exchange]){
        exchange = right
      }
      if(container[exchange] <= container[index]){
        break
      }
      swap(container, exchange, index)
      index = exchange
      exchange = index * 2 + 1
    }
    return res
  }
  top(){
    if(this.container.length){
      return this.container[0]
    }
    return null
  }
}
var getLeastNumbers = function(arr, k){
  const length =  arr.length
  if(k >= length){
    return arr
  }
  const heap = new MaxHeap(arr.slice(0,k))
  for(let i = k; i < length; ++i){
    if(hash.top() > arr[i]){
      heap.extract()
      heap.insert(arr[i])
    }
  }
  return heap.container
}

3)基于快速排序的Partition(时间O(N) ---- 空间O(N)):

可以使用快速排序中的Partition,把元素arr[0]放入排序后的正确位置,并且返回这个位置index。算法流程如下:

  • 如果index = k, 说明第k个元素已经放在正确位置,返回前k个
  • 如果index > k,前k个元素在[left, index-1]之间,缩小查找范围,继续查找
  • 如果index < k,前k个元素在[index+1,right]之间,缩小查找范围,继续查找
function partition(arr, start, end){
  const k = arr[start]
  let left = start + 1
  let right = end
  while(1){
    while(left <= end && arr[left] <= k) ++ left
    while(right >= start && arr[right] >= k) -- right

    if(left >= right){
      break
    }
    [arr[right], arr[start]] = [arr[start], arr[right]]
    return right
  }
}
var getLeastNumbers = function(arr, k){
  const length = arr.length
  if(k >= length) return arr
  let left = 0
  let right = length - 1
  let index = partition(arr, left, right)
  while(index !== k){
    if(index < k){
      left = index + 1
    }else if(index > k){
      right = index - 1
    }
    index = partition(arr, left, right)
  }
  return arr.slice(0,k)
}

相比之下,更推荐第三种Partition的解法,手写堆太复杂了

5. ⭐️ Leetcode 93. 复原IP地址

思路:使用基于递归的DFS方法,去掉不符合的情况,每次从字符串开始位置截掉长度为1,2,3之后剩下的子串,用于递归
关于这个题,求助了Z同学🐷,开始思路有点乱💫💫💫然后茅塞顿开
定义递归函数很重要,思路就是每次切完当前的串之后,继续切后面的串,所以在递归函数中,我把变量设置为剩下的串,

var restoreIpAddresses = function(s){
  let res = []
  // 首先判断s长度是否满足ip,标准IP地址的长度范围4~12 (0.0.0.0 ~ 255.255.255.255)
  if(s.length < 4 || s.length > 12)   return res
   // cur表示当前未划分的字符串,pre表示前面已经划分好的(加上.的),counts表示已经划分的段数(pre中.的个数)
  function divideIpAddress(cur, pre, counts){
    if(counts === 3){
	  // 如果已经到了第三段了,只需要检查剩下的串
	  if(cur.length <= 3 && parseInt(cur.slice(0,3)) < 256 ){
	    if(cur.length >= 2 && cur.charAt(0)==='0'){
	      return //说明第四段里面首位是0,绝对不可能再划分了,剪枝
	    }
	    // 否则,将当前剩余的字符串加入到分段队列,ip分段成功
	    let item = pre.concat(cur)
	    res.push(item)
	  }
   }
   if(counts < 3){
     // 划分长度为1的作为一段
     let item = pre.concat(cur.slice(0,1)).concat('.')
     divideIpAddress(cur.slice(1), item, counts+1)
     if(cur.charAt(0) !== '0'){
       // 首位不是0的情况下,划分长度为2的作为一段
       item = pre.concat(cur.slice(0,2)).concat('.')
       divideIpAddress(cur.slice(2), item, counts+1)
       if(parseInt(cur.slice(0,3)) < 256){
         // 首位不是0且数值小于256的情况下,划分长度为3的作为一段
         item = pre.concat(cur.slice(0,3)).concat('.')
         divideIpAddress(cur.slice(3), item, counts+1)
       }
     }
   }
  }
  divideIpAddress(s, "", 0)
  return res
}

tips: 1)注意开头是0的情况 2)提前添加长度的判断(标准ip长度在4~12)

6. Leetcode 121.买卖股票的最佳时机

思路:遍历一遍数组,记录今天之前的最低价格,和今天卖出的利润,更新最大利润,时间复杂度O(n),空间复杂度O(1)

var maxProfit = function(prices) {    
  let minP = prices[0]    
  let maxP = 0    
  prices.map((item)=>{        
    maxP = item - minP > maxP? item - minP : maxP        
    if(item < minP){            
      minP = item        
    }    
  })    
  if(maxP > 0){        
    return maxP    
  }    
  return 0
};

7. Leetcode 543.二叉树的直径

思路:遍历树,比较每个节点的左右子树的高度之和,记录最大的长度为树的直径(最初cc只考虑了根节点,导致忽略了最长直径不一定过根节点,虽然题目没有指明,但是一定要考虑到)

var diameterOfBinaryTree = function(root){
  let diameter = 0
  const getMaxDepth = node =>{
    if(!node){
      return -1
    }
    let left = getMaxDepth(node.left)
    let right = getMaxDepth(node.right)
    diameter = Math.max(diameter,left+right+2) // 更新最大值
    return Math.max(left,right)+1 // 返回当前节点下的最大深度
  }
}

8. Leetcode 112.路径总和

思路:总之,递归大法好

var hasPathSum = function(root, sum) {
    if(root === null){
        return false
    }
     if(root.left === null && root.right === null){
        if(root.val === sum){
            return true
        }
        return false
    }
    return hasPathSum(root.left, sum-root.val) || hasPathSum(root.right, sum-root.val)
};

Author: Casey Lu
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source Casey Lu !
评论
 Previous
Javascript中的闭包Closure Javascript中的闭包Closure
对JS中的闭包还没有了解过的可看 MDN 闭包, 在面试的时候闭包也是经常被提及的Topic 一、闭包的概念 简单的说,闭包就是有权访问其他函数作用域内部变量的函数 闭包 = 函数 + 函数能够访问的自由变量 闭包的三个特点: 函数嵌套函
2020-03-10
Next 
ES6的基础语法新特性 ES6的基础语法新特性
ES6语法在面试中是频繁被提及的,相比于ES3和ES5它具备许多特性,接下来,主要通过对比将从多个方面逐个介绍它的新特性。 一、常量定义 在ES3中不存在常量的定义,ES5中可以实现,但并未明确的定义,通过对象添加属性prototype实现
2020-03-09
  TOC