2.将数组和减半的最少操作次数(medium)

1.题目链接:

2208. 将数组和减半的最少操作次数 - 力扣(LeetCode)2208. 将数组和减半的最少操作次数 - 给你一个正整数数组 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 = 15.5 。我们需要 3 个操作实现题目要求,所以返回 3 。可以证明,无法通过少于 3 个操作使数组和减少至少一半。 提示: * 1 <= nums.length <= 105 * 1 <= nums[i] <= 107https://leetcode.cn/problems/minimum-operations-to-halve-array-sum/description/2.题目描述:

给你一个正整数数组 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 <= 10^5

1 <= nums[i] <= 10^7

3. 解法(贪心):

贪心策略:

a. 每次挑选出「当前」数组中「最大」的数,然后「减半」;

b. 直到数组和减少到至少一半为止。​

为了「快速」挑选出数组中最大的数,我们可以利用「堆」这个数据结构。

Java算法代码:

class Solution {

    public int halveArray(int[] nums) {

        //创建一个大根堆

        PriorityQueue<Double> heap = new PriorityQueue<>((a,b) ->b.compareTo(a));

        double sum = 0.0;

        for(int x : nums){ //将元素都丢到堆中,并求出累加和

            heap.offer((double)x);

            sum += x;

        }

        sum /= 2.0; //先计算出目标和

        int count = 0;

        while(sum > 0){//依次取出堆顶元素减半,直到减到以前的一半以下

            double t =heap.poll() / 2.0;

            sum -= t;

            count ++;

            heap.offer(t);

        }

        return count;

    }

}

运行结果:

贪心思想:

这个题的思路很好想。

细节解释:

PriorityQueue<Double> heap = new PriorityQueue<>((a, b) -> b.compareTo(a));

这行代码创建了一个PriorityQueue(优先队列)对象,专门用于存储Double类型的数据

PriorityQueue是Java中的一个基于堆数据结构的队列,默认情况下,是一个小顶堆(即队列头部的元素是最小的)

但是通过Comparator(比较器),这段代码将默认的最小堆改成了大顶堆(队列头部是最大的元素)

3.什么是Lambda表达式?

a. 基本概念
  • Lambda 表达式是 Java 8 引入的一种功能,用于简化函数式接口(Functional Interface)的实现。
  • 函数式接口:是指只有一个抽象方法的接口(例如 Comparator、Runnable、Predicate 等)。Java 8 允许使用 Lambda 表达式来代替匿名内部类的实现。
  • Lambda 表达式的核心作用是提供一种简洁的方式来表示方法的行为,通常用于传递行为(如排序规则、过滤条件等)。
b. 语法

Lambda 表达式的基本语法如下:

(参数列表) -> { 表达式或方法体 }

  • 参数列表:可以是空的,也可以包含一个或多个参数。如果只有一个参数,可以省略括号。
  • 箭头(->):分隔参数列表和方法体。
  • 方法体:可以是一个表达式(直接返回值),也可以是一个代码块(用 {} 包裹,可能包含多条语句)。
  • 示例
    • 无参数:() -> System.out.println("Hello")
    • 一个参数:x -> x * 2
    • 多个参数:(a, b) -> a + b
    • 带代码块:(a, b) -> { System.out.println(a); return a + b; }
c. 函数式接口与 Lambda 的关系
  • Lambda 表达式本质上是函数式接口的一个实现。
  • Java 编译器会根据上下文推断 Lambda 表达式的目标接口。例如:
    • Runnable 接口(void run()):() -> System.out.println("Run")
    • Comparator<T> 接口(int compare(T o1, T o2)):(a, b) -> a.compareTo(b)

2. Lambda 表达式在 PriorityQueue 配置中的应用

a. PriorityQueue 的默认行为
  • PriorityQueue 是 Java 中的优先队列,默认使用最小堆(Min Heap),即队列头部的元素是最小的。
  • 默认排序规则基于元素的自然顺序(Comparable 接口的 compareTo 方法)。对于 Double 类型,compareTo 按升序排序(从小到大)。
  • 构造函数 PriorityQueue(Comparator<? super E> comparator) 允许通过自定义 Comparator 来改变排序规则。
b. Lambda 表达式配置排序规则

在代码中:

PriorityQueue<Double> heap = new PriorityQueue<>((a, b) -> b.compareTo(a));

  • PriorityQueue<>(Comparator) 构造函数
    • PriorityQueue 接受一个 Comparator<Double> 参数,用于定义元素的比较规则。
    • Comparator<Double> 是一个函数式接口,定义了一个抽象方法:

      int compare(Double a, Double b);

      返回值:
      • 负数:a 排在 b 之前。
      • 零:a 和 b 相等。
      • 正数:a 排在 b 之后。
  • Lambda 表达式 (a, b) -> b.compareTo(a)
    • 这里 Lambda 表达式实现了 Comparator<Double> 的 compare 方法。
    • (a, b) 是参数列表,表示两个 Double 类型的参数。
    • b.compareTo(a) 是方法体,返回比较结果:
      • Double 的 compareTo 方法:b.compareTo(a) 比较 b 和 a 的大小。
        • 如果 b < a,返回负数。
        • 如果 b == a,返回 0。
        • 如果 b > a,返回正数。
      • 效果:b.compareTo(a) 将默认的升序(a.compareTo(b))反转为降序。
  • 配置效果
    • 默认情况下,PriorityQueue 使用 a.compareTo(b)(升序),是最小堆,poll() 返回最小值。
    • 使用 (a, b) -> b.compareTo(a)(降序),将 PriorityQueue 配置为最大堆,poll() 返回最大值。
c. 等价的传统写法

为了更清楚地理解 Lambda 表达式的作用,我们可以用传统的匿名内部类实现对比:

PriorityQueue<Double> heap = new PriorityQueue<>(new Comparator<Double>() {
    @Override
    public int compare(Double a, Double b) {
        return b.compareTo(a);
    }
});
  • 对比
    • 匿名内部类写法需要显式声明 new Comparator<Double>() 和 @Override,代码较冗长。
    • Lambda 表达式 (a, b) -> b.compareTo(a) 更简洁,直接表达了 compare 方法的逻辑。
    • Java 编译器会根据 PriorityQueue 的构造函数签名(需要 Comparator),推断出 Lambda 表达式实现了 Comparator<Double>。
d. 更简洁的写法

由于 Double 实现了 Comparable,还可以使用 Collections.reverseOrder() 来代替 Lambda 表达式:

PriorityQueue<Double> heap = new PriorityQueue<>(Collections.reverseOrder());

  • Collections.reverseOrder() 返回一个 Comparator,内部逻辑等价于 (a, b) -> b.compareTo(a)。
  • 这种写法更简洁,但 Lambda 表达式提供了更大的灵活性(例如可以自定义更复杂的比较逻辑)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值