编程进阶网 编程进阶网
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接

杨充

专注编程 · 终身学习者
首页
  • 计算机原理
  • 操作系统
  • 网络协议
  • 数据库原理
  • 面向对象
  • 设计原则
  • 设计模式
  • 系统架构
  • 性能优化
  • 编程原理
  • 方案设计
  • 稳定可靠
  • 工程运维
  • 基础认知
  • 线性结构
  • 树与哈希
  • 工业级实现
  • 算法思想
  • 实战与综合
  • 算法题考核
  • C语言入门
  • C综合案例
  • C专栏博客
  • C标准集库
  • C++入门教程
  • C++综合案例
  • C++专栏博客
  • C++开发技巧
  • Java入门教程
  • Java综合案例
  • Java专栏博客
  • Go入门教程
  • Go综合案例
  • Go专栏博客
  • Go开发技巧
  • JavaScript入门
  • JavaScript高级
  • Android库解读
  • Android专栏
  • Android智能硬件
  • iOS ObjC入门
  • iOS Swift入门
  • iOS入门精通
  • Web之Html手册
  • Web之TypeScript
  • Web之Vue高级进阶
  • Linux之QML入门
  • Linux之QT核心库
  • Linux实践开发
  • Python教程
  • Shell&Bash教程
  • 工具脚本
  • 自动化脚本
  • 质量保障
  • 产品思考
  • 软实力
  • 开发流程
  • Git应用
  • 技术模版
  • 技术规范
  • Markdown
  • Mermaid
  • 开源协议
  • JSON工具
  • 文本工具
  • 图片处理
  • 文档转化
  • 代码压缩
  • 关于我
  • 自我精进
  • 职场管理
  • 职场面试
  • 心情杂货
  • 友情链接
  • 数据结构与算法专栏
  • 基础认知

  • 线性结构

  • 树与哈希

  • 工业级实现

  • 算法思想

  • 实战与综合

  • 算法题考核

    • README
    • 数组算法题合集
    • 链表算法题合集
    • 栈队列算法题合集
    • 哈希算法题合集
    • 二叉树算法题合集
    • 堆算法题合集
      • 数组中的第 K 个最大元素(Kth Largest Element)
        • 01. 题目描述
        • 02. 题目分析:三种思维的演变
        • 03. 解法一:最小堆(O(NlogK))
        • 04. 解法二:快速选择(O(N) 平均)
        • 05. 两解法对比
        • 06. 总结
      • 数据流的中位数(Find Median from Data Stream)
        • 01. 题目描述
        • 02. 题目分析
        • 03. 代码
        • 04. 总结
      • 丑数 II(Ugly Number II)
        • 01. 题目
        • 02. 题目分析
        • 03. 代码
        • 04. 总结
      • 重构字符串(Reorganize String)
        • 01. 题目
        • 02. 题目分析
        • 03. 代码
        • 04. 总结
      • 任务调度器 (621) / IPO (502)
        • F009 任务调度器
        • F010 IPO
      • F010 IPO(最大资本)
        • 01. 题目
        • 02. 代码
    • 图算法题合集
    • 二分查找算法题合集
    • 双指针算法题合集
    • 排序算法题合集
    • 分治算法题合集
    • 贪心算法题合集
    • 回溯算法题合集
    • 动规算法题合集
    • 位运算算法题合集
    • 数学算法题合集
  • 算法
  • 算法题考核
杨充
2020-12-01
目录

堆算法题合集

# 数组中的第 K 个最大元素(Kth Largest Element)

LeetCode 215 · ⭐⭐ · 快速选择 / 堆

一道题考察了排序、堆、快速选择三种思路。面试首选快速选择(O(N) 平均),次选大小为 K 的最小堆(O(NlogK))。

# 01. 题目描述

示例 1:

输入:nums = [3,2,1,5,6,4], k = 2
输出:5
1
2

示例 2:

输入:nums = [3,2,3,1,2,4,5,5,6], k = 4
输出:4
1
2

# 02. 题目分析:三种思维的演变

flowchart LR
    A["第K大"] --> B["全排序<br/>O(NlogN)/O(1)"]
    A --> C["大小为K的最小堆<br/>O(NlogK)/O(K)"]
    A --> D["快速选择<br/>O(N)平均/O(logN)"]
1
2
3
4
解法 时间 空间 核心思想
全排序 O(NlogN) O(1) 直接取第K个
最小堆 O(NlogK) O(K) 维护K个"候选最大值"
快速选择 O(N)平均 O(logN) 快排partition,只递归一侧

