LeetCode2208. 将数组和减半的最少操作次数、LeetCode2406.将区间分为最少组数

文章目录

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

代码实现

python 复制代码
from typing import List
import heapq


class Solution:
    def halveArray(self, nums: List[int]) -> int:
        """
        计算将数组和至少减少一半的最少操作数

        解题思路:
        使用贪心算法 + 最大堆(用负数实现)

        核心思想:
        1. 每次操作都选择当前最大的数进行减半,这样能最大程度减少数组和
        2. 使用最大堆维护当前所有数,每次取出最大值
        3. 累加每次减半的数值,直到减少的总和 >= 原数组和的一半

        时间复杂度:O(n * log n)
        空间复杂度:O(n)
        """
        # 计算初始数组和
        total_sum = sum(nums)
        # 目标是减少至少一半
        target = total_sum / 2

        # 记录已经减少的数值
        reduced_sum = 0
        # 记录操作次数
        operations = 0

        # Python 的 heapq 是最小堆,用负数实现最大堆
        # 将所有数取负后入堆,堆顶就是最大的数的负数
        max_heap = [-num for num in nums]
        heapq.heapify(max_heap)

        # 贪心:每次选择最大的数减半
        while reduced_sum < target:
            # 弹出最大值(注意是负数)
            max_num = -heapq.heappop(max_heap)

            # 减半
            half_num = max_num / 2

            # 累加减少的数值
            reduced_sum += half_num

            # 操作次数 +1
            operations += 1

            # 将减半后的数重新加入堆中(后续可能继续减半)
            heapq.heappush(max_heap, -half_num)

        return operations

核心思路 贪心策略

每次选择当前最大的数减半

为什么?

  • 假设当前有 [19, 8, 5, 1]
  • 减半 19:减少 9.5 ✓ 最大
  • 减半 8:减少 4
  • 减半 5:减少 2.5
  • 减半 1:减少 0.5
    结论 :减半越大的数,减少得越多,越快达到目标!

详细执行过程

以 [5, 19, 8, 1] 为例:

初始状态:

数组:[5, 19, 8, 1]

总和:33

目标:减少至少 16.5

操作 1:

选择最大的 19,减半为 9.5

减少:9.5

累计:9.5

数组变为:[5, 9.5, 8, 1]

操作 2:

选择最大的 9.5,减半为 4.75

减少:4.75

累计:14.25

数组变为:[5, 4.75, 8, 1]

操作 3:

选择最大的 8,减半为 4

减少:4

累计:18.25 ✓ 达到目标(18.25 >= 16.5)

数组变为:[5, 4.75, 4, 1]

答案:3 次操作

代码关键点 最大堆的实现

Python 的 heapq 是最小堆,如何模拟最大堆?

技巧:所有数取负数

max_heap = [-num for num in nums]

heapq.heapify(max_heap)

弹出时再取负,得到最大值

max_num = -heapq.heappop(max_heap)

循环条件

while reduced_sum < target:

继续操作,直到减少的总和 >= 目标值

重复利用

减半后的数可以再次减半:

减半后重新加入堆中

heapq.heappush(max_heap, -half_num)

复杂度分析

  • 时间复杂度 :O(n log n)

    • 建堆:O(n)
    • 每次堆操作:O(log n)
    • 最多操作 n 次(实际远小于 n)
  • 空间复杂度 :O(n)

    • 堆存储所有元素

LeetCode2406.将区间分为最少组数

代码实现

