贪心算法训练汇总

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
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

metabit

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值