2.1 最小堆为什么是大小为K的最小堆?

堆顶是堆中最小值。当堆大小>K时,弹出最小值——淘汰最小的,保留K个最大的。最终堆顶就是第K大。

nums = [3,2,1,5,6,4], k=2

3入堆: pq=[3]
2入堆: pq=[2,3]  堆顶=2(第2大)  size=2, 不需要弹出
1入堆: pq=[1,2,3] → poll去掉1 → pq=[2,3]  堆顶=2
5入堆: pq=[2,3,5] → poll去掉2 → pq=[3,5]  堆顶=3
6入堆: pq=[3,5,6] → poll去掉3 → pq=[5,6]  堆顶=5
4入堆: pq=[4,5,6] → poll去掉4 → pq=[5,6]  堆顶=5 ←答案
1
2
3
4
5
6
7
8

2.2 快速选择的直觉

快排的 partition 把数组分成 [小于pivot] | pivot | [大于pivot]。pivot 的 index 如果 == N-K,它就是答案。否则只递归一侧。

# 03. 解法一:最小堆(O(NlogK))

public int findKthLargest(int[] nums, int k) {
    PriorityQueue<Integer> pq = new PriorityQueue<>(); // 默认最小堆
    for (int n : nums) {
        pq.offer(n);
        if (pq.size() > k) pq.poll();   // 淘汰最小的
    }
    return pq.peek();
}
1
2
3
4
5
6
7
8

Python:

def findKthLargest(self, nums: List[int], k: int) -> int:
    import heapq
    pq = []
    for n in nums:
        heapq.heappush(pq, n)
        if len(pq) > k: heapq.heappop(pq)
    return pq[0]
1
2
3
4
5
6
7

C++:

int findKthLargest(vector<int>& nums, int k) {
    priority_queue<int, vector<int>, greater<int>> pq;
    for (int n : nums) { pq.push(n); if (pq.size() > k) pq.pop(); }
    return pq.top();
}
1
2
3
4
5

复杂度:O(NlogK) / O(K)。

# 04. 解法二:快速选择(O(N) 平均)

public int findKthLargest(int[] nums, int k) {
    return quickSelect(nums, 0, nums.length - 1, nums.length - k);
}
int quickSelect(int[] a, int l, int r, int k) {
    int pivot = a[r], i = l;
    for (int j = l; j < r; j++)
        if (a[j] <= pivot) swap(a, i++, j);
    swap(a, i, r);
    if (i == k) return a[i];
    return i < k ? quickSelect(a, i + 1, r, k) : quickSelect(a, l, i - 1, k);
}
void swap(int[] a, int i, int j) { int t = a[i]; a[i] = a[j]; a[j] = t; }
1
2
3
4
5
6
7
8
9
10
11
12

Python:

def findKthLargest(self, nums, k):
    def quick(l, r, k):
        pivot, i = nums[r], l
        for j in range(l, r):
            if nums[j] <= pivot: nums[i], nums[j] = nums[j], nums[i]; i += 1
        nums[i], nums[r] = nums[r], nums[i]
        if i == k: return nums[i]
        return quick(i+1, r, k) if i < k else quick(l, i-1, k)
    return quick(0, len(nums)-1, len(nums)-k)
1
2
3
4
5
6
7
8
9

复杂度:O(N)平均 / O(N²)最坏(有序时退化),O(logN)栈空间。

常见追问:如何避免 O(N²) 最坏?

随机化 pivot:swap(a[r], a[rand.nextInt(r-l+1)+l])。

# 05. 两解法对比

维度 最小堆 快速选择
时间复杂度 O(NlogK) O(N) 平均
空间复杂度 O(K) O(logN)
稳定性 很稳定 最坏 O(N²)
K << N 时 优 —
K ≈ N 时 O(NlogN) O(N)
面试推荐 ✅ 首选 ✅** 加分

# 06. 总结

核心启示 说明
最小堆维护TopK 大小为K的最小堆,堆顶=第K大
快速选择 partition后只递归一侧,O(N)平均
与中位数的联系 找中位数 = 第 N/2 大 = KthLargest 的特例

相关题目:087 数据流中位数、J001 快速排序

# 数据流的中位数(Find Median from Data Stream)

LeetCode 295 · ⭐⭐⭐ · 双堆

第K大是"静态TopK",中位数是"动态TopK"——数据不断流入,随时能返回中位数。双堆是最优雅的解法。

# 01. 题目描述

