贪心算法应用全解析,程序员节必备的7道经典题型汇总

第一章:贪心算法的核心思想与适用场景

贪心算法是一种在每一步选择中都采取当前状态下最优决策的算法设计策略,期望通过局部最优解达到全局最优解。该算法不回溯,一旦做出选择便不可更改,因此其效率较高,但并不适用于所有问题。

核心思想

贪心算法的核心在于“局部最优选择”。它在每个阶段都选择能使目标函数值最大的选项,而不考虑未来可能产生的影响。这种策略的关键是问题必须具备贪心选择性质和最优子结构。
  • 贪心选择性质:全局最优解可以通过一系列局部最优的选择来构造
  • 最优子结构:一个问题的最优解包含其子问题的最优解

典型适用场景

贪心算法适用于一些特定优化问题,例如:
  1. 活动选择问题
  2. 最小生成树(如Prim和Kruskal算法)
  3. 霍夫曼编码
  4. 单源最短路径(Dijkstra算法)
问题类型是否适用贪心说明
背包问题(分数型)可按单位价值排序贪心选取
0-1背包问题需动态规划求解
找零钱问题视币种而定标准币系下贪心有效

代码示例:分数背包问题

// 分数背包贪心实现
package main

import (
	"fmt"
	"sort"
)

type Item struct {
	value, weight float64
}

func fractionalKnapsack(items []Item, capacity float64) float64 {
	// 按单位重量价值降序排列
	sort.Slice(items, func(i, j int) bool {
		return items[i].value/items[i].weight > items[j].value/items[j].weight
	})

	var totalValue float64 = 0.0
	for _, item := range items {
		if capacity >= item.weight {
			totalValue += item.value
			capacity -= item.weight
		} else {
			// 可分割物品,取部分
			totalValue += item.value * (capacity / item.weight)
			break
		}
	}
	return totalValue
}

func main() {
	items := []Item{{60, 10}, {100, 20}, {120, 30}}
	capacity := 50.0
	fmt.Printf("最大价值: %.2f\n", fractionalKnapsack(items, capacity))
}
graph TD A[开始] --> B{按单位价值排序} B --> C[选取最高单位价值物品] C --> D{容量足够?} D -- 是 --> E[装入整件] D -- 否 --> F[装入部分] E --> G{还有物品?} F --> H[结束] G -- 是 --> C G -- 否 --> H

第二章:贪心算法基础题型精讲

2.1 区间调度问题:如何选择最多的不重叠区间

在众多贪心算法的经典应用中,区间调度问题是一个典型代表:给定一组区间的起止时间,目标是选出尽可能多的互不重叠的区间。
问题建模与策略选择
关键在于选择策略。若按起始时间排序,可能被迫接受长区间;而按结束时间升序排列,能尽早释放资源,为后续区间腾出空间。
算法实现
def interval_scheduling(intervals):
    intervals.sort(key=lambda x: x[1])  # 按结束时间排序
    selected = []
    last_end = float('-inf')
    for start, end in intervals:
        if start >= last_end:
            selected.append((start, end))
            last_end = end
    return selected
该函数接收区间列表 intervals,每项为 (start, end)。排序后遍历,仅当当前区间起始时间不早于已选区间的最晚结束时间时才纳入。
复杂度分析
排序耗时 O(n log n),扫描过程为 O(n),整体时间复杂度由排序主导。空间复杂度为 O(1)(不计输出)。

2.2 分发饼干问题:从贪心策略理解资源最优匹配

在资源分配场景中,分发饼干问题是贪心算法的经典应用。目标是让尽可能多的孩子满足,每个孩子有最小的胃口值,每块饼干有特定尺寸。
问题建模与策略选择
将孩子胃口和饼干尺寸分别排序,采用贪心策略:优先用最小的饼干满足最小胃口且能被满足的孩子。
算法实现
func findContentChildren(g []int, s []int) int {
    sort.Ints(g)
    sort.Ints(s)
    i, j := 0, 0
    for i < len(g) && j < len(s) {
        if g[i] <= s[j] { // 饼干能满足孩子
            i++
        }
        j++ // 无论是否满足,都尝试下一块饼干
    }
    return i
}
该函数通过双指针遍历排序后的数组,时间复杂度为 O(n log n),主要开销在排序。参数 g 表示孩子胃口,s 表示饼干尺寸。

