贪心算法三个小练习题(入门到进阶)

练习 1:活动区间调度(贪心入门)

给定多个活动区间 [start,end],同一时间只能参加一个活动,求最多能参加多少个活动

贪心核心思想

局部最优→ 全局最优:每次选结束时间最早的活动,结束越早,剩余时间越多,能选更多活动

代码及解析

package main

import (
	"fmt"
	"sort"
)

// 定义活动结构体
type Interval struct {
	start int
	end   int
}

func maxActivity(intervals []Interval) int {
	// 1.贪心第一步:排序
	// 按结束时间升序排列
	sort.Slice(intervals, func(i, j int) bool {
		return intervals[i].end < intervals[j].end
	})

	count := 0
	lastEnd := -1 // 记录上一个活动的结束时间

	// 2.遍历选择
	for _, cur := range intervals {
		// 当前活动开始 >= 上一个结束 → 不重叠,可以选
		if cur.start >= lastEnd {
			count++
			lastEnd = cur.end
		}
	}
	return count
}

func main() {
	list := []Interval{
		{1, 3},
		{2, 4},
		{3, 5},
		{6, 7},
	}
	fmt.Println(maxActivity(list)) // 输出:3
}

步骤拆解:

  1. 排序:让所有活动按结束时间从小到大;
  2. 初始化:记录上一场活动结束时间;
  3. 遍历:
    • 不重叠 → 选中,更新结束时间;
    • 重叠 → 直接跳过;
  4. 最终统计选中总数。

练习 2:分发饼干(LeetCode 455 极简贪心)

贪心核心思想

局部最优:最小的饼干满足胃口最小的孩子,节省大饼干留给大胃口。

代码及解析

package main

import (
	"fmt"
	"sort"
)

func findContentChildren(g []int, s []int) int {
	// 1.全部排序
	sort.Ints(g)
	sort.Ints(s)

	child := 0
	cookie := 0

	// 2.双指针遍历
	for child < len(g) && cookie < len(s) {
		// 当前饼干能满足孩子
		if s[cookie] >= g[child] {
			child++  // 满足人数+1
			cookie++ // 换下一块饼干
		} else {
			// 饼干太小,直接换下一块
			cookie++
		}
	}
	return child
}

func main() {
	g := []int{1, 2, 3}
	s := []int{1, 1}
	fmt.Println(findContentChildren(g, s)) // 输出:1
}

步骤拆解

  1. 孩子、饼干数组全部升序排序
  2. 双指针:分别指向当前孩子、当前饼干;
  3. 饼干够吃:满足孩子,两个指针都后移;
  4. 饼干不够:只换更大的饼干;
  5. 最终满足的孩子数即为答案。

练习 3:LeetCode 68 文本左右对齐(进阶贪心)

贪心核心思想

局部最优:每一行尽可能多装入单词,保证整体排版最优、行数最少。

代码及解析

package main

import (
	"fmt"
	"strings"
)

func fullJustify(words []string, maxWidth int) []string {
	var result []string
	totalWords := len(words)
	index := 0 // 当前处理到第几个单词

	// ===================== 步骤 1:循环处理每一行 =====================
	for index < totalWords {
		// ===================== 步骤 2:贪心选择:当前行最多放哪些单词 =====================
		startIndex := index               // 本行第一个单词
		currentLineLength := len(words[index])
		index++

		// 能放下就继续放:空格 + 单词长度不超过 maxWidth
		for index < totalWords && currentLineLength+1+len(words[index]) <= maxWidth {
			currentLineLength += 1 + len(words[index])
			index++
		}

		// ===================== 步骤 3:计算空格数量 =====================
		wordCount := index - startIndex          // 本行单词数
		allWordsLength := currentLineLength - (wordCount - 1) // 纯单词总长度
		totalSpaces := maxWidth - allWordsLength // 本行需要填充的总空格数

		var currentLine string

		// ===================== 步骤 4:分情况构建行 =====================
		// 情况 A:最后一行 或 只有一个单词 → 左对齐
		if index == totalWords || wordCount == 1 {
			currentLine = words[startIndex]
			// 单词之间只加一个空格
			for i := startIndex + 1; i < index; i++ {
				currentLine += " " + words[i]
			}
			// 末尾补空格
			for len(currentLine) < maxWidth {
				currentLine += " "
			}
		} else {
			// 情况 B:普通行 → 左右对齐,空格均匀分配
			gapCount := wordCount - 1                // 单词间的空隙数
			baseSpaces := totalSpaces / gapCount     // 每个空隙最少空格数
			extraSpaces := totalSpaces % gapCount    // 前几个空隙要多一个空格

			currentLine = ""
			for i := startIndex; i < index; i++ {
				currentLine += words[i]
				// 不是最后一个单词就补空格
				if i != index-1 {
					if i-startIndex < extraSpaces {
						currentLine += strings.Repeat(" ", baseSpaces+1)
					} else {
						currentLine += strings.Repeat(" ", baseSpaces)
					}
				}
			}
		}

		// 把当前行加入结果集
		result = append(result, currentLine)
	}

	return result
}

func main() {
	words := []string{"This", "is", "an", "example", "of", "text", "justification."}
	res := fullJustify(words, 16)
	for _, line := range res {
		fmt.Println("|" + line + "|")
	}
}

核心分段拆解

  1. 贪心选行从当前单词开始,不断往后加,直到再加一个单词 + 空格就超宽度;
  2. 长度计算算出所有单词纯长度,用总行宽减去单词长度,得到需要填充的全部空格;
  3. 分支处理
  • 最后一行:单词间单个空格,后面补空格;
  • 普通行:均分空格,多余空格优先补在左侧间隔

三篇练习 统一总结

1.贪心算法核心:局部最优 → 全局最优

2.通用解题步骤:

        确定贪心策略;

        排序 / 顺序遍历;

        逐一步骤做选择,不回溯、不反悔;

3.Go 贪心高频场景:字符串排版:

        贪心填充 + 规则化拼接。数组匹配:

        双指针 + 排序;

        区间问题:

        按结束 / 开始排序;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值