实现 MedianFinder 类:

  • addNum(int num):将整数添加到数据流中
  • findMedian():返回当前所有元素的中位数

示例:

addNum(1) → [1]       中位数=1
addNum(2) → [1,2]     中位数=1.5
findMedian() → 1.5
addNum(3) → [1,2,3]   中位数=2
findMedian() → 2
1
2
3
4
5

# 02. 题目分析

2.1 暴力解法

每次插入后排序 → O(NlogN)。数据流场景不可接受。

2.2 双堆设计

flowchart LR
    subgraph lo["最大堆(存较小一半)"]
        L1["堆顶=左半最大值"]
    end
    subgraph hi["最小堆(存较大一半)"]
        H1["堆顶=右半最小值"]
    end
    L1 --> M["中位数 = lo.top 或 (lo.top+hi.top)/2"]
    H1 --> M
1
2
3
4
5
6
7
8
9

平衡规则:lo.size() >= hi.size(),|size差| ≤ 1。

2.3 插入流程

flowchart TD
    A["新数 num"] --> B["lo.offer(num)"]
    B --> C["hi.offer(lo.poll())  ← 把lo的最大给hi"]
    C --> D{"hi.size() > lo.size() ?"}
    D -->|YES| E["lo.offer(hi.poll())  ← 把hi的最小还给lo"]
    D -->|NO| F["完成"]
1
2
3
4
5
6

2.4 手推演示

addNum(5): lo=[5],hi=[] → hi.size()<lo.size() → 中位数=5
addNum(2): lo.offer(2)→lo=[2,5], hi.offer(lo.poll)=lo.poll=5? 不对...

重新来:
addNum(5): lo=[5], hi=[]
  → lo.offer(5): lo=[5]
  → hi.offer(lo.poll()): hi=[5], lo=[]
  → hi.size()>lo.size() → lo.offer(hi.poll()): lo=[5], hi=[]
  → 中位数=5

addNum(2): 
  → lo.offer(2): lo=[2,5]  (lo是最大堆: maxheap)
  → hi.offer(lo.poll()): lo.poll=5, hi=[5], lo=[2]
  → hi.size()==lo.size()==1 → 中位数=(2+5)/2=3.5

addNum(3):
  → lo.offer(3): lo=[3,2]  
  → hi.offer(lo.poll()=3): hi=[3,5], lo=[2]
  → hi.size()>lo.size() → lo.offer(hi.poll()=3): lo=[3,2], hi=[5]
  → 中位数=lo.top=3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

# 03. 代码

Java:

class MedianFinder {
    // lo: 存储较小一半 → 最大堆(取反或用Comparator)
    PriorityQueue<Integer> lo = new PriorityQueue<>(Collections.reverseOrder());
    // hi: 存储较大一半 → 最小堆(默认)
    PriorityQueue<Integer> hi = new PriorityQueue<>();

    public void addNum(int num) {
        lo.offer(num);
        hi.offer(lo.poll());          // 把lo的最大值给hi
        if (hi.size() > lo.size())    // 保持平衡
            lo.offer(hi.poll());
    }