2.3 跳跃游戏:基于局部最优判断全局可行性

在跳跃游戏问题中,核心目标是判断能否从数组起点到达最后一个位置。通过贪心策略,每一步都更新能到达的最远边界,即可高效求解。
算法思路
维护一个变量 maxReach 表示当前可达的最远下标。遍历数组时,若当前下标 i 超过 maxReach,说明无法继续前进,直接返回 false;否则更新 maxReach = max(maxReach, i + nums[i])
代码实现
func canJump(nums []int) bool {
    maxReach := 0
    for i := 0; i < len(nums); i++ {
        if i > maxReach {
            return false
        }
        maxReach = max(maxReach, i + nums[i])
    }
    return true
}
该实现时间复杂度为 O(n),空间复杂度 O(1)。每次迭代检查是否可到达位置 i,并动态扩展最远可达范围,体现了局部最优决策推动全局可行性的思想。

2.4 钱币找零问题:经典贪心模型的成立条件分析

在特定货币系统中,钱币找零问题常被视为贪心算法的经典应用。其核心思想是每次选择面值不超过剩余金额的最大硬币,以期用最少数量完成找零。
贪心策略的适用前提
该策略成立的关键在于货币面值的构造方式。若面值序列满足“规范性”条件——即任意面值都能被其后续更大面值整除或构成最优组合,则贪心解即为全局最优解。例如标准人民币体系(1, 5, 10, 20, 50, 100)就具备此性质。
算法实现示例

def coin_change_greedy(coins, amount):
    coins.sort(reverse=True)  # 降序排列
    count = 0
    for coin in coins:
        while amount >= coin:
            amount -= coin
            count += 1
    return count if amount == 0 else -1
上述代码按贪心策略计算最小硬币数。参数 coins 为可用面值列表,amount 为目标金额。循环中逐次扣除最大可选面值,直至金额归零或无解。
反例与局限性
当面值为 [1, 3, 4],目标金额为 6 时,贪心法选择 4+1+1(共3枚),而最优解为 3+3(仅2枚)。这表明贪心模型依赖于面值系统的数学结构,并非普适。

2.5 划分字母区间:贪心划分在字符串处理中的应用

在字符串处理中,合理划分字符区间有助于优化存储与检索。一个典型问题是:如何将字符串划分为尽可能少的子串,使得每个字母最多只出现在一个子串中?
问题建模与贪心策略
核心思想是记录每个字符最后一次出现的位置。遍历字符串时,动态扩展当前区间的右边界,直到当前位置等于当前区间内所有字符的最远出现位置。
func partitionLabels(s string) []int {
    lastPos := make(map[byte]int)
    for i := range s {
        lastPos[s[i]] = i
    }

    var result []int
    start, end := 0, 0
    for i := range s {
        if lastPos[s[i]] > end {
            end = lastPos[s[i]]
        }
        if i == end {
            result = append(result, end-start+1)
            start = i + 1
        }
    }
    return result
}
上述代码通过一次预处理确定每个字符的最远位置,再进行单次扫描完成区间划分。时间复杂度为 O(n),空间复杂度 O(1)(字符集固定)。该贪心策略确保每步决策不可逆且局部最优,最终达成全局最优划分。

第三章:贪心与数据结构结合应用

3.1 用优先队列优化贪心决策:IPO问题解析

在IPO问题中,给定初始资本和多个项目(具有收益和启动资金要求),目标是通过最多k次投资最大化最终资本。该问题的核心在于每一步选择当前可承担项目中收益最大的一个——典型的贪心策略。
贪心策略的瓶颈
若每次遍历所有项目寻找可执行且收益最高的项目,时间复杂度为O(kn),效率低下。此时引入**优先队列**优化候选项目的选取过程。
双堆优化思路
使用两个数据结构:
  • 按启动资金排序的项目列表(升序),用于筛选可投资项目;
  • 最大堆(基于收益)存储当前可投资的项目。
