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 <= 10^5 | |
◦ |
| |
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))反转为降序。
- Double 的 compareTo 方法:b.compareTo(a) 比较 b 和 a 的大小。
- 配置效果:
- 默认情况下,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 表达式提供了更大的灵活性(例如可以自定义更复杂的比较逻辑)。