python 复制代码
class Solution:
    def minGroups(self, intervals: List[List[int]]) -> int:
        """
        计算将区间划分为最少组数

        解题思路:
        使用贪心算法 + 最小堆

        核心思想:
        1. 将所有区间按照左端点从小到大排序
        2. 使用最小堆维护每个组当前最后一个区间的右端点
        3. 遍历每个区间:
           - 如果当前区间的左端点 > 堆顶(某个组的最右端点),说明可以接在该组后面,弹出堆顶
           - 将当前区间的右端点加入堆中(可能是新开一组,也可能是更新某组的右端点)
        4. 堆的大小就是所需的最少组数

        注意:这是闭区间,[1,4] 和 [4,5] 在点 4 处相交,不能放在同一组
        所以判断条件是 min_heap[0] < left(严格小于)

        时间复杂度:O(n * log n)
        空间复杂度:O(n)
        """
        # 按照区间的左端点从小到大排序
        # 这样可以保证我们按顺序处理每个区间,便于贪心决策
        intervals.sort(key=lambda x: x[0])

        # 最小堆,存储每个组当前最后一个区间的右端点
        # 堆顶元素是所有组中右端点最小的值(即最早结束的组)
        min_heap = []

        # 遍历排序后的每个区间
        for interval in intervals:
            left, right = interval[0], interval[1]

            # 关键判断:如果堆不为空,且堆顶(最小右端点)< 当前区间的左端点
            # 说明当前区间可以接在该组后面(无重叠)
            # 因为是闭区间,[a,b] 和 [b,c] 在点 b 处相交,不能放在同一组
            # 所以必须是严格小于,即 min_heap[0] < left 才能接上
            if min_heap and (min_heap[0] < left):
                # 弹出堆顶,表示当前区间可以接在这个组后面
                # 弹出后该组会被当前区间更新右端点
                heapq.heappop(min_heap)

            # 将当前区间的右端点加入堆中
            # 两种情况:
            # 1. 如果刚才弹出了堆顶,这相当于更新了该组的右端点
            # 2. 如果没有弹出,这是新开一个组
            heapq.heappush(min_heap, right)

        # 堆的大小就是最少需要的组数
        # 因为堆中每个元素代表一个组的右端点
        return len(min_heap)
        

算法:贪心 + 最小堆

步骤 1:排序

将所有区间按左端点从小到大排序

步骤 2:遍历 + 维护最小堆

  • 堆的作用:存储每个组的右端点
  • 堆顶:所有组中右端点最小的(最早结束的组)
python 复制代码
堆 = [组 1 的右端点,组 2 的右端点,组 3 的右端点...]
堆大小 = 当前组数
堆顶 = 最早结束的组

步骤 3:对每个区间做决策

bash 复制代码
对于区间 [left, right]:
  if 堆顶 < left:
    # 可以接在该组后面(无重叠)
    弹出堆顶,加入 right(更新该组)
  else:
    # 不能接在任何组后面(会重叠)
    直接加入 right(新开一组)

详细示例

示例

\[5,10\],\[6,8\],\[1,5\],\[2,3\],\[1,10\]

排序后

复制代码
[1,5], [1,10], [2,3], [5,10], [6,8]

执行过程

步骤 处理区间 堆状态(处理前) 判断 操作 堆状态(处理后) 组数
1 [1,5] [] - 开新组,加入 5 [5] 1
2 [1,10] [5] 5≥1 ✗ 开新组,加入 10 [5,10] 2
3 [2,3] [5,10] 5≥2 ✗ 开新组,加入 3 [3,5,10] 3
4 [5,10] [3,5,10] 3<5 ✓ 弹出 3,加入 10 [5,10,10] 3
5 [6,8] [5,10,10] 5<6 ✓ 弹出 5,加入 8 [8,10,10] 3

复杂度分析

时间复杂度:O(n log n)

  • 排序:O(n log n)
  • 遍历:O(n)
  • 堆操作:每次 O(log n),共 n 次 → O(n log n)
  • 总计:O(n log n)

空间复杂度:O(n)

  • 堆存储最多 n 个元素
相关推荐
shehuiyuelaiyuehao1 小时前
算法1,移动零
数据结构·算法·排序算法
shehuiyuelaiyuehao1 小时前
算法2,复写零
数据结构·算法
像污秽一样1 小时前
算法设计与分析-算法效率分析基础-习题1.1
c语言·数据结构·c++·算法
chilavert3181 小时前
程序员面试经典问题解答:java篇-2
开发语言·python
abant22 小时前
leetcode 739 单调栈模板题
算法·leetcode·职场和发展
John Song4 小时前
Python创建虚拟环境的方式对比与区别?
开发语言·python
geovindu4 小时前
python: Bridge Pattern
python·设计模式·桥接模式
搞程序的心海4 小时前
Python面试题(一):5个最常见的Python基础问题
开发语言·python
宝贝儿好7 小时前
【强化学习实战】第十一章:Gymnasium库的介绍和使用(1)、出租车游戏代码详解(Sarsa & Q learning)
人工智能·python·深度学习·算法·游戏·机器学习