priority_queue maxProfit;
sort(projects.begin(), projects.end());
int i = 0;
for (int k = 0; k < K; ++k) {
    while (i < n && projects[i][0] <= W)
        maxProfit.push(projects[i++][1]);
    if (maxProfit.empty()) break;
    W += maxProfit.top(); maxProfit.pop();
}
上述代码中,先按成本排序所有项目,再将符合条件的项目收益加入最大堆。每次从堆顶取出最大收益项目进行投资,显著提升决策效率至O(n log n + k log n)。

3.2 结合排序技巧:任务调度问题中的贪心排序策略

在任务调度问题中,贪心算法常通过合理的排序策略实现近似最优解。关键在于选择合适的排序准则,如最早截止时间优先(EDF)或最短执行时间优先(SJF)。
贪心排序的核心思想
将任务按某一维度排序后依次处理,可显著降低复杂度。例如,在单处理器上调度独立任务以最小化总延迟,按截止时间升序排列通常是最优策略。
代码实现示例
// Task 表示一个任务
type Task struct {
    id       int
    duration int // 执行时长
    deadline int // 截止时间
}

// 按截止时间升序排序并计算总延迟
sort.Slice(tasks, func(i, j int) bool {
    return tasks[i].deadline < tasks[j].deadline
})
上述代码对任务按截止时间排序,确保紧迫任务优先执行。排序后遍历任务,累计完成时间并与截止时间比较,即可得出延迟总量。该策略在多项式时间内逼近最优解,广泛应用于实时系统调度场景。

3.3 区间合并问题:贪心思维下的高效合并算法

在处理重叠区间时,区间合并问题要求将所有存在交集的区间进行合并,最终输出互不重叠的区间集合。该问题广泛应用于日程安排、资源分配等场景。
核心思路:贪心策略
通过按左端点排序,依次遍历区间,若当前区间与结果集中最后一个区间重叠,则合并;否则将其加入结果集。这种局部最优选择保证全局最优解。
代码实现

// merge 合并区间
func merge(intervals [][]int) [][]int {
    sort.Slice(intervals, func(i, j int) bool {
        return intervals[i][0] < intervals[j][0] // 按左端点升序
    })
    
    var result [][]int
    for _, interval := range intervals {
        if len(result) == 0 || result[len(result)-1][1] < interval[0] {
            result = append(result, interval) // 无重叠,直接添加
        } else {
            result[len(result)-1][1] = max(result[len(result)-1][1], interval[1]) // 更新右端点
        }
    }
    return result
}
上述代码时间复杂度为 O(n log n),主要消耗在排序阶段。合并过程仅需一次线性扫描,体现了贪心算法的高效性。

第四章:复杂场景下的贪心策略设计

4.1 加油站问题:环形路径下的贪心可行性证明

在环形路径上,加油站问题要求判断从某一起点出发能否环绕一周。核心条件是总油量不小于总消耗量。
贪心策略的正确性
若从站点 i 出发无法到达 j,则区间 [i, j) 中任意站点作为起点均会失败。因此可跳过这些点,直接尝试从 j 开始。
算法实现
func canCompleteCircuit(gas []int, cost []int) int {
    total, curr, start := 0, 0, 0
    for i := 0; i < len(gas); i++ {
        diff := gas[i] - cost[i]
        total += diff
        curr += diff
        if curr < 0 {
            start = i + 1
            curr = 0
        }
    }
    return total >= 0 ? start : -1
}
total 跟踪净油量,决定全局可行性;curr 记录当前累积油量,为负时更新起点。

4.2 救生艇问题:双指针与贪心的协同优化

