【上分日记】第376场周赛(中位数 + 排序)
2023-12-18 15:36:51
    		前言
?本周的力扣只写出来了两道题,都较为简单,之后的两道题个人觉得比较难想,因为我做不出来(hhh,菜鸡勿喷)。今天就来具体的总结一下。
正文
1.100161. 划分数组并满足最大差限制
- 题目链接: 100161. 划分数组并满足最大差限制
- 下面我们直接给出思路,题目如果感兴趣可以点开链接查看。
?这道题虽然做出来了,但写的的代码不太优雅,因此稍稍总结一下。
- 我们只需排序,不断取出三个数进行比较即可。
- 计算三个数的最大值与最小值的差。
- 如果差小于等于k,则添加到数组中。
- 如果差大于k,则不满足情况直接返回空即可。
- 代码:
class Solution {
public:
    vector<vector<int>> divideArray(vector<int>& nums, int k) 
    {
        vector<vector<int>> ret;
        sort(nums.begin(),nums.end());
        for(int i = 0; i < nums.size(); i += 3)
        {
            //如果最大值与最小值的差,如果大于k,直接返回空数组。
            if(nums[i + 2] - nums[i] > k)
                return vector<vector<int>>();
            //添加到数组中。
            vector<int> tmp;
            for(int j = i; j < i + 3; j++)
                tmp.push_back(nums[j]);
            ret.push_back(tmp);
        }
        return ret;
    }
};
2.100151. 使数组成为等数数组的最小代价
- 题目链接:使数组成为等数数组的最小代价
- 这道题卡了半天,还做不出来,真的寄了。
- 题目思路:
- 准备工作,快速枚举1到109 的回文数。
如何枚举呢?我们可以这样进行思考:
- 回文数的长度是分奇数长度和偶数长度的。
- 奇数长度的回文数,就等于选中间的数,然后拆成两半。
- 偶数长度的回文数,就等于直接将数砍成两半。
- 图解:
如何实现呢?我们可以这样进行控制进行严格递增。
- 每次枚举长度相同的数。
- 比如 1 - 9 数字长度是相同的,且为1。10, 99 长度是相同的,且为2。
如何将数字分成两种情况进行讨论呢?
- 此处我们以121进行举例:
  
- 实现代码:
    vector<int> Palindromic_number;//存放回文数
    //枚举函数
    void enumerate()
    {
        //区间左闭右开
        for (int i = 1; i < 1e5; i *= 10)
        {
            for (int j = i; j < i * 10; j++)//枚举长度相同的数
            {
                int x = j;
                int t = j / 10;//枚举奇数位
                while (t)
                {
                    x *= 10;
                    int tmp = t % 10;
                    x += tmp;
                    t /= 10;
                }
                //添加到数组中
                Palindromic_number.push_back(x);
            }
            if(i < 1e4)
            {
                for (int j = i; j < i * 10; j++)
                {
                    int x = j;
                    int t = j ;//枚举偶数位
                    while (t)
                    {
                        if((long long)x * 10  > INT_MAX)
                            cout << j << endl; 
                        x *= 10;
                        int tmp = t % 10;
                        x += tmp;
                        t /= 10;
                    }
                    //添加到数组中
                    Palindromic_number.push_back(x);
                }
            }
        }
    }
- 分析题目
- 如何做到 " 代价 " 最小 ?
- 在解题的过程中博主想到的平均数与中位数,但不知道用哪一个,下面我们来具体的分析一下。
- 平均数(代价无法达到最小)。
下面我们来简单思考一下为什么?
先简单的举个例子:

- 平均数是用来反映
整体的水平的,比如两个地区的收入。- 可见平均数不能反应
部分的情况,如果把马云的工资给各位分一分,相一个大学生也是有比较不错的收入的。
下面我们再来分析分析中位数为什么能呢?

