LeetCode 3634. 使数组平衡的最少移除数目【排序+滑动窗口】1453

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

给你一个整数数组 nums 和一个整数 k

如果一个数组的 最大 元素的值 至多 是其 最小 元素的 k 倍,则该数组被称为是 平衡 的。

你可以从 nums 中移除 任意 数量的元素,但不能使其变为 数组。

返回为了使剩余数组平衡,需要移除的元素的 最小 数量。

注意:大小为 1 的数组被认为是平衡的,因为其最大值和最小值相等,且条件总是成立。

示例 1:

java 复制代码
输入:nums = [2,1,5], k = 2
输出:1

解释:

  • 移除 nums[2] = 5 得到 nums = [2, 1]
  • 现在 max = 2, min = 1,且 max <= min * k,因为 2 <= 1 * 2。因此,答案是 1。

示例 2:

java 复制代码
输入:nums = [1,6,2,9], k = 3
输出:2

解释:

  • 移除 nums[0] = 1nums[3] = 9 得到 nums = [6, 2]
  • 现在 max = 6, min = 2,且 max <= min * k,因为 6 <= 2 * 3。因此,答案是 2。

示例 3:

java 复制代码
输入:nums = [4,6], k = 2
输出:0

解释:

  • 由于 nums 已经平衡,因为 6 <= 4 * 2,所以不需要移除任何元素。

提示:

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^9
  • 1 <= k <= 10^5

解法 排序+滑动窗口

由于我们只关心剩余元素的最小最大值,不关心元素的顺序,所以可从小到大排序,方便后续计算。

排序后,要想整个数组平衡,我们必须让数组左右两端的最大最小值满足题意,不满足的话,需要去掉最大值或最小值。到底是去掉最大值还是最小值呢?我们还要满足移除最少元素个数剩余最大元素个数)的题意。

此时本题和\[LeetCode 1423. 可获得的最大点数【定长滑窗,逆向和正向思维】1574]十分类似,1423题也只能从开头或末尾拿牌,但该题能拿走的牌数是固定的,本题能移除的元素个数则是不定的。为了移除最少元素,我们要计算排序后平衡子数组的最大长度

排序后,枚举最大值 m x = n u m s i mx = numsi mx=numsi ,那最小值为 m n = n u m s l e f t mn = numsleft mn=numsleft 必须满足 m n × k ≥ m x mn \times k \ge mx mn×k≥mx在 m n , m x mn, mx mn,mx 中的元素保留,其余元素丢掉。由于排序了,所以这些元素在数组中是连续 的,问题转换为一个标准的滑动窗口 模型。如果不满足上式,就把 l e f t left left 加一,直到满足上式。

内层循环结束后,用窗口长度 i − l e f t + 1 i - left + 1 i−left+1 更新保留元素个数的最大值 m a x S a v e maxSave maxSave 。

java 复制代码
class Solution {
    public int minRemoval(int[] nums, int k) {
        Arrays.sort(nums);
        int maxSave = 0;
        int left = 0;
        for (int i = 0; i < nums.length; i++) {
            while ((long) nums[left] * k < nums[i]) {
                left++;
            }
            maxSave = Math.max(maxSave, i - left + 1);
        }
        return nums.length - maxSave;
    }
}
cpp 复制代码
class Solution {
public:
    int minRemoval(vector<int>& nums, int k) {
        ranges::sort(nums);
        int max_save = 0, left = 0;
        for (int i = 0; i < nums.size(); i++) {
            while (1LL * nums[left] * k < nums[i]) {
                left++;
            }
            max_save = max(max_save, i - left + 1);
        }
        return nums.size() - max_save;
    }
};
python 复制代码
class Solution:
    def minRemoval(self, nums: List[int], k: int) -> int:
        nums.sort()
        max_save = left = 0
        for i, mx in enumerate(nums):
            while nums[left] * k < mx:
                left += 1
            max_save = max(max_save, i - left + 1)
        return len(nums) - max_save
go 复制代码
func minRemoval(nums []int, k int) int {
	slices.Sort(nums)
	maxSave, left := 0, 0
	for i, mx := range nums {
		for nums[left]*k < mx {
			left++
		}
		maxSave = max(maxSave, i-left+1)
	}
	return len(nums) - maxSave
}
rust 复制代码
impl Solution {
    pub fn min_removal(mut nums: Vec<i32>, k: i32) -> i32 {
        nums.sort_unstable();
        let mut max_save = 0;
        let mut left = 0;
        for (i, &x) in nums.iter().enumerate() {
            while (nums[left] as i64) * (k as i64) < x as i64 {
                left += 1;
            }
            max_save = max_save.max(i - left + 1);
        }
        (nums.len() - max_save) as _
    }
}
js 复制代码
var minRemoval = function(nums, k) {
    nums.sort((a, b) => a - b);
    const n = nums.length;
    let maxSave = 0;
    let left = 0;
    for (let i = 0; i < n; i++) {
        while (nums[left] * k < nums[i]) {
            left++;
        }
        maxSave = Math.max(maxSave, i - left + 1);
    }
    return n - maxSave;
};
c 复制代码
#define MAX(a, b) ((b) > (a) ? (b) : (a))

int cmp(const void* a, const void* b) {
    return *(int*)a - *(int*)b;
}

int minRemoval(int* nums, int numsSize, int k) {
    qsort(nums, numsSize, sizeof(int), cmp);
    int max_save = 0;
    int left = 0;
    for (int i = 0; i < numsSize; i++) {
        while (1LL * nums[left] * k < nums[i]) {
            left++;
        }
        max_save = MAX(max_save, i - left + 1);
    }
    return numsSize - max_save;
}

复杂度分析:

  • 时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn) ,其中 n n n 是 n u m s nums nums 的长度。瓶颈在排序上。
  • 空间复杂度: O ( 1 ) O(1) O(1) 。忽略排序的栈开销。
相关推荐
沐籽李15 小时前
Proteina-Complexa:NVIDIA 如何把蛋白 Binder 设计推进到全原子生成时代?
大数据·人工智能·算法·英伟达·蛋白质生成
落羽的落羽15 小时前
【项目】JsonRpc框架——开发实现2(业务层)
linux·数据结构·c++·人工智能·算法·json·动态规划
h_a_o777oah15 小时前
2026 蓝桥杯软件 C++B组 国赛比赛经历及备赛建议
c++·经验分享·算法·蓝桥杯
酉鬼女又兒16 小时前
零基础入门计算机网络:点对点协议PPP、媒体接入控制基本概念、静态划分信道技术、CSMA/CD与CSMA/CA协议全面详解
服务器·网络·网络协议·计算机网络·职场和发展·求职招聘·媒体
lightqjx16 小时前
【算法】数据结构_并查集
数据结构·算法·并查集
小雨下雨的雨16 小时前
鸿蒙PC Electron框架实现流体气泡模拟器
前端·人工智能·算法·华为·electron·鸿蒙
txzrxz16 小时前
广度优先搜索详解(BFS)
算法·宽度优先
酉鬼女又兒16 小时前
零基础快速入门IP编址计算练习题详解:从基础到实战
网络·网络协议·tcp/ip·计算机网络·考研·职场和发展·分类
8Qi816 小时前
LeetCode 198:打家劫舍(House Robber)—— 题解 ✅
算法·leetcode·动态规划
无限码力16 小时前
华为非AI方向0603笔试真题-爆破小游戏(详细思路+多语言题解)
算法·华为·华为笔试真题·华为非ai笔试真题