在救生艇问题中,给定一个数组表示乘客体重,每艘船最多载两人且有最大承重限制,目标是求出运送所有人的最少船只数量。该问题可通过贪心策略结合双指针技术高效求解。
贪心策略的核心思想
优先将最重的人与最轻的人配对,若二者体重之和未超限,则共乘一船;否则,重者单独乘船。这样可最大化每艘船的利用率。
双指针实现方案
先对数组排序,使用左右指针分别指向最轻和最重者:
func numRescueBoats(people []int, limit int) int {
    sort.Ints(people)
    left, right := 0, len(people)-1
    boats := 0
    for left <= right {
        if people[left]+people[right] <= limit {
            left++ // 轻者可同行
        }
        right--     // 重者必上船
        boats++
    }
    return boats
}
代码中,leftright 分别代表当前最轻和最重的未安排乘客。每次循环判断是否可配对,若可以则左指针右移,否则仅右指针左移,每轮均消耗一艘船。

4.3 最大数拼接:自定义比较器实现贪心排序

在处理将一组非负整数拼接成最大数的问题时,关键在于定义正确的排序规则。简单的数值大小排序无法满足需求,需通过贪心策略比较两种拼接顺序的字典序大小。
核心思路
对于任意两个数 a 和 b,若字符串拼接结果 a+b > b+a,则 a 应排在 b 前面。该比较规则可保证全局最优解。
代码实现
func largestNumber(nums []int) string {
    strNums := make([]string, len(nums))
    for i, num := range nums {
        strNums[i] = strconv.Itoa(num)
    }

    sort.Slice(strNums, func(i, j int) bool {
        return strNums[i]+strNums[j] > strNums[j]+strNums[i]
    })

    if strNums[0] == "0" {
        return "0"
    }

    return strings.Join(strNums, "")
}
上述代码将整数转为字符串后,使用自定义比较器进行排序。比较时拼接两个字符串并判断字典序,确保高位尽可能放置“更大贡献”的数字。最后合并结果,特殊处理全零情况。

4.4 买卖股票的最佳时机Ⅱ:贪心视角下的多交易建模

在允许多次交易的前提下,目标是最大化累计利润。关键洞察在于:只要明天的价格高于今天,就应今天买入、明天卖出。
贪心策略的直观理解
将价格序列视为趋势片段,每一段上升区间都可拆解为连续的单日价差。因此,总利润等于所有正向价差之和。
算法实现

func maxProfit(prices []int) int {
    profit := 0
    for i := 1; i < len(prices); i++ {
        if prices[i] > prices[i-1] {
            profit += prices[i] - prices[i-1]  // 累加所有正向差值
        }
    }
    return profit
}
该函数遍历价格数组,每当发现价格上涨即刻“交易”。时间复杂度为 O(n),空间复杂度 O(1)。核心思想是将全局最优分解为局部最优决策的叠加。

第五章:总结与进阶学习建议

构建持续学习的技术路径
技术演进迅速,掌握基础后应主动拓展知识边界。例如,在深入理解 Go 语言并发模型后,可进一步研究 runtime 调度机制。以下代码展示了如何通过 sync.Pool 优化高频对象分配:

var bufferPool = sync.Pool{
    New: func() interface{} {
        return new(bytes.Buffer)
    },
}

func getBuffer() *bytes.Buffer {
    return bufferPool.Get().(*bytes.Buffer)
}

func putBuffer(b *bytes.Buffer) {
    b.Reset()
    bufferPool.Put(b)
}
参与开源项目提升实战能力
实际工程项目中,协作与代码审查至关重要。建议从贡献文档、修复简单 bug 入手,逐步参与核心模块开发。以下是推荐的参与步骤:
  • 在 GitHub 上筛选标记为 "good first issue" 的项目
  • 阅读 CONTRIBUTING.md 并配置本地开发环境
  • 提交 PR 前确保通过所有单元测试和 CI 流程
  • 积极回应维护者的评审意见,学习工程规范
