【备战秋招】每日一题:2023.08.19-美团-第五题-塔子哥的众数

通过计算数组元素的和、平均数和二分法找出操作策略,确保众数出现次数最多,输出最少操作次数。
摘要由CSDN通过智能技术生成

为了更好的阅读体检,可以查看我的算法学习网
在线评测链接:P1471

题目内容

塔子哥有一个长度为 n n n 的数组 a a a ,但是塔子哥很喜欢众数,所以他想改造这个数组,使得众数的出现次数尽可能多。

一次操作,塔子哥可以选择两个下标 i , j ( 1 ≤ i , j ≤ n , i ≠ j ) i, j (1\leq i,j\leq n, i\neq j) i,j(1i,jn,i=j),使得 a i a_i ai 1 1 1 a j a_j aj 1 1 1

现在塔子哥想问你,使得众数出现次数最多的情况下,最少的操作次数是多少。

输入描述

第一行,一个正整数 n ( 1 ≤ n ≤ 1 0 5 ) n(1\leq n\leq 10^5) n(1n105) ,表示数组 a a a 的大小

第二行, n n n 个正整数 a i ( 1 ≤ a i ≤ 1 0 9 ) a_i(1\leq a_i\leq 10^9) ai(1ai109)

输出描述

使得众数出现次数最多的情况下的最少操作次数。

样例

输入

3
1 2 3

输出

1

说明

a 1 a_1 a1 1 1 1 a 3 a_3 a3 1 1 1 ,一次操作得到 [2, 2, 2]

思路:思维

记录 s u m = ∑ i = 0 n − 1 a i sum = \sum\limits_{i=0}^{n-1} a_i sum=i=0n1ai

如果 s u m % n = 0 sum \% n =0 sum%n=0 ,则所有数都可以修改为 s u m n \frac{sum}{n} nsum

否则,必然可以将 n − 1 n-1 n1 个数通过操作修改为同样的数。
这是因为,假设使得 a [ 1 ] , a [ 2 ] ⋯   , a [ n − 2 ] , a [ n − 1 ] a[1],a[2] \cdots,a[n-2], a[n - 1] a[1],a[2],a[n2],a[n1] 都变为 1 1 1 ,则 a [ i ] a[i] a[i] 变为 1 1 1 的增加或者减少,都由 a [ 0 ] a[0] a[0] 来配对 。

现在问题转换为:

  1. 将哪个数作为配对数
  2. n − 1 n-1 n1 个数都转换为哪些数

对于问题 1 :每个数都有可能,枚举每个数作为配对数
对于问题 2 :转换为平均数,这样我们就可以尽可能使得 配对数使用的次数尽可能少

为什么转换为平均数就是最少的?

首先,确定最终每个数都转换为 target

那么总的操作为:
l t lt lt 个小于 t a r g e t target target 的数,这些数的和为 l t _ s u m lt\_sum lt_sum ,则 x = l t × t a r g e t − l t _ s u m x = lt \times target - lt\_sum x=lt×targetlt_sum 是需要加 1 1 1 的部分
g t gt gt 个大于 t a r g e t target target 的数,这些数的和为 g t _ s u m gt\_sum gt_sum ,则 y = g t _ s u m − g t × t a r g e t y = gt\_sum - gt \times target y=gt_sumgt×target 是需要减 1 1 1 的部分

最后的总操作数为 m a x ( x , y ) max(x, y) max(x,y)

所以我们要保证这两者尽可能接近,这样 m a x ( x , y ) max(x, y) max(x,y) 才能尽可能小,这时候 t a r g e t target target 就得是这 n − 1 n-1 n1 个数的平均数 a v e r a g e average average 了 。

假设 x _ a v e x\_ave x_ave 为 小于 t a r g e t target target 的数增加到 a v e r a g e average average 的部分, y _ a v e y\_ave y_ave 为 大于 t a r g e t target target 的数减少到 a v e r a g e average average 的部分,此时 x _ a v e = y _ a v e x\_ave = y\_ave x_ave=y_ave

