1984. 学生分数的最小差值

该篇博客探讨了一个算法问题,涉及给定数组和整数k,目标是找到数组中k个数的子集,使得子集内最高分和最低分的差值最小。通过排序数组和使用滑动窗口的方法,可以有效地找到最小差值。此外,博客还讨论了当不能改变原数组顺序时的解决方案,利用单调队列实现滑动窗口,以保持子区间内最大值和最小值的更新。
摘要由CSDN通过智能技术生成

1984. 学生分数的最小差值

难度简单58

给你一个 下标从 0 开始 的整数数组 nums ,其中 nums[i] 表示第 i 名学生的分数。另给你一个整数 k 。

从数组中选出任意 k 名学生的分数,使这 k 个分数间 最高分 和 最低分 的 差值 达到 最小化 。

返回可能的 最小差值 。

示例 1:

输入:nums = [90], k = 1
输出:0
解释:选出 1 名学生的分数,仅有 1 种方法:
- [90] 最高分和最低分之间的差值是 90 - 90 = 0
可能的最小差值是 0

示例 2:

输入:nums = [9,4,1,7], k = 2
输出:2
解释:选出 2 名学生的分数,有 6 种方法:
- [9,4,1,7] 最高分和最低分之间的差值是 9 - 4 = 5
- [9,4,1,7] 最高分和最低分之间的差值是 9 - 1 = 8
- [9,4,1,7] 最高分和最低分之间的差值是 9 - 7 = 2
- [9,4,1,7] 最高分和最低分之间的差值是 4 - 1 = 3
- [9,4,1,7] 最高分和最低分之间的差值是 7 - 4 = 3
- [9,4,1,7] 最高分和最低分之间的差值是 7 - 1 = 6
可能的最小差值是 2

提示:

  • 1 <= k <= nums.length <= 1000
  • 0 <= nums[i] <= 105

思路:由于是任意取k个,那么很明显,我们将数组排序后,枚举长度为k的子区间,最终答案一定由这些产生,因为排序可以保证排序后在一个长度为k的连续区间内,头尾的差一定是最小的。

理解:假设我们取排序后数组的一个长度为k的子数组部分,现在我们考虑将其中一个值替换掉,那么由于排序后的数值是连续的,因此被替换的数只可能是这个区间的端值(即最大值或者最小值);我们不讨论和端值相等的情况,因为这种情况下替换和不替换带来的结果是一样的。

不妨设是最大值被替换了,那么替换后的新值一定比原来的值要大,此时我们可以选择把原区间内部的最小值丢弃,而把刚刚被替换的端值重新加入,我们看看发生了什么?

原区间:i,i+1,...,j

替换最大值:i,i+1,...,j+1

丢弃最小值,重新放入被替换的端值:i+1,...,j,j+1

可以发现,这就对应了在有序数组上把k窗口向后移动了一步

换个思路理解:排序后的数组,其中的数是按照顺序紧密靠在一起的,如果某个值发生了替换,在不考虑重复端值的情况下,一定会使得原本紧密的数中间拉开了距离,即导致最小值和最大值差值变大。

以上述数轴为例,假设原来要判断的区间为第一第二第三,现在将第三替换为第四,这必定会拉大最小值和最大值的差。 

class Solution {
public:
    
    int minimumDifference(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end());
        int i, j, min_diff = nums[nums.size() - 1];
        for(i = 0; i + k <= nums.size(); ++ i){
            j = i + k - 1;
            min_diff = min(min_diff, nums[j] - nums[i]);
        }
        return min_diff;
    }

};

进阶:如果本题改成不能改变原数组顺序,不再是任意选取呢?很显然我们需要枚举出所有长度为k的子区间,然后求出区间内的最大值和最小值做差。对于这种问题,很显然可以用滑动窗口来解决,我们用一个单调递增队列来维护最小值,用一个单调递减队列来维护最大值,当前索引可以产生一个区间的时候,就从两个队列头取出元素并做差,然后更新答案。

class Solution {
public:

    int minimumDifference(vector<int>& nums, int k) {
        //
        struct NumNode{
            int index, num;
        };
        queue<NumNode> min_window, max_window;
        int i, min_diff = 0x3f3f3f3f;
        for(i = 0; i < nums.size(); ++ i){
            while(!min_window.empty() && (i - min_window.front().index >= k || min_window.front().num >= nums[i])){
                min_window.pop();
            }
            min_window.push(NumNode{i, nums[i]});

             while(!max_window.empty() && (i - max_window.front().index >= k || max_window.front().num <= nums[i])){
                max_window.pop();
            }

            max_window.push(NumNode{i, nums[i]});

            if(i >= k - 1){
                cout << max_window.front().num << " " << min_window.front().num << endl;
                min_diff = min(min_diff, max_window.front().num - min_window.front().num);  
            }

        }
        return min_diff;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值