系统化知识体系构建
为避免陷入“碎片化学习”,建议建立个人知识图谱。可参考以下分类结构进行归纳:
领域核心技术推荐资源
分布式系统共识算法、服务发现《Designing Data-Intensive Applications》
云原生Kubernetes、Service Mesh官方文档 + CNCF 项目实践
代码转载自:https://pan.quark.cn/s/8ce4326d996e 对于在 CentOS 7 系统中修改网卡配置文件后无法使设置生效的情况,经过实践验证,可以通过使用 nmcli 命令来进行调整。完成修改之后,需要重新启动虚拟机以使更改生效,这样操作流程即告完成。如果设置仍然无法生效,则表明虚拟机在启动过程中所获取的 IP 地址配置并非针对 eth0,此时可以对其它网卡的配置文件进行修改或将其移除。在 CentOS 7 系统中,网络配置的管理机制与早期版本存在差异,主要体现为采用了 Network Manager 服务来负责网络接口的管理。在某些情形下,尽管修改了 `/etc/sysconfig/network-scripts` 目录下的 `ifcfg-eth0` 文件,但网络配置却未能即时生效。此类问题的发生通常源于 CentOS 7 采用了不同于以往的配置读取方法。接下来将具体阐述如何借助 nmcli 命令来处理这一挑战。 以 root 用户身份登录系统并打开终端界面。nmcli 是 Network Manager 提供的命令行界面工具,它支持在命令行环境下执行网络连接的建立、编辑、查询及管理任务。针对修改 eth0 网卡配置的需求,可以遵循以下步骤进行操作: 1. 导航至 `/etc/sysconfig/network-scripts` 目录: ``` cd /etc/sysconfig/network-scripts ``` 2. 检查该目录内是否存在 `ifcfg-eth0.bak` 文件,该备份文件可能是先前调整配置时遗留下来的,若存在可能造成冲突。若发现该文件,可以选择将其删除: ``` [root@localhost netw...
代码转载自:https://pan.quark.cn/s/46fd08fb879c 网管教程 从入门到精通软件篇 ★一。★详尽的xp修复控制台指令及其应用!!! 放入xp(2000)的光盘,安装时选择R,执行修复! Windows XP(涵盖 Windows 2000)的控制台指令是在系统遭遇某些意外状况时的一种极具效用的诊断、检测以及恢复系统功能的工具。笔者确实一直期望能够将这方面的指令进行归纳,此次由老范辛苦整理了这份极具价值的秘籍。 Bootcfg bootcfg 命令用于启动配置与故障恢复(对大多数计算机而言,即 boot.ini 文件)。 带有特定参数的 bootcfg 命令仅在运用故障恢复控制台时方可使用。能够在命令行界面下运用带有不同参数的 bootcfg 命令。 用法: bootcfg /default 设定默认引导选项。 bootcfg /add 向引导清单中增添 Windows 安装。 bootcfg /rebuild 重复整个 Windows 安装流程并让用户选择需添加的项目。 注意:运用 bootcfg /rebuild 之前,应先借助 bootcfg /copy 命令备份 boot.ini 文件。 bootcfg /scan 探查用于 Windows 安装的部磁盘并展示结果。 注意:这些结果被静态存储,并用于当前会话。若在当前会话期间磁盘配置发生变动,为获取更新的探查结果,必须先重启计算机,然后再次探查磁盘。 bootcfg /list 列示引导清单中已有的项目。 bootcfg /disableredirect 在启动引导程序中禁用重定向。 bootcfg /redirect [ PortBaudRrate] |[ useBio...
代码下载链接: https://pan.quark.cn/s/fc524f791b68 AA制程,即Active Alignment,被理解为主动对准,是一种用于确定零部件装配中相对位置的方法。在摄像头封装阶段,涉及图像传感器、镜座、马达、镜头、线路板等多个部件的重复组装,而传统的封装设备如CSP及COB等,均是依据设备设定的参数进行零部件的移动装配,因而零部件的叠加误差会逐渐增大,最终在摄像头上表现为拍照最清晰的位置可能偏离画面中心、四边清晰度不均等现象。伴随智能手机和其他高端电子产品的普及,摄像头模组的性能正日益受到重视。高分辨率、卓越的低光表现以及稳定视频输出是现代用户所期望的。在摄像头模组的制造环节,各部件的精准定位对成像质量具有决定性作用。因此,一种名为“AA制程”(Active Alignment)的前沿技术被开发出来,成为摄像头精密对准的核心技术。 AA制程,即Active Alignment,是一种在摄像头封装过程中应用的主动对准方法。该方法在多个组件装配阶段发挥作用,涵盖图像传感器、镜座、马达、镜头和线路板等部件。传统的封装方式,例如CSP(Chip Scale Package)和COB(Chip On Board),依赖于设备预设的参数进行组装,但随着组件数量的增加,误差也会累积,最终影响摄像头的表现。例如在成像质量上可能出现中心位置偏移、四角清晰度不一致等问题。 AA制程技术的核心在于实时监测与主动调整。在组装过程中,它借助先进的检测设备持续监控半成品的状态,并根据实时信息对组装部件进行精确修正,从而显著降低装配误差。通过这种技术,能够确保摄像头模组中各组件的相对位置准确无误,从而使得最终的成像效果更加稳定,特别是在中心区域和四角的清晰度上...
内容概要:本文介绍了一套基于Matlab实现的光子晶体90度弯曲波导的二维时域有限差分法(2D FDTD)仿真代码,旨在通过数值模拟手段深入研究光子晶体波导中的光传播特性。该资源聚焦于电磁场与光子学领域的仿真技术应用,系统实现了FDTD算法在复杂介质结构中的建模过程,涵盖空间网格剖分、时间步进迭代、完美匹配层(UPML)边界条件处理、总场散射场(TFSF)激励源设置、介电常数分布定义及电磁场演化可视化等核心模块,能够有效分析光在90度弯曲波导中的传输效率、模式分布与反射损耗等关键性能指标。; 适合人群:具备电磁场理论基础和Matlab编程能力的研究生、科研人员以及从事光子晶体器件设计与仿真的工程技术人员。; 使用场景及目标:①用于教学演示FDTD方法的基本原理与算法流程,帮助理解麦克斯韦方程的离散化求解过程;②支撑科研工作中对光子晶体弯曲波导结构的传输特性进行仿真分析与性能优化;③作为开发更复杂光子集成器件(如分束器、滤波器)数值仿真工具的基础框架; 阅读建议:建议使用者结合经典FDTD教材(如Taflove著作)深入理解算法理论,并在Matlab环境中逐模块调试代码,重点关注电场与磁场的交替更新过程、UPML吸收边界的设计实现以及TFSF源的引入方式,从而面提升对时域电磁仿真机制的掌握与应用能力。
内容概要:本文围绕直驱式永磁同步电机(PMSM)的矢量控制仿真模型展开研究,基于Simulink平台构建了完整的电机控制系统仿真模型,涵盖电机本体建模、坐标变换(如Clark变换与Park变换)、磁场定向控制(FOC)、电流环与速度环的PI调节、空间矢量脉宽调制(SVPWM)等核心技术环节,旨在实现对电机转矩与转速的高精度、动态响应良好的控制。通过系统化仿真验证控制策略的有效性与鲁棒性,深入分析各模块间的信号流向与控制逻辑,为电机驱动系统的设计与优化提供理论依据和技术支撑,是理论联系工程实践的重要桥梁。; 适合人群:具备电机学、电力电子与自动控制基础知识,熟悉Simulink/MATLAB仿真环境,从事电气工程、自动化、新能源车辆、智能制造等方向的研究生、科研人员及工程技术人员。; 使用场景及目标:①深入理解永磁同步电机矢量控制的核心原理与系统架构;②掌握在Simulink中从零开始搭建复杂电机控制系统的方法与技巧;③应用于课程设计、毕业论文、科研项目中的控制算法验证、参数整定与性能优化;④为后续的硬件在环(HIL)测试或实物系统开发奠定仿真基础。; 阅读建议:建议结合经典电机控制理论教材同步学习,注重理论推导与仿真实现的对应关系,动手实践模型搭建、参数调试与波形分析,特别关注PI控制器参数整定对系统稳定性、动态响应速度和抗干扰能力的影响,通过反复仿真迭代加深对控制机理的理解。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值