如果我们调整 t a r g e t < a v e r a g e target < average target<average ,则大于 t a r g e t target target 的部分必然需要大于 y _ a v e y\_ave y_ave 次减 1 1 1 才能到达 t a r g e t target target

如果我们调整 t a r g e t > a v e r a g e target > average target>average ,则小于 t a r g e t target target 的部分必然需要大于 x _ a v e x\_ave x_ave 次加 1 1 1 才能到达 t a r g e t target target

所以答案 t a r g e t target target 就是 a v e r a g e average average

注意:当 a v e r a g e average average 不是整数时,有两个可能,一个是 f l o o r ( a v e r a g e ) floor(average) floor(average) ,一个是 c e i l ( a v e r a g e ) ceil(average) ceil(average) ,这两者都考虑一下即可 。

时间复杂度: O ( n log ⁡ n ) O(n\log n) O(nlogn)

代码

C++

#include <bits/stdc++.h>
using namespace std;

typedef long long ll;
int main()
{
    ios::sync_with_stdio(false);
    cin.tie(nullptr);

    int n;
    cin >> n;

    vector<int> a(n);

    ll sum = 0;
    for (int i = 0; i < n; ++i) {
        cin >> a[i];
        sum += a[i];
    }

    // sum % n == 0,则 a[i] 都可以改为 ave = sum / n
    if (sum % n == 0) {
        ll ans = 0;
        ll ave = sum / n;
        for (int i = 0; i < n; ++i) {
            ans += abs(a[i] - ave);
        }
        cout << ans / 2 << "\n";
        return 0;
    }

    sort(a.begin(), a.end());

    vector<ll> pre(n + 1);
    for (int i = 0; i < n; ++i) {
        pre[i + 1] = pre[i] + a[i];
    }

    auto get_operation = [&](ll ave, ll cur_sum, int idx) {
        // 小于 ave 的部分,所以需要二分找到小于 ave 的最大的数
        int l = 0, r = n - 1;
        while (l < r) {
            int mid = (l + r + 1) >> 1;
            if (a[mid] < ave) l = mid;
            else r = mid - 1;
        }

        // 计算小于 ave 的数的和 lt_sum ,小于 ave 的数的个数 lt_cnt
        ll lt_sum = 0;
        int lt_cnt = 0;
        // 计算大于等于 ave 的数的和 gt_sum ,大于等于 ave 的数的个数 gt_cnt
        ll gt_sum = 0;
        int gt_cnt = 0;

        // 说明 a[idx] >= ave ,属于大于等于 ave 的部分
        if (idx > l) {
            lt_sum = pre[l + 1];
            lt_cnt = l + 1;
            gt_sum = cur_sum - lt_sum;
            gt_cnt = n - 1 - lt_cnt;
        } else {
            // 说明 a[idx] < ave ,属于小于 ave 的部分
            gt_sum = pre[n] - pre[l + 1];
            gt_cnt = n - (l + 1);
            lt_sum = cur_sum - gt_sum;
            lt_cnt = n - 1 - gt_cnt;
        }

        // 小于 ave 的数需要增加
        ll x = ave * lt_cnt - lt_sum;
        // 大于等于 ave 的数需要减小
        ll y = gt_sum - ave * gt_cnt;

        // 取两者较大值
        return max(x, y);
    };

    auto get_ans = [&](int idx) {
        // n-1 个数的总和
        ll cur_sum = sum - a[idx];
        // 计算 n-1 个数的平均值
        ll ave = cur_sum / (n - 1);
        // floor(average) 
        ll ans = get_operation(ave, cur_sum, idx);

        if (cur_sum % (n - 1) != 0) {
            // ceil(average)
            ans = min(ans, get_operation(ave + 1, cur_sum, idx));
        }

        return ans;
    };

    ll ans = 1e18;
    for (int i = 0; i < n; ++i) {
        // 枚举每个数作为配对数
        ans = min(ans, get_ans(i));
    }

    cout << ans << "\n";

    return 0;
}