- 结合回文数。
我们的目的就变成的,找与中位数最相近的回文数。
- 此处我们还要接着分析:
- 如果中位数恰好为回文数,则直接计算代价即可。
- 如果中位数不为回文数,我们要从附近的范围开始找。
-  以上默认数组以排好序。 
-  实现代码: 
class Solution {
public:
    vector<int> Palindromic_number;//存放回文数
    //枚举函数
    void enumerate()
    {
        //区间左闭右开
        for (int i = 1; i < 1e5; i *= 10)
        {
            for (int j = i; j < i * 10; j++)//枚举长度相同的数
            {
                int x = j;
                int t = j / 10;//枚举奇数位
                while (t)
                {
                    x *= 10;
                    int tmp = t % 10;
                    x += tmp;
                    t /= 10;
                }
                //添加到数组中
                Palindromic_number.push_back(x);
            }
            if(i < 1e4)
            {
                for (int j = i; j < i * 10; j++)
                {
                    int x = j;
                    int t = j ;//枚举偶数位
                    while (t)
                    {
                        if((long long)x * 10  > INT_MAX)
                            cout << j << endl; 
                        x *= 10;
                        int tmp = t % 10;
                        x += tmp;
                        t /= 10;
                    }
                    //添加到数组中
                    Palindromic_number.push_back(x);
                }
            }
        }
    }
    int near_palind(int num)
    {
        int index = 0;
        for(int i = 0; i < Palindromic_number.size(); i++)
        {
            if(Palindromic_number[i] >= num)
            {
                index = i;
                break;
            }
        }
        return index;
    } 
    long long cost(vector<int> &nums,int target)
    {
        long long c = 0;
        for(long long e : nums)
            c += abs((long long)target - e);
        return c;
    }
    long long minimumCost(vector<int>& nums) 
    {
        //快速枚举回文数
        enumerate();
        //添加一个数防止Palindromic_number访问越界
        Palindromic_number.push_back(1e9 + 1);
        //排序数组
        sort(nums.begin(),nums.end());
        //找离中位数最近的回文数
        int sz = nums.size();
        int left = nums[(sz - 1) / 2]; 
        int right = nums[sz / 2];
        //如果为偶数个,(sz - 1) / 2 != sz / 2;
        //如果为奇数数个,(sz - 1) / 2 == sz / 2;
        //找到第一个大于等于 left的回文数的下标.
        int index = near_palind(left);
        //先计算当前的l_cost
        long long l_cost = cost(nums,Palindromic_number[index]);
        //如果index指向的值小于等于right。
        if(Palindromic_number[index] <= right)
        {
            //说明在[left,right]之间直接返回即可
            return l_cost;
        }
        long long r_cost = cost(nums,Palindromic_number[index-1]);
		/*
		否则在Palindromic_number[index]和Palindromic_number[index-1]
		之间。
		*/
        return min(r_cost,l_cost);
    }
};
- 暴力枚举需注意——index-1可能会出现负数的情况。
3.2968. 执行操作使频率分数最大
- 题目链接:2968. 执行操作使频率分数最大
- 此题与上面的一道题的思路大致一样。
- 除此之外,多的两点是
滑动窗口 + 前缀和
- 先来简单的解析一下。
- 首先让每一位数都增加与减少一,限定操作次数,从而使这个数组的相同的数最多。
- 每一位数增加或减少一 + 限定操作次数 == 所有数离某一个数距离和最近。这里的
限定操作次数 < = > 所有数据中位数的距离和。这两个数要进行比较。- 而且一个数组向中位数转换的操作次数是最小的。
- 因此问题转换为是求一个子数组中,向中位数转换的距离和,目的是问这个在限定操作次数只内,这个子数组的最长的长度。
- 具体思路。
首先我们先来讨论一下,为什么要用滑动窗口?
- 滑动窗口使用条件:满足单调性
- 我们再来看维护子数组的区间,right 向 右移动,这里因为新来的这个数,所有数据中位数的距离和在增加,即所需的操作次数在增加。
- 在来看,left右移,因为要减少一个数,这个距离和就缺少了left执行的数距中位数的距离。因此所需操作次数减少。
- 因此满足单调性,我们可以使用滑动窗口。
- 滑动窗口的使用方法,枚举右/左端点,求右/左端点。本题为枚举右端点找左端点。
- 首先要想使子数组操作次数最小,我们先要对数组进行排序。(sort)
- 其次我们要维护一个区间是这段区间满足的,此时向中位数转换的距离和小于等于限定次数。
- 因此求出这段区间距中位数距离和。进行判断,如果满足则进行与当前在限定操作次数内的最长子数组求最大值。
我们还有一个点:如何快速求所有数距离中位数的和。

- 很明显:蓝色面积 + 绿色面积,即为距中位数的距离和。
- 细节:偶数中位数取哪一个都不影响,切入点——距离和,即代价不变,具体详见第二题。
- 实现代码:
class Solution {
public:
    int maxFrequencyScore(vector<int>& nums, long long k) 
    {
        //先进行排序
        sort(nums.begin(),nums.end());
        int sz = nums.size();
        //求前缀和
        vector<long long> s(sz + 1);
        for(int i = 1; i <= sz; i++)
            s[i] = s[i-1] + nums[i-1];
        
    
        auto distance_sum = [&](int left,int right)->long long 
        {
            int mid_i  = (left + right) / 2; 
            
            long long s_left = (long long)nums[mid_i] * \
            (mid_i - left) - (s[mid_i] - s[left]);
            
            long long s_right = (s[right+1] - s[mid_i+1]) - \
            (long long)nums[mid_i] * (right - mid_i);
            return s_left + s_right;
        };
        //滑动窗口,枚举右端点,找左端点
        int left = 0;
        int ans = 0;
        for(int right = 0; right < sz; right++)
        {
        
            while(distance_sum(left,right) > k) 
            	left++;
            	
            ans = max(ans,right - left + 1);
        }
        return ans;
    }
};
总结
- 所用算法知识:
- 排序
- 中位数
- 回文数
- 滑动窗口
- 前缀和
?每周的周赛总结,今天就到这里了,我是舜华,期待与你的下一次相遇!
    			文章来源:https://blog.csdn.net/Shun_Hua/article/details/135047764
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
    	本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!
