导航菜单

贪心算法

贪心算法(Greedy Algorithm)是一种在每一步都选择当前最优解的策略,希望通过局部最优解达到全局最优解。虽然贪心算法不一定能得到全局最优解,但在许多问题中它能够高效地得到近似最优解或最优解。

贪心算法基础

贪心算法的特点

  1. 贪心选择性质:每一步都选择当前最优的选择
  2. 最优子结构:问题的最优解包含子问题的最优解
  3. 无后效性:当前的选择不会影响未来的选择

贪心 vs 动态规划

特性贪心算法动态规划
选择方式局部最优考虑所有可能
子问题只解决一次可能重复解决
时间复杂度通常较低通常较高
正确性需要证明总是正确

经典贪心问题

1. 活动选择问题

题目:选择最多的互不重叠的活动。

def activity_selection(start, finish):
    activities = list(zip(start, finish))
    activities.sort(key=lambda x: x[1])  # 按结束时间排序
    
    selected = [0]
    last_finish = activities[0][1]
    
    for i in range(1, len(activities)):
        if activities[i][0] >= last_finish:
            selected.append(i)
            last_finish = activities[i][1]
    
    return selected

2. 区间调度问题

题目:移除最少的区间,使得剩余区间互不重叠。

def erase_overlap_intervals(intervals):
    if not intervals:
        return 0
    
    intervals.sort(key=lambda x: x[1])  # 按结束时间排序
    count = 0
    end = intervals[0][1]
    
    for i in range(1, len(intervals)):
        if intervals[i][0] < end:
            count += 1
        else:
            end = intervals[i][1]
    
    return count

3. 跳跃游戏

题目:判断能否到达最后一个位置。

def can_jump(nums):
    max_reach = 0
    
    for i in range(len(nums)):
        if i > max_reach:
            return False
        max_reach = max(max_reach, i + nums[i])
        if max_reach >= len(nums) - 1:
            return True
    
    return True

4. 跳跃游戏 II

题目:最少跳跃次数到达最后一个位置。

def jump(nums):
    jumps = 0
    current_end = 0
    farthest = 0
    
    for i in range(len(nums) - 1):
        farthest = max(farthest, i + nums[i])
        
        if i == current_end:
            jumps += 1
            current_end = farthest
            
            if current_end >= len(nums) - 1:
                break
    
    return jumps

5. 分发糖果

题目:每个孩子至少一个糖果,相邻孩子中评分高的孩子获得更多糖果。

def candy(ratings):
    n = len(ratings)
    candies = [1] * n
    
    # 从左到右
    for i in range(1, n):
        if ratings[i] > ratings[i - 1]:
            candies[i] = candies[i - 1] + 1
    
    # 从右到左
    for i in range(n - 2, -1, -1):
        if ratings[i] > ratings[i + 1]:
            candies[i] = max(candies[i], candies[i + 1] + 1)
    
    return sum(candies)

6. 无重叠区间

def erase_overlap_intervals(intervals):
    if not intervals:
        return 0
    
    intervals.sort(key=lambda x: x[1])
    count = 0
    end = intervals[0][1]
    
    for i in range(1, len(intervals)):
        if intervals[i][0] < end:
            count += 1
        else:
            end = intervals[i][1]
    
    return count

7. 用最少数量的箭引爆气球

def find_min_arrow_shots(points):
    if not points:
        return 0
    
    points.sort(key=lambda x: x[1])
    arrows = 1
    end = points[0][1]
    
    for start, finish in points[1:]:
        if start > end:
            arrows += 1
            end = finish
    
    return arrows

8. 买卖股票的最佳时机

题目:只能进行一次交易,求最大利润。

def max_profit(prices):
    if not prices:
        return 0
    
    min_price = prices[0]
    max_profit = 0
    
    for price in prices[1:]:
        max_profit = max(max_profit, price - min_price)
        min_price = min(min_price, price)
    
    return max_profit

9. 买卖股票的最佳时机 II

题目:可以进行多次交易,求最大利润。

def max_profit_multiple(prices):
    profit = 0
    
    for i in range(1, len(prices)):
        if prices[i] > prices[i - 1]:
            profit += prices[i] - prices[i - 1]
    
    return profit

10. 加油站

题目:找到可以绕一圈的起始加油站。

def can_complete_circuit(gas, cost):
    total_gas = sum(gas)
    total_cost = sum(cost)
    
    if total_gas < total_cost:
        return -1
    
    start = 0
    tank = 0
    
    for i in range(len(gas)):
        tank += gas[i] - cost[i]
        if tank < 0:
            start = i + 1
            tank = 0
    
    return start

贪心算法的证明

1. 交换论证

证明:如果存在一个最优解,我们可以通过交换将其转换为贪心解,且不会使解变差。

2. 数学归纳法

证明:对于所有规模的问题,贪心选择都是最优的。

3. 反证法

假设贪心解不是最优解,然后推导出矛盾。

常见题目

1. 划分字母区间

def partition_labels(s):
    last_occurrence = {char: i for i, char in enumerate(s)}
    
    result = []
    start = 0
    end = 0
    
    for i, char in enumerate(s):
        end = max(end, last_occurrence[char])
        if i == end:
            result.append(end - start + 1)
            start = i + 1
    
    return result

2. 任务调度器

def least_interval(tasks, n):
    from collections import Counter
    counts = Counter(tasks)
    max_count = max(counts.values())
    num_max = sum(1 for count in counts.values() if count == max_count)
    
    return max(len(tasks), (max_count - 1) * (n + 1) + num_max)

3. 重构字符串

def reorganize_string(s):
    from collections import Counter
    import heapq
    
    counts = Counter(s)
    heap = [(-count, char) for char, count in counts.items()]
    heapq.heapify(heap)
    
    result = []
    prev_count, prev_char = 0, ''
    
    while heap:
        count, char = heapq.heappop(heap)
        result.append(char)
        
        if prev_count < 0:
            heapq.heappush(heap, (prev_count, prev_char))
        
        prev_count, prev_char = count + 1, char
    
    if len(result) != len(s):
        return ""
    
    return ''.join(result)

总结

贪心算法是解决最优化问题的重要方法:

  1. 核心思想:每一步选择当前最优解
  2. 适用条件:贪心选择性质 + 最优子结构
  3. 常见问题:区间调度、跳跃游戏、股票买卖、任务调度
  4. 证明方法:交换论证、数学归纳法、反证法

掌握贪心算法能够帮助你高效解决许多优化问题,但需要注意证明其正确性。


算法部分的学习就到这里了!接下来,让我们学习面向对象设计,这是面试中的重要环节。

搜索