这是我参与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)。