package tanxin
import (
"fmt"
"math"
"sort"
"testing"
)
/*
贪心算法
1. 最自然智慧的算法
2. 用一种局部最功利的标准,总是做出在当前看来是最好的选择
3. 难点在于证明局部最功利的标准可以得到全局最优解
4. 对于贪心算法的学习主要以增加阅历和经验为主
*/
/*
从头到尾讲一道利用贪心算法求解的题目
给定一个字符串组成的数组str
必须把所有的字符串拼接起来
返回所有可能的拼接结果中,字典序最小的结果
[ac,bk,sc,ket]
ac + bk + sc + ket
ba b
反例 bba > bab
1.0 x, y { x字 < y字 x 前 否则 y 前 错误想法
2.0 x, y { x字 + y字 <= y字 + x字 x 前 否则 y 前
排序算法的传递性 a < b b < c | a < c ? 不一定具有传递性
证明出传递性
*/
type IntHashSet struct {
Elem map[int]bool
}
func NewIntHashSet() *IntHashSet {
return &IntHashSet{Elem: map[int]bool{}}
}
func (hs *IntHashSet)Size() int {
return len(hs.Elem)
}
func (hs *IntHashSet)Add(index int) {
hs.Elem[index] = true
}
func (hs *IntHashSet)Contains(value int) bool {
return hs.Elem[value]
}
func (hs *IntHashSet)Remove(value int) {
delete(hs.Elem,value)
}
type StringList struct {
Elem []string
}
func NewStringList() *StringList {
return &StringList{Elem: []string{}}
}
func (sl *StringList)Get(i int) string {
return sl.Elem[i]
}
func (sl *StringList)Add(str string) {
sl.Elem = append(sl.Elem,str)
}
func (sl *StringList)Size() int {
return len(sl.Elem)
}
func lowestString1(strs []string) string { // 全排列 O(N!)
if strs == nil || len(strs) == 0 {
return ""
}
all := NewStringList()
use := NewIntHashSet()
processLowestString1(strs, use,"",all)
lowest := all.Get(0)
for i := 1; i < all.Size(); i++ {
if all.Get(i) <lowest {
lowest = all.Get(i)
}
}
return lowest
}
// strs 里放着所有的字符串
// 已经使用过的字符串的下标在use里登记了,不要再使用了
// 之前使用过的字符串,拼接成了 path
// 用all 收集所有可能的拼接结果
func processLowestString1(strs []string, use *IntHashSet, path string, all *StringList) {
if use.Size() == len(strs) { // base case 收集完了
all.Add(path)
}else {
for i := 0; i < len(strs); i++ {
if !use.Contains(i) { //没用使用过的
use.Add(i)
processLowestString1(strs,use,path + strs[i],all) //
use.Remove(i) // 回溯
}
}
}
}
func TestLowstString1(t *testing.T) {
strs := []string{
"abc","aacb","def","hijk","nlo","nmmol",
}
fmt.Println(lowestString1(strs))
fmt.Println(lowestString2(strs))
}
func lowestString2(strs []string) string {
if strs == nil || len(strs) == 0 {
return ""
}
sort.Slice(strs, func(i, j int) bool {
return strs[i] + strs[j] < strs[j] + strs[i]
})
res := ""
for i := 0 ; i < len(strs); i++ {
res += strs[i]
}
return res
}
/*
贪心算法求解的标准过程
1. 分析业务
2. 根据业务逻辑找到不同的贪心策略
3. 对于能举出反例的策略直接跳过,不能举出反例的策略要证明有效性
这往往是特别困难的,要求数学能力很高且不具有统一的技巧性
*/
/*
贪心算法的解题套路
1.实现一个不依赖叹息你策略的解法X,可以用最暴力的尝试
2.脑补出贪心策略A、贪心策略B、贪心策略C。。。。
3.用解法X 和对数器,用试验的方式得出哪个贪心策略正确
4.不要去纠结贪心策略的证明
*/
/*
笔试出贪心概率 60% ~ 70%
面试出贪心概率 低于20%
笔试:淘汰率 2/5 笔试者写没写过code
面试:区分度 --> 可能需要证明 如哈夫曼树
*/
/*
贪心算法的解题套路实战
一些项目要占用一个会议室宣讲,会议室不能同事容纳两个项目的宣讲。
给你每一个项目开始的时间和结束的时间
你来安排宣讲的日程,要求会议室进行的宣讲次数最多。
返回最多的宣讲场次。
按照会议结束时间早 来贪
*/
type program struct {
start,end int
}
func bestArrange1(programs []program) int {
if programs == nil || len(programs) == 0 {
return 0
}
return ProcessBestArrange1(programs,0,0)
}
// 还剩什么会议 都放programs 里
// done 已经安排过了多少
// timeLine 目前来到的时间点
// 目前来到timeLine 的时间点,已经安排了done多的会议,剩下的会议programs可以自由安排
// 返回能安排的最多会议数量
func ProcessBestArrange1(programs []program,done, timeLine int) int {
if len(programs) == 0 {
return 0
}
max := done
for i := 0; i < len(programs); i++ {
if programs[i].start >= timeLine {
next := copyButException(programs,i)
max = Max(max,ProcessBestArrange1(next,done+1,programs[i].end))
}
}
return max
}
func copyButException(programs []program,i int) []program {
ans := make([]program,len(programs)-1)
index := 0
for k := 0; k < len(programs); k++ {
if k != i {
ans[index] = programs[k]
index++
}
}
return ans
}
func Max(a,b int) int {
if b > a {
return b
}
return a
}
func bestArrange2(programs []program) int {
sort.Slice(programs, func(i, j int) bool {
return programs[i].end < programs[j].end // 根据谁的结束时间早排序
})
timeLine, result := 0, 0
for i := 0; i < len(programs); i++ {
if timeLine <= programs[i].start {
result++
timeLine = programs[i].end
}
}
return result
}
/*
贪心算法的解题套路实战
给定一个字符串str,只由'X'和'.'两种字符构成
'X'表示墙,不能放灯,也不需要点亮
'.'表示居民点,可以放灯,需要点亮
如果灯放在i位置,可以让i-1,i,i+1 三个位置被点亮
返回如果点亮str中所有需要点亮的位置,至少需要几盏灯
*/
func minLight1(road string) int {
if len(road) == 0 {
return 0
}
return processMinLight1([]byte(road),0,NewIntHashSet())
}
func processMinLight1(str []byte, index int, lights *IntHashSet) int {
if index == len(str) { // 结束的时候
for i := 0; i < len(str); i++ {
if str[i] != 'X' { //当前位置是'.'的话
if !lights.Contains(i-1) && !lights.Contains(i) && !lights.Contains(i+1) {
return math.MaxInt
}
}
}
return lights.Size()
}else { // str 还没结束
//i X .
no := processMinLight1(str,index+1,lights)
yes := math.MaxInt
if str[index] == '.' {
lights.Add(index)
yes = processMinLight1(str,index+1,lights)
lights.Remove(index)
}
return Min(no,yes)
}
}
func Min(a , b int) int {
if a > b {
return b
}
return a
}
func minLight2(road string) int { // 在当前步做出最优决定,每一道贪心题没有固定套路
str := []byte(road)
index, light := 0, 0
for index < len(str) {
if str[index] == 'X' { // 既不需要放灯,也不需要被照亮
index++ // 去下一个位置
}else{ // index位置是’.‘
light++ // 可能 index index + 1
if index + 1 == len(str) { // 没有字符
break
}else {
if str[index+1] == 'X' {
index += 2 // 去i
}else {
index += 3
}
}
}
}
return light
}
func TestLight(t *testing.T) {
load := "XXX.XX...X.XX.XX..........XXXXX.XXX....X....XXXX"
fmt.Println(minLight1(load))
fmt.Println(minLight2(load))
}
/*
贪心算法的解题套路实战 哈夫曼编码树
一块金条切成两半,是需要花费和长度数值一样的铜板的。
比如长度为20的金条,不管怎么切,都要花费20个铜板。一群人想整分整块金条,怎么分最省铜板?
例如给定数组[10,20,30]代表一共三个人,整块金条长度为60,金条要分为10,20,30三个部分。
如果先把长度60的金条分成10 和 50 ,花费60,再把长度50的金条分成20和30,花费50,一共花费110铜板
但如果先把长度60的金条分成30和30,花费60,再把长度30的金条分成10和20 ,花费30,一共花费90铜板
输入一个数组,返回分割的最小代价
金条的总长度是数组的累加和
[3,9,6,4,1]
小根堆,大根堆,排序 是贪心最常用的手段
*/
func lessMoney1(arr []int) int {
if arr == nil || len(arr) == 0 {
return 0
}
return processLessMoney(arr, 0)
}
func processLessMoney(arr []int, pre int) int {
if len(arr) == 1 {
return pre
}
ans := math.MaxInt
for i := 0; i < len(arr); i++ {
for j := i+1; j < len(arr); j++ {
ans = Min(ans,processLessMoney(copyAndMergeTwo(arr,i,j),pre + arr[i] + arr[j]))
}
}
return ans
}
func copyAndMergeTwo(arr []int, i, j int) []int {
ans := make([]int,len(arr) -1 )
ansi := 0
for arri := 0; arri < len(arr); arri++ {
if arri != i && arri != j {
ans[ansi] = arr[arri]
ansi++
}
}
ans[ansi] = arr[i] + arr[j]
return ans
}
func TestLessMoney(t *testing.T) {
fmt.Println(lessMoney1([]int{10,20,30}))
fmt.Println(lessMoney1([]int{1,3,4,6,9}))
fmt.Println(lessMoney2([]int{10,20,30}))
fmt.Println(lessMoney2([]int{1,3,4,6,9}))
}
func lessMoney2(arr []int) int {
heap := NewHeap(func(a,b interface{}) bool {
return a.(int) < b.(int)
})
for _, v := range arr {
heap.push(v)
}
sum := 0
cur := 0
for heap.size() > 1 {
cur = heap.pop().(int) + heap.pop().(int)
sum += cur
heap.push(cur)
}
return sum
}
type Heap struct {
Elem []interface{}
heapSize int
comparator func(a,b interface{}) bool
}
func NewHeap(com func(a,b interface{}) bool) *Heap {
return &Heap{
Elem: make([]interface{},100), //大小为100 的堆
heapSize: 0,
comparator: com,
}
}
func (h *Heap)size() int {
return h.heapSize
}
func (h *Heap) isEmpty() bool {
return h.heapSize == 0
}
func (h *Heap)swap(i,j int) {
h.Elem[i],h.Elem[j] = h.Elem[j], h.Elem[i]
}
func (h *Heap)push(val interface{}) {
h.Elem[h.heapSize] = val
h.insert(h.heapSize)
h.heapSize++
}
func (h *Heap)pop() interface{} {
res := h.Elem[0]
h.heapSize--
h.swap(0,h.heapSize)
h.heapify(0,h.heapSize)
return res
}
func (h *Heap)insert(index int) {
for h.comparator(h.Elem[index] , h.Elem[(index-1)/2]) {
h.swap(index,(index-1)/2)
index = (index - 1) / 2
}
}
func (h *Heap)peek() interface{} {
return h.Elem[0]
}
func (h *Heap)heapify(index, heapSize int) {
left := 2 * index + 1
for left < heapSize {
best := left
if best + 1 < heapSize && h.comparator(h.Elem[best+1],h.Elem[best]) {
best++
}
if h.comparator(h.Elem[index],h.Elem[best]) {
break
}
h.swap(best,index)
index = best
left = 2 * index + 1
}
}
func TestMinHeap(t *testing.T) {
heap := NewHeap(func(a,b interface{}) bool {
return a.(int) < b.(int)
})
heap.push(123)
heap.push(21)
heap.push(99)
heap.push(32)
heap.push(2332)
heap.push(3424)
heap.push(-322)
heap.push(-32)
heap.push(-32)
heap.push(0)
fmt.Println(heap.size())
fmt.Println(heap.Elem)
for !heap.isEmpty() {
fmt.Println(heap.pop().(int))
}
}
/*
贪心算法的解题套路实战
输入:正整数 costs、正整数组profits、 正整数K、正整数M
cost[i]表示i号项目的花费
profits[i]表示i号项目在扣除花费之后还能挣到的钱(纯利润)
K表示你只能串行的最多做K个项目
M表示你初始的资金
说明:每做完一个项目,马上获得的收益,可以支持你去做下一个项目。不能并行的做项目
输出:你最后获得的最大钱数。
小根堆 花费组织
1 1
1 3
4 3
5 2
此时被锁住的项目
大根堆
解锁的项目
*/
type project struct {
p int
c int
}
func findMaximizedCapital(K , W int, Profits, Capital []int) int {
minCostQ := NewHeap(func(a,b interface{}) bool { //小根堆
return a.(project).c < b.(project).c
})
maxProfitQ := NewHeap(func(a, b interface{}) bool { //大根堆
return a.(project).p > b.(project).p
})
for i := 0; i < len(Profits); i++ {
minCostQ.push(project{c: Capital[i],p: Profits[i]})
}
for i := 0; i < K; i++ {
for !minCostQ.isEmpty() && minCostQ.peek().(project).c <= W {
maxProfitQ.push(minCostQ.pop().(project))
}
if maxProfitQ.isEmpty() {
return W
}
W += maxProfitQ.pop().(project).p
}
return W
}
贪心算法训练汇总
于 2021-10-27 15:22:56 首次发布