洞悉滑动窗口、前缀和与二分的巧妙结合:在 JavaScript 中化解 LeetCode 1658
2023-09-30 23:49:41
踏入 LeetCode 1658 的难题之门
LeetCode 1658 题意直白而引人入胜:给定一个整数数组 nums
,其中每个元素都代表一种操作。具体而言,你可以对数组执行两种操作:
- 将数组中的任意一个元素减 1。
- 将数组中的任意两个相邻元素互换。
你的目标是找出将数组中的所有元素都减到 0 所需的最小操作次数。
乍一看,这个难题似乎有些棘手,但不要气馁,掌握了正确的算法技巧,你将所向披靡。
滑动窗口:划定可控范围
滑动窗口算法就像一把锋利的双刃剑,它可以动态地遍历数组,在有限的窗口内执行操作。在本题中,滑动窗口将帮助我们聚焦于当前需要处理的元素。
我们从窗口的左端开始,将窗口向右滑动,同时执行以下步骤:
- 计算当前窗口内元素的和 :这将告诉我们窗口内有多少个元素需要减到 0。
- 计算窗口内最大元素 :这将告诉我们窗口内最需要优先减小的元素。
前缀和:快速查询元素之和
前缀和是一种预处理技术,它可以快速计算数组中任意连续元素的和。在本题中,我们利用前缀和来计算滑动窗口内元素的和。
具体而言,我们创建一个前缀和数组 prefix
,其中 prefix[i]
表示数组 nums
中前 i
个元素的和。有了前缀和数组,我们就可以使用常数时间复杂度 O(1) 计算任意滑动窗口内元素的和。
二分查找:精准定位最小值
二分查找是一种高效的搜索算法,它可以快速找到有序数组中的目标元素。在本题中,我们将利用二分查找来寻找数组 nums
中的最小元素。
为什么需要寻找最小元素呢?因为在任何给定的时刻,我们需要优先减小数组中最小的元素。这将确保我们在每次操作中获得最大的收益。
三剑合璧:算法的优雅舞步
现在,让我们将这三种算法利器巧妙地融合在一起:
- 滑动窗口 :动态地遍历数组,聚焦于当前需要处理的元素。
- 前缀和 :快速计算滑动窗口内元素的和。
- 二分查找 :精准地找到数组中的最小元素。
有了这些算法的加持,我们可以设计出一个高效的算法来解决 LeetCode 1658:
- 初始化前缀和数组
prefix
。 - 初始化滑动窗口的左端指针
left
和右端指针right
,并计算初始窗口的和。 - 进入循环,执行以下步骤:
- 计算当前窗口的最小元素
min
。 - 使用二分查找找到数组中第一个大于或等于
min
的元素。 - 更新滑动窗口的左端指针
left
为找到的元素的索引。 - 计算新窗口的和。
- 更新最小操作次数
count
。
- 计算当前窗口的最小元素
- 退出循环,返回
count
。
代码实现:JavaScript 的算法之美
掌握了算法的精髓,让我们将其转化为优雅的 JavaScript 代码:
/**
* LeetCode 1658. 将 x 减到 0 的最小操作数
*
* @param {number[]} nums
* @return {number}
*/
const minOperations = function (nums) {
// 初始化前缀和数组
const prefix = [0];
for (let i = 0; i < nums.length; i++) {
prefix.push(prefix[i] + nums[i]);
}
// 初始化滑动窗口
let left = 0;
let right = 0;
let sum = 0;
// 初始化最小操作次数
let count = 0;
while (right < nums.length) {
// 计算当前窗口的最小元素
let min = nums[right];
for (let i = left; i <= right; i++) {
min = Math.min(min, nums[i]);
}
// 使用二分查找找到数组中第一个大于或等于 min 的元素
let index = binarySearch(nums, min);
// 更新滑动窗口的左端指针
left = index;
// 计算新窗口的和
sum = prefix[right + 1] - prefix[left];
// 更新最小操作次数
count += Math.ceil(sum / min);
// 移动右端指针
right++;
}
return count;
};
// 二分查找函数
const binarySearch = function (nums, target) {
let left = 0;
let right = nums.length - 1;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (nums[mid] >= target) {
right = mid - 1;
} else {
left = mid + 1;
}
}
return left;
};
结语
通过巧妙地结合滑动窗口、前缀和和二分查找,我们征服了 LeetCode 1658 的挑战。这道难题不仅考验了我们的算法技能,更拓宽了我们解决复杂问题的思路。
掌握了这些算法利器,你将如虎添翼,在算法的世界中游刃有余。继续探索,不断挑战自己,算法的殿堂将为你敞开大门!