    public double findMedian() {
        return lo.size() > hi.size() ?
            lo.peek() : (lo.peek() + hi.peek()) / 2.0;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Python:

class MedianFinder:
    def __init__(self):
        self.lo = []  # 最大堆(取反存储)
        self.hi = []  # 最小堆

    def addNum(self, num: int) -> None:
        heapq.heappush(self.lo, -num)
        heapq.heappush(self.hi, -heapq.heappop(self.lo))
        if len(self.hi) > len(self.lo):
            heapq.heappush(self.lo, -heapq.heappop(self.hi))

    def findMedian(self) -> float:
        if len(self.lo) > len(self.hi):
            return -self.lo[0]
        return (-self.lo[0] + self.hi[0]) / 2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

C++:

class MedianFinder {
    priority_queue<int> lo;                           // 最大堆
    priority_queue<int, vector<int>, greater<int>> hi; // 最小堆
public:
    void addNum(int num) {
        lo.push(num); hi.push(lo.top()); lo.pop();
        if (hi.size() > lo.size()) { lo.push(hi.top()); hi.pop(); }
    }
    double findMedian() {
        return lo.size() > hi.size() ? lo.top() : (lo.top() + hi.top()) / 2.0;
    }
};
1
2
3
4
5
6
7
8
9
10
11
12

复杂度:add O(logN),find O(1)。

# 04. 总结

核心启示 说明
双堆思想 一个最大堆 + 一个最小堆 = 中位数的天然存储结构
平衡规则 每次插入后做一次"lo→hi→lo"的三步循环
TopK 延伸 静态(KthLargest) → 动态(数据流中位数)

相关题目:086 第K大

# 丑数 II(Ugly Number II)

LeetCode 264 · ⭐⭐ · 多路归并(三指针)

# 01. 题目

只含质因数 2/3/5 的第 n 个丑数。n=10 → 12(1,2,3,4,5,6,8,9,10,12)

# 02. 题目分析

2.1 朴素思路

从 1 开始逐个检查每个数是否为丑数 → 太慢。

2.2 三指针归并

flowchart TD
    A["dp[0]=1"] --> B["p2=p3=p5=0"]
    B --> C["每次 min(dp[p2]*2, dp[p3]*3, dp[p5]*5)"]
    C --> D["选中后对应指针+1"]
1
2
3
4

本质:三个有序序列的归并。

序列 生成方式 前几个值
×2 dp[p2]×2 2,4,6,8,10,...
×3 dp[p3]×3 3,6,9,12,15,...
×5 dp[p5]×5 5,10,15,20,25,...

2.3 手推

dp[0]=1, p2=p3=p5=0

i=1: n2=2, n3=3, n5=5 → min=2 → dp[1]=2, p2++
i=2: n2=4, n3=3, n5=5 → min=3 → dp[2]=3, p3++
i=3: n2=4, n3=6, n5=5 → min=4 → dp[3]=4, p2++
i=4: n2=6, n3=6, n5=5 → min=5 → dp[4]=5, p5++
i=5: n2=6, n3=6, n5=10→ min=6 → dp[5]=6, p2++ && p3++ (两个6)
1
2
3
4
5
6
7

注意:dp[i]==n2 时不加 else,让相等的指针同时前进(如6同时被p2和p3命中),防止重复。

# 03. 代码

Java:

public int nthUglyNumber(int n) {
    int[] dp = new int[n]; dp[0] = 1;
    int p2 = 0, p3 = 0, p5 = 0;
    for (int i = 1; i < n; i++) {
        int n2 = dp[p2] * 2, n3 = dp[p3] * 3, n5 = dp[p5] * 5;
        dp[i] = Math.min(n2, Math.min(n3, n5));
        if (dp[i] == n2) p2++;
        if (dp[i] == n3) p3++;
        if (dp[i] == n5) p5++;
    }
    return dp[n - 1];
}
1
2
3
4
5
6
7
8
9
10
11
12

Python:

def nthUglyNumber(self, n: int) -> int:
    dp = [1]; p2 = p3 = p5 = 0
    for _ in range(n - 1):
        n2, n3, n5 = dp[p2]*2, dp[p3]*3, dp[p5]*5
        nxt = min(n2, n3, n5)
        dp.append(nxt)
        if nxt == n2: p2 += 1
        if nxt == n3: p3 += 1
        if nxt == n5: p5 += 1
    return dp[-1]
1
2
3
4
5
6
7
8
9
10

C++:

int nthUglyNumber(int n) {
    vector<int> dp(n); dp[0] = 1;
    int p2 = 0, p3 = 0, p5 = 0;
    for (int i = 1; i < n; i++) {
        int n2 = dp[p2]*2, n3 = dp[p3]*3, n5 = dp[p5]*5;
        dp[i] = min({n2, n3, n5});
        if (dp[i] == n2) p2++;
        if (dp[i] == n3) p3++;
        if (dp[i] == n5) p5++;  // 注意:是 if 不是 else if
    }
    return dp[n - 1];
}
1
2
3
4
5
6
7
8
9
10
11
12

复杂度:O(N)/O(N)。

# 04. 总结

核心 说明
多路归并 三个有序序列归并去重
去重 if 不用 else if,相同时多指针同时前进
本质 DP + 三指针 = 动态规划的多维扩展

相关题目:086 第K大

# 重构字符串(Reorganize String)

LeetCode 767 · ⭐⭐ · 最大堆贪心

# 01. 题目

重排字符串使相邻字符不同。"aab" → "aba";"aaab" → ""(不可能)。

# 02. 题目分析

2.1 何时不可能?

若某字符频率 > (N+1)/2,则不可能。"aaab":a频=3,总长4,(4+1)/2=2.5 → 3>2.5 → 不可能。

2.2 贪心策略

flowchart TD
    A["统计频率"] --> B["最大堆存(char,freq)"]
    B --> C["每次弹出频率最高的"]
    C --> D{"和上一个字符相同?"}
    D -->|YES| E["弹第二高的(不同字符)"]
    D -->|NO| F["直接使用"]
    E --> G["用完第一个后把之前暂存的放回去"]
    F --> H["freq-- > 0 → 暂存"]
    G --> C
    H --> C
1
2
3
4
5
6
7
8
9
10

为什么贪心正确? 每次都选当前剩余最多且与上一个不同的字符,最大化未来的排列空间。

2.3 手推

s = "aab", freq = {a:2, b:1}

pq = [(a,2), (b,1)]

弹(a,2): sb='a', 暂存prev=(a,1)
弹(b,1): sb='ab', prev=(a,1)放回 → pq=[(a,1)]
弹(a,1): sb='aba', prev=(a,0)

结果: "aba" ✓
1
2
3
4
5
6
7
8
9

# 03. 代码

Java:

public String reorganizeString(String s) {
    int[] f = new int[26];
    for (char c : s.toCharArray()) f[c - 'a']++;
    PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> b[1] - a[1]);
    for (int i = 0; i < 26; i++) if (f[i] > 0) pq.offer(new int[]{i, f[i]});

    StringBuilder sb = new StringBuilder();
    int[] prev = {-1, 0};                           // 暂存上一个用过的字符
    while (!pq.isEmpty()) {
        int[] cur = pq.poll();
        sb.append((char)(cur[0] + 'a')); cur[1]--;
        if (prev[1] > 0) pq.offer(prev);            // 把暂存放回
        prev = cur;                                  // 当前成为新的暂存
    }
    return sb.length() == s.length() ? sb.toString() : "";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Python:

def reorganizeString(self, s: str) -> str:
    from collections import Counter
    import heapq
    freq = Counter(s)
    pq = [(-v, k) for k, v in freq.items()]         # Python最大堆=取反
    heapq.heapify(pq)
    res, prev = [], (0, '')                          # prev=(剩余频次, 字符)

    while pq:
        v, k = heapq.heappop(pq)
        res.append(k)
        if prev[0] < 0: heapq.heappush(pq, prev)     # 放回暂存
        prev = (v + 1, k)                            # freq-1 (v是负数)

    return ''.join(res) if len(res) == len(s) else ''
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

C++:

string reorganizeString(string s) {
    vector<int> f(26); for (char c : s) f[c-'a']++;
    auto cmp = [](pair<int,int>& a, pair<int,int>& b) { return a.second < b.second; };
    priority_queue<pair<int,int>, vector<pair<int,int>>, decltype(cmp)> pq(cmp);
    for (int i = 0; i < 26; i++) if (f[i] > 0) pq.push({i, f[i]});

    string ans; pair<int,int> prev = {-1, 0};
    while (!pq.empty()) {
        auto cur = pq.top(); pq.pop();
        ans += (char)(cur.first + 'a'); cur.second--;
        if (prev.second > 0) pq.push(prev);
        prev = cur;
    }
    return ans.size() == s.size() ? ans : "";
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

复杂度:O(Nlog26) ≈ O(N) / O(26)。

# 04. 总结

核心启示 说明
最大堆 每次选剩余最多的不同字符
暂存(prev) 本轮用了但仍有剩余的字符,下轮放回堆
不可行判定 某字符频率 > (N+1)/2

相关题目:094 任务调度器

# 任务调度器 (621) / IPO (502)

# F009 任务调度器

LeetCode 621 · ⭐⭐ · 桶思想

01. 题目

相同任务需间隔 n 个单位。tasks=["A","A","A","B","B","B"], n=2 → 8。

02. 题解分析

flowchart TD
    A["统计每个任务的频次"] --> B["maxF = 最高频次"]
    B --> C["maxCnt = 有几个任务达到了 maxF"]
    C --> D["公式: max(len, (maxF-1)*(n+1)+maxCnt)"]
1
2
3
4

桶模型:最高频任务形成 (maxF-1) 个完整桶,每个桶大小 n+1。最后一桶只放 maxCnt 个最高频任务。

A A A
B B B    n=2

桶1: A B idle  (周期=3=n+1)
桶2: A B idle  
最后: A B

总: 2×(2+1) + 2 = 8
公式: (3-1)×3 + 2 = 8
1
2
3
4
5
6
7
8
9

03. 代码

public int leastInterval(char[] tasks, int n) {
    int[] f = new int[26];
    for (char c : tasks) f[c - 'A']++;
    int maxF = 0, maxCnt = 0;
    for (int x : f) {
        if (x > maxF) { maxF = x; maxCnt = 1; }
        else if (x == maxF) maxCnt++;
    }
    return Math.max(tasks.length, (maxF - 1) * (n + 1) + maxCnt);
}
1
2
3
4
5
6
7
8
9
10

Python:

def leastInterval(self, tasks, n):
    from collections import Counter
    f = list(Counter(tasks).values())
    m = max(f); cnt = f.count(m)
    return max(len(tasks), (m - 1) * (n + 1) + cnt)
1
2
3
4
5

复杂度:O(N)/O(1)。

# F010 IPO

LeetCode 502 · ⭐⭐⭐ · 贪心+堆

01. 题目

初始资本 w,选 ≤ k 个项目。每个项目有成本 c 和利润 p。最大化最终资本。

02. 分析

flowchart TD
    A["项目按成本排序"] --> B["while 成本≤当前资本 → 把利润入堆"]
    B --> C["选最大利润的项目(pq.poll)"]
    C --> D["资本 += 利润"]
    D --> E{"还有剩余次数?"}
    E -->|YES| B
    E -->|NO| F["返回资本"]
1
2
3
4
5
6
7

03. 代码

Java:

public int findMaximizedCapital(int k, int w, int[] p, int[] c) {
    int n = p.length;
    int[][] arr = new int[n][2];
    for (int i = 0; i < n; i++) arr[i] = new int[]{c[i], p[i]};
    Arrays.sort(arr, (a, b) -> a[0] - b[0]);           // 按成本排序
    PriorityQueue<Integer> pq = new PriorityQueue<>(Collections.reverseOrder());

    int i = 0;
    while (k-- > 0) {
        while (i < n && arr[i][0] <= w)                 // 能买得起的全入堆
            pq.offer(arr[i++][1]);
        if (pq.isEmpty()) break;                         // 没有可选的了
        w += pq.poll();                                  // 选利润最大的
    }
    return w;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Python:

def findMaximizedCapital(self, k, w, profits, capital):
    import heapq
    arr = sorted(zip(capital, profits))
    pq = []; i = 0
    for _ in range(k):
        while i < len(arr) and arr[i][0] <= w:
            heapq.heappush(pq, -arr[i][1]); i += 1
        if not pq: break
        w += -heapq.heappop(pq)
    return w
1
2
3
4
5
6
7
8
9
10

复杂度:O(NlogN)/O(N)。

04. 两题对比

任务调度器 IPO
核心 桶思想(O(N)) 贪心+最大堆(O(NlogN))
堆用途 — 选最大利润
公式 数学推导 贪心迭代

相关:093 重构字符串、086 第K大

# F010 IPO(最大资本)

LeetCode 502 · ⭐⭐⭐ · 贪心+双堆

# 01. 题目

初始资本 w,最多选 k 个项目,每个项目有成本 c 和利润 p。资本足够时可选,利润加入资本。最大化最终资本。

# 02. 代码

Java:

public int findMaximizedCapital(int k, int w, int[] p, int[] c) {
    int n=p.length; int[][] arr=new int[n][2];
    for(int i=0;i<n;i++) arr[i]=new int[]{c[i],p[i]};
    Arrays.sort(arr,(a,b)->a[0]-b[0]);
    PriorityQueue<Integer> pq=new PriorityQueue<>(Collections.reverseOrder());
    int i=0;
    while(k-->0){ while(i<n&&arr[i][0]<=w) pq.offer(arr[i++][1]); if(pq.isEmpty()) break; w+=pq.poll(); }
    return w;
}
1
2
3
4
5
6
7
8
9

Python:

def findMaximizedCapital(self, k, w, profits, capital):
    import heapq
    arr=sorted(zip(capital,profits))
    pq=[]; i=0
    for _ in range(k):
        while i<len(arr) and arr[i][0]<=w: heapq.heappush(pq,-arr[i][1]); i+=1
        if not pq: break
        w+=-heapq.heappop(pq)
    return w
1
2
3
4
5
6
7
8
9

复杂度:O(NlogN)/O(N)。

#算法
上次更新: 2026/06/17, 12:46:05
二叉树算法题合集
图算法题合集

← 二叉树算法题合集 图算法题合集→

最近更新
01
信号崩溃快速排查
06-15
02
CoreDump破案
06-15
03
perf火焰图实战
06-15
更多文章>
Theme by Vdoing | Copyright © 2019-2026 杨充 | MIT License | 桂ICP备2024034950号 | 桂公网安备45142202000030
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式