LeetCode探索(九):169_多数元素

143 阅读3分钟

这是我参与11月更文挑战的第 16 天,活动详情查看:2021最后一次更文挑战

前言

算法,对于前端er来说是既熟悉又陌生的。 说到熟悉,在我们日常的写bug 🐛 的过程中,我们总会有意无意地用到了算法,比如最简单的数组sort()排序、Math.max()Math.min()等方法。说到陌生,我们很少去深入探索算法是怎么实现的、不同算法之间的优劣、算法的执行效率... =_=

本文我们将学习一种新的算法思想:分治算法。和把大象塞进冰箱一样,分治算法只要遵循三个步骤即可:分解 -> 解决 -> 合并

  • 分解:分解原问题为结构相同的子问题;

  • 解决:当分解到容易求解的边界后,进行递归求解;

  • 合并:将子问题的解合并成原问题的解。

下面我们通过一道 LeetCode 题目来了解分治算法的特点以及应用。

题目

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

示例 1:

输入:[3,2,3]
输出:3

示例 2:

输入:[2,2,1,1,1,2,2]
输出:2

进阶:

  • 尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

思考

题目本身很简单,就是从给定数组中寻找多数元素,也就是出现次数大于一半的数字。

首先,我们能想到的是使用哈希表去统计每个数字出现的次数,然后遍历哈希表,找到数组中的多数元素。该方法的时间复杂度和空间复杂度都是O(n),那么我们如何改进算法呢?

其中一种优化方法是,我们可以使用分治算法去优化代码。分治算法,即分而治之,我们可以把一个复杂问题分成两个或更多的相同或相似子问题,直到最后子问题可以简单地直接求解,最后将子问题的解合并为原问题的解。

假如数字a是数组的多数元素,那么把数组分为两部分后,a至少是其中一部分的多数元素。那么我们就可以应用分治算法:我们把数组分成两部分,分别求出这两部分的多数元素a1和a2,并从中选出正确的多数元素。

解答

哈希表

代码略。

分治

// [min, max]范围内计数
const countRange = (nums, target, min, max) => {
  let count = 0
  for (let i = min; i <= max; i++) {
    if (nums[i] === target) {
      count++
    }
  }
  return count
}
// 辅助函数
const helper = (nums, left, right) => {
  // 递归终止条件
  if (left === right) {
    return nums[left]
  }
  const mid = Math.floor((right - left) / 2) + left
  const leftMajor = helper(nums, left, mid)
  const rightMajor = helper(nums, mid + 1, right)
  // 相等
  if (leftMajor === rightMajor) {
    return leftMajor
  }
  // 不等
  const leftMajorCount = countRange(nums, leftMajor, left, right)
  const rightMajorCount = countRange(nums, rightMajor, left, right)
  return leftMajorCount > rightMajorCount ? leftMajor : rightMajor
}
// 求解
var majorityElement = function(nums) {
  return helper(nums, 0, nums.length - 1)
};

复杂度分析:

  • 时间复杂度:O(nlogn)

  • 空间复杂度:O(logn)

参考