python

import math

n = int(input())
a = list(map(int, input().split()))
s = sum(a)
# sum % n == 0,则 a[i] 都可以改为 ave = sum / n
if sum(a) % n == 0:
    ave = s // n
    ans = sum(abs(x - ave) for x in a) // 2
    print(ans)
    exit()

a.sort()

pre = [0] * (n + 1)
for i in range(n):
    pre[i + 1] = pre[i] + a[i]


def get_operation(ave, cur_sum, idx):
    l = 0
    r = n - 1
    while l < r:
        mid = (l + r + 1) // 2
        if a[mid] < ave:
            l = mid
        else:
            r = mid - 1

    lt_sum = 0
    lt_cnt = 0
    gt_sum = 0
    gt_cnt = 0

    if idx > l:
        lt_sum = pre[l + 1]
        lt_cnt = l + 1
        gt_sum = cur_sum - lt_sum
        gt_cnt = n - 1 - lt_cnt
    else:
        gt_sum = pre[n] - pre[l + 1]
        gt_cnt = n - (l + 1)
        lt_sum = cur_sum - gt_sum
        lt_cnt = n - 1 - gt_cnt

    x = ave * lt_cnt - lt_sum
    y = gt_sum - ave * gt_cnt

    return max(x, y)


def get_ans(idx):
    cur_sum = s - a[idx]
    ave = cur_sum // (n - 1)
    ans = get_operation(ave, cur_sum, idx)

    if cur_sum % (n - 1) != 0:
        ans = min(ans, get_operation(ave + 1, cur_sum, idx))

    return ans


ans = math.inf
for i in range(n):
    ans = min(ans, get_ans(i))

print(ans)

Java

import java.util.Arrays;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();

        int[] a = new int[n];

        long sum = 0;
        for (int i = 0; i < n; ++i) {
            a[i] = scanner.nextInt();
            sum += a[i];
        }

        // sum % n == 0,则 a[i] 都可以改为 ave = sum / n
        if (sum % n == 0) {
            long ans = 0;
            long ave = sum / n;
            for (int i = 0; i < n; ++i) {
                ans += Math.abs(a[i] - ave);
            }
            System.out.println(ans / 2);
            return;
        }

        Arrays.sort(a);

        long[] pre = new long[n + 1];
        for (int i = 0; i < n; ++i) {
            pre[i + 1] = pre[i] + a[i];
        }

        long ans = Long.MAX_VALUE;
        for (int i = 0; i < n; ++i) {
            ans = Math.min(ans, getAns(a, pre, sum, n, i));
        }

        System.out.println(ans);
    }

    private static long getOperation(long ave, long curSum, int[] a, long[] pre, int n, int idx) {
        int l = 0, r = n - 1;
        while (l < r) {
            int mid = (l + r + 1) >> 1;
            if (a[mid] < ave) l = mid;
            else r = mid - 1;
        }

        long ltSum = 0;
        int ltCnt = 0;
        long gtSum = 0;
        int gtCnt = 0;

        if (idx > l) {
            ltSum = pre[l + 1];
            ltCnt = l + 1;
            gtSum = curSum - ltSum;
            gtCnt = n - 1 - ltCnt;
        } else {
            gtSum = pre[n] - pre[l + 1];
            gtCnt = n - (l + 1);
            ltSum = curSum - gtSum;
            ltCnt = n - 1 - gtCnt;
        }

        long x = ave * ltCnt - ltSum;
        long y = gtSum - ave * gtCnt;

        return Math.max(x, y);
    }

    private static long getAns(int[] a, long[] pre, long sum, int n, int idx) {
        long curSum = sum - a[idx];
        long ave = curSum / (n - 1);
        long ans = getOperation(ave, curSum, a, pre, n, idx);

        if (curSum % (n - 1) != 0) {
            ans = Math.min(ans, getOperation(ave + 1, curSum, a, pre, n, idx));
        }

        return ans;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值