Leetcode周赛 6022. 将数组和减半的最少操作次数

175 阅读3分钟

Leetcode 6022. 将数组和减半的最少操作次数

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

1、题目📑

给你一个正整数数组 nums 。每一次操作中,你可以从 nums 中选择 任意 一个数并将它减小到 恰好 一半。(注意,在后续操作中你可以对减半过的数继续执行操作)

请你返回将 nums 数组和 至少 减少一半的 最少 操作数。

实例1

输入:nums = [5,19,8,1]

输出:3

解释:初始 nums 的和为 5 + 19 + 8 + 1 = 33 。

以下是将数组和减少至少一半的一种方法:

选择数字 19 并减小为 9.5 。

选择数字 9.5 并减小为 4.75 。

选择数字 8 并减小为 4 。

最终数组为 [5, 4.75, 4, 1] ,和为 5 + 4.75 + 4 + 1 = 14.75 。

nums 的和减小了 33 - 14.75 = 18.25 ,减小的部分超过了初始数组和的一半,18.25 >= 33/2 = 16.5 。

我们需要 3 个操作实现题目要求,所以返回 3 。

可以证明,无法通过少于 3 个操作使数组和减少至少一半。

实例2

输入:nums = [3,8,20]

输出:3

解释:初始 nums 的和为 3 + 8 + 20 = 31 。

以下是将数组和减少至少一半的一种方法:

选择数字 20 并减小为 10 。

选择数字 10 并减小为 5 。

选择数字 3 并减小为 1.5 。

最终数组为 [1.5, 8, 5] ,和为 1.5 + 8 + 5 = 14.5 。

nums 的和减小了 31 - 14.5 = 16.5 ,减小的部分超过了初始数组和的一半, 16.5 >= 31/2 = 16.5 。

我们需要 3 个操作实现题目要求,所以返回 3 。

可以证明,无法通过少于 3 个操作使数组和减少至少一半。

限制

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 107

2、思路🧠

方法一:优先队列

  1. 将数组按照大根堆的形式放入,每次对最大的数进行减半的操作,要求数组和恰好减到恰好一半并且最少的操作数。
  2. 求减少一半的 最少 操作数,肯定是对最大数进行减半的操作有最大的收益。

废话少说~~~~~上代码!

3、代码👨‍💻

第一次commit AC

class Solution {
    public int halveArray(int[] nums) {
        PriorityQueue<Double> q = new PriorityQueue<>(nums.length, new Comparator<Double>() {
            @Override
            public int compare(Double o1, Double o2) {
                int temp = (o2 - o1) > 0 ? 1 : - 1;
                return temp;
            }
        });
        double sum = 0;
        for(int i = 0; i < nums.length; i++){
            sum+=nums[i];
            q.offer(nums[i] * 1.0);
        }
        double now = sum / 2.0;
        int count = 0;
        while(now < sum) {
            double temp = q.poll();
            q.offer(temp / 2.0);
            sum -= temp / 2.0;
            count++;
        }
        return count;
    }
}

时间复杂度:O(N) N为数组 nums 的长度

空间复杂度:O(N)

image-20220321175908652

4、总结

该题目的对大根堆的结构进行灵活的运用,并且能够想到对最大数减半操作收益是最大的。 自建堆模板:

package com.cz.Sort;
​
import java.util.Arrays;
​
/**
 * @ProjectName: 大根堆
 * @Package: com.cz.Sort
 * @ClassName: Heap
 * @Author: 张晟睿
 * @Date: 2022/3/17 20:23
 * @Version: 1.0
 */
public class Heap {
    public static class MyMaxheap {
        private int heap[];
        private final int limit;
        private int heapSize;
​
        public MyMaxheap(int limit) {
            heap = new int [limit];
            this.limit = limit;
            heapSize = 0;
        }
​
        public boolean isEmpty() {
            return heapSize == 0;
        }
​
        public boolean isFull() {
            return heapSize == limit;
        }
​
        public void push(int value){
            if (isFull())
                throw new RuntimeException("heap is full");
            heap[heapSize] = value;
            heapInsert(heap, heapSize++);
​
        }
        public int pop(){
            int ans = heap[0];
            swap(heap, 0 , --heapSize);
            heapify(heap, 0, heapSize);
            return ans;
        }
​
        private void heapInsert(int[] arr, int index) {
            //当前index和父亲比较 当前index比其父亲大,交换
​
            //当前来的值和他的父亲节点比较
​
            //结束条件
            //arr[index] 不比  arr[index父] 大的时候
            //index = 0 来到了顶端
            while(arr[index] > arr[(index - 1) / 2]) {
                swap(arr, index , (index - 1) / 2);
                index = (index - 1) / 2;
            }
        }
​
        /**
         *
         * @param arr  需要调整为大根堆的数组
         * @param index 从index位置往下看,不断的下沉   停止条件:当前所有的孩子都不比我大,没有孩子了
         * @param heapSize  堆的大小
         */
        private void heapify(int[] arr, int index, int heapSize) {
            int left = 2 * index ^ 1; //左孩子的下标
            while(left < heapSize) { //左孩子下标不越界,即有孩子存在
​
                //左右两个孩子,谁大把谁给largest
                //右孩子胜出的情况下 ->1.有右孩子  2.有孩子的值比左孩子的值大
                //否则,就是左孩子胜出
                // 右孩子胜出   left + 1 < heapSize 右孩子不越界     arr[left + 1] > arr[left]  右孩子的值大于左孩子的值
                int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
​
                //largest两个孩子中最大孩子的下标
                //最大孩子的值和父亲的值进行pk 谁大把谁的下标给largest
                largest = arr[largest] > arr[index] ? largest : index;
                //两个孩子都pk不过父节点,此时不用下沉了
                if(largest == index) break;
                //largest,index交换
                swap(arr, largest, index);
                //index继续往下沉,继续找下一个左孩子
                index = largest;
​
                left = (2 * index) ^ 1;
            }
        }
​
        public void swap(int arr[], int i, int j){
            int temp = arr[i];
            arr[i] = arr[j];
            arr[j] = temp;
        }
    }
​
    public static void main(String[] args) {
       MyMaxheap mmp = new  MyMaxheap(10);
//        for (int i = 0; i < 1000; i++) {
//            if (mmp.isFull()) break;
//            mmp.push((int)(Math.random() * 100));
//        }
//        mmp.push(1);
//        mmp.push(3);
//        mmp.push(6);
//        mmp.push(2);
//        mmp.push(5);
//        mmp.push(9);
//        System.out.println(Arrays.toString(mmp.heap));
//        System.out.println(mmp.pop());
        int arr[] = new int[]{1,3,6,2,5,9};
        for (int i = arr.length - 1; i >=  0; i--) {
            mmp.heapify(arr, i , arr.length);
        }
    }
}

❤️‍来自专栏《LeetCode基础算法题》欢迎订阅❤️‍

厂长写博客目的初衷很简单,希望大家在学习的过程中少走弯路,多学一些东西,对自己有帮助的留下你的赞赞👍或者关注➕都是对我最大的支持,你的关注和点赞给厂长每天更文的动力。

对文章其中一部分不理解,都可以评论区回复我,我们来一起讨论,共同学习,一起进步!

原题链接:6022. 将数组和减半的最少操作次数 - 力扣(LeetCode)