加减操作使数组中至少有k个数相同(贪心)

题目描述:

一维数组中由n个数,执行加减操作,目标是使n个数中至少有k个数相同,问最少需要多少次才能达到目标。加减操作的规则是:

1)每次对最大的数减1

2)每次对最小的数加1

3)每次只能完成上述两个操作的一个。

输入描述:第一行为n和k,第二行为数组的值。

输出描述:最少需要多少次操作才能达到目标。

示例输入:6 5 1 2 2 4 2 3

输出:3

题解参考:(先Mark一下,代码还没看懂,以后学完了贪心再回来看看!)

package acm;
 
import java.util.*;
 
public class Main {
    public static void main(String[] args) {
        Scanner sc=new Scanner(System.in);
        int n=sc.nextInt(),k=sc.nextInt();
        int sum=0;
        int[] data=new int[n];
        for(int i=0;i<n;i++){
            data[i]=sc.nextInt();
            sum+=data[i];
        }
        Arrays.sort(data);
        int p=-1;
        int ans=Integer.MAX_VALUE;
        int pre=0;
        //最终的结果肯定是要有某个数出现的个数>=k,把这个数叫做基准数
        for(int i=0;i<n;){
            p=i;
            while(p<n&&data[p]==data[i]){
                sum-=data[i];
                p++;
            }
            int ned=Math.max(0,k-p+i);
            //ned的含义是目前如果让data[i]作为基准数,还差多少个达到k,如果为0说明已经超过k个了,也就是说不需要任何操作就可以满足题意要求
            if(ned==0){
                ans=0;
//                break;
            }
            if(p>=k){//p>=k说明以data[i]为基准数,调整下标i之前的数到达基准数,就可以满足题意,求出这时候需要操作个数
                ans=Math.min(ans,i*data[i]-pre-(i-ned));//pre记录的是下标i之前的所有数字之和,要把下标i之前的数调整到等于基准数data[i],操作次数=i*data[i]-pre
                //同时要考虑只需要k个满足就可以了,也就是这时候可以有i-ned不满足,也就是i-ned个数可以调整等于data[i]-1,所以要减去i-ned.
            }
            if(n-i>=k){//i往后的数字降到data[i]就直接可以满足题意,与p>=k类似,只是调整方向不一样
                ans=Math.min(ans,sum-(n-p)*data[i]-(n-p-ned));//
            }
            ans=Math.min(ans,i*data[i]-pre+sum-(n-p)*data[i]-(n-(p-i)-ned));//同时需要判断两边都做操作的情况下调整的个数
            pre+=data[i]*(p-i);//pre加上data[i]乘以data[i]的个数,那么下一步i=p的时候pre就代表i之前所有数之和。
            i=p;
        }
        System.out.println(ans);
    }
 
}
Demo 1
public class Test {
 
    public static void main(String[] args) {
        // input
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        int k = scanner.nextInt();
        int[] data = new int[n];
        for(int i=0; i<n; i++) {
            data[i] = scanner.nextInt();
        }
        Arrays.sort(data);
 
        // cal result from two paths
        int res1 = 0;
        int res2 = 0;
        for (int i = 0; i < k; i++) {
            // 若把所有较小高度补到第 k 大的高度,需要的步数
            res1 += (data[k-1] - data[i]);
            // 若把所有较大高度削到倒数第 k 大的高度,需要的步数
            res2 += (data[n-1-i] - data[n-1-k+1]);
        }
        int index1=k, index2=n-1-k;
        // 如果第 k 大的高度 h1 在后面的数值中还重复出现了 m 次
        // 则说明多进行了 m 次操作
        while(index1<n && data[index1++]==data[k-1]) res1--;
        // 如果倒数第 k 大的高度 h2 在后面的数值中还重复出现了 l 次
        // 则说明多进行了 l 次操作
        while(index2>=0 && data[index2--]==data[n-1-k+1]) res2--;
        // 如果 res1 和 res2 中有负值
        // 则说明在一开始第 k 大或倒数第 k 大的高度就已经有了 k 个
        // 此时返回0
        System.out.println(Math.max(0, Math.min(res1, res2)));
    }
 
}
Demo 2
public class Test {
    public static void main(String[] args) {
        Scanner scan=new Scanner(System.in);
        int N=scan.nextInt();
        int k=scan.nextInt();
        scan.nextLine();
        int [] arr=new int[N];
        for(int i=0;i<N;i++){
            arr[i]=scan.nextInt();
        }
        Arrays.sort(arr);
        int result = beginGame(arr, N, k);
        System.out.println(result);
    }

    private static int beginGame(int[] arr, int N, int k) {

        int path1 = 0;
        int path2 = 0;

        for (int i=0;i<k;i++){
            path1+=arr[k-1]-arr[i];
            path2+=arr[N-i-1]-arr[N-k];
        }

        int index1=k;
        int index2=N-k-1;
        while(index1<N&&arr[index1++]==arr[k-1]){
           path1--;
        }
        while(index2>=0&&arr[index2--]==arr[N-k]){
            path2--;
        }
        return Math.min(path1, path2);
    }
}
Demo 3
原文地址:https://www.cnblogs.com/HuangYJ/p/12809514.html