四、leetcode刷题之【贪心算法】

[TOC]

局部最优解->全局最优

455. 分发饼干(简单/贪心)

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
 
输入: g = [1,2,3], s = [1,1]
输出: 1
解释: 
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
 
输入: g = [1,2], s = [1,2,3]
输出: 2
解释: 
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.
 
// g是胃口,s是饼干
// 用大饼干优先满足胃口大的,并统计满足小孩数量。 https://leetcode.cn/problems/assign-cookies/solution/455-fen-fa-bing-gan-tan-xin-jing-dian-ti-o6k6/
func findContentChildren(g []int, s []int) int {
    sort.Ints(g)
    sort.Ints(s)

    gLen := len(g)
    sLen := len(s)

    index := sLen - 1
    count := 0
    for i := gLen - 1; i > 0; i-- {
        if index >= 0 && s[index] >= g[i] {
            count++
            index--
        }
    }
    return count
}

392. 判断子序列(简单/贪心)

452. 用最少数量的箭引爆气球(中等/贪心)

有一些球形气球贴在一堵用 XY 平面表示的墙面上。墙面上的气球记录在整数数组 points ,其中points[i] = [xstart, xend] 表示水平直径在 xstart 和 xend之间的气球。你不知道气球的确切 y 坐标。一支弓箭可以沿着 x 轴从不同点 完全垂直 地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足  xstart ≤ x ≤ xend,则该气球会被 引爆 。可以射出的弓箭的数量 没有限制 。 弓箭一旦被射出之后,可以无限地前进。给你一个数组 points ,返回引爆所有气球所必须射出的 最小 弓箭数 。
 
输入:points = [[10,16],[2,8],[1,6],[7,12]]
输出:2
解释:气球可以用2支箭来爆破:
-在x = 6处射出箭,击破气球[2,8]和[1,6]。
-在x = 11处发射箭,击破气球[10,16]和[7,12]。

输入:points = [[1,2],[3,4],[5,6],[7,8]]
输出:4
解释:每个气球需要射出一支箭,总共需要4支箭。
// https://leetcode.cn/problems/minimum-number-of-arrows-to-burst-balloons/solution/yong-zui-shao-shu-liang-de-jian-yin-bao-qi-qiu-1-2/
// 一定存在一种最优(射出的箭数最小)的方法,使得每一支箭的射出位置都恰好对应着某一个气球的右边界。
func findMinArrowShots(points [][]int) int {
    // 按照右边界进行排序
    sort.Slice(points, func(i, j int) bool {
        return points[i][1] < points[j][1]
    })

    fmt.Println(points)
    count := 1 // 必定有一只箭
    end := points[0][1] 
    for i := 1; i < len(points); i++ {
        if points[i][0] > end { //前一个气球重点 < 当前气球起点,则数量+1,这里注意是point[i][0],注意是 > 
            count++
            end = points[i][1] //  这里注意是point[i][1]
        }
    }
    return count
}

435. 无重叠区间(中等/贪心)

给定一个区间的集合 intervals ,其中 intervals[i] = [starti, endi] 。返回 需要移除区间的最小数量,使剩余区间互不重叠 。

输入: intervals = [[1,2],[2,3],[3,4],[1,3]]
输出: 1
解释: 移除 [1,3] 后,剩下的区间没有重叠。

输入: intervals = [ [1,2], [1,2], [1,2] ]
输出: 2
解释: 你需要移除两个 [1,2] 来使剩下的区间没有重叠。

输入: intervals = [ [1,2], [2,3] ]
输出: 0
解释: 你不需要移除任何区间,因为它们已经是无重叠的了。
func eraseOverlapIntervals(intervals [][]int) int {
    sort.Slice(intervals, func(i, j int) bool {
        return intervals[i][1] < intervals[j][1]
    })
    fmt.Println(intervals)
    count := 1
    end := intervals[0][1]
    for i := 1; i < len(intervals); i++ {
        if intervals[i][0] >= end { // /当区间的左边界大于上一个区间的右边界的时候 说明是一对不重合区间,注意是 >=
            count++
            end = intervals[i][1]
        }

    }
    return len(intervals) - count // //intervals的长度减去最多的不重复的区间 就是最少删除区间的个数
}

1167. 连接棒材的最低费用(贪心/小顶堆)

你有一些长度为正整数的棍子。这些长度以数组 sticks 的形式给出, sticks[i] 是 第i个 木棍的长度。你可以通过支付 x + y 的成本将任意两个长度为 x 和 y 的棍子连接成一个棍子。你必须连接所有的棍子,直到剩下一个棍子。返回以这种方式将所有给定的棍子连接成一个棍子的 最小成本 。

输入:sticks = [2,4,3]
输出:14
解释:从 sticks = [2,4,3] 开始。
1. 连接 2 和 3 ,费用为 2 + 3 = 5 。现在 sticks = [5,4]
2. 连接 5 和 4 ,费用为 5 + 4 = 9 。现在 sticks = [9]
所有木棍已经连成一根,总费用 5 + 9 = 14

输入:sticks = [1,8,3,5]
输出:30
解释:从 sticks = [1,8,3,5] 开始。
1. 连接 1 和 3 ,费用为 1 + 3 = 4 。现在 sticks = [4,8,5]
2. 连接 4 和 5 ,费用为 4 + 5 = 9 。现在 sticks = [9,8]
3. 连接 9 和 8 ,费用为 9 + 8 = 17 。现在 sticks = [17]
所有木棍已经连成一根,总费用 4 + 9 + 17 = 30

输入:sticks = [5]
输出:0
解释:只有一根木棍,不必再连接。总费用 0
、、https://www.jianshu.com/p/80a3d7add522

type intHeap []int

func (key intHeap) Len() int {
    return len(key)
}

func (key intHeap) Less(i int, j int) bool {
    return key[i] < key[j]
}

func (key intHeap) Swap(i int, j int) {
    key[i], key[j] = key[j], key[i]
}

func (h *intHeap) Push(x interface{}) {
    *h = append(*h, x.(int))
}

func (h *intHeap) Pop() interface{} {
    old := *h
    n := len(old)
    x := old[n-1]
    *h = old[0 : n-1]
    return x
}

func (h *intHeap) push(x int) {
    heap.Push(h, x)
}

func (h *intHeap) pop() int {
    return heap.Pop(h).(int)
}

func connectSticks(sticks []int) int {
    res := 0
    sticksHeap := intHeap{}
    sticksHeap = sticks
    heap.Init(&sticksHeap)
    for sticksHeap.Len() > 1 {
        first, second := sticksHeap.pop(), sticksHeap.pop()
        sticksHeap.push(first+second)
        res += first+second
    }   
    return res
}

860. 柠檬水找零(简单/其实感觉没用到贪心)

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。注意,一开始你手头没有任何零钱。给你一个整数数组 bills ,其中 bills[i] 是第 i 位顾客付的账。如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

输入:bills = [5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。
 
输入:bills = [5,5,10,10,20]
输出:false
解释:
前 2 位顾客那里,我们按顺序收取 2 张 5 美元的钞票。
对于接下来的 2 位顾客,我们收取一张 10 美元的钞票,然后返还 5 美元。
对于最后一位顾客,我们无法退回 15 美元,因为我们现在只有两张 10 美元的钞票。
由于不是每位顾客都得到了正确的找零,所以答案是 false。
// 一开始我还想着利用map来存,我真是傻逼
func lemonadeChange(bills []int) bool {
    five, ten := 0, 0
    for _, bill := range bills {
        if bill == 5 {
            five++
        } else if bill == 10 {
            if five == 0 {
                return false
            }
            five--
            ten++
        } else {
            if five > 0 && ten > 0 {
                five--
                ten--
            } else if five >= 3 {
                five -= 3
            } else {
                return false
            }
        }
    }
    return true
}

56. 合并区间 (中等/感觉和贪心也没啥关系)

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

// ================ 解法一 ====================================
func merge(intervals [][]int) [][]int {
    //先从小到大排序
    sort.Slice(intervals, func(i, j int) bool {
        return intervals[i][0] < intervals[j][0]
    })
    //再弄重复的
    for i := 0; i < len(intervals)-1; i++ {
        // 当前右边界intervals[i][1]和下一区间左边界intervals[i+1][0]比较
        if intervals[i][1] >= intervals[i+1][0] {
            // 更新区间右边界最大值
            intervals[i][1] = max(intervals[i][1], intervals[i+1][1]) //赋值最大值
            // 删除下一个区间元素,因为此时当前区间右边界已经变了
            intervals = append(intervals[:i+1], intervals[i+2:]...) // 很重要,删除i+1这一行
            i--
        }
    }
    return intervals
}

func max(a, b int) int {
    if a > b {
        return a
    }
    return b
}

//  ====================  解法二(其实就是硬写,挺好理解的)  ============================
func merge(intervals [][]int) [][]int {
    sort.Slice(intervals, func(i, j int) bool {
        return intervals[i][0] < intervals[j][0]
    })
    var res [][]int
    start, end := intervals[0][0], intervals[0][1]
    for i := 1; i < len(intervals); i++ {
        if intervals[i][0] <= end {
            if intervals[i][1] > end {
                end = intervals[i][1]
            }
        } else {
            res = append(res, []int{start, end})
            start, end = intervals[i][0], intervals[i][1]
        }
    }
    return append(res, []int{start, end})
}

55. 跳跃游戏(中等)

给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个下标。

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。

45. 跳跃游戏 II(中等)

给你一个非负整数数组 nums ,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。你的目标是使用最少的跳跃次数到达数组的最后一个位置。假设你总是可以到达数组的最后一个位置。

输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
     从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

输入: nums = [2,3,0,1,4]
输出: 2

1306. 跳跃游戏 III(中等)

这里有一个非负整数数组 arr,你最开始位于该数组的起始下标 start 处。当你位于下标 i 处时,你可以跳到 i + arr[i] 或者 i - arr[i]。请你判断自己是否能够跳到对应元素值为 0 的 任一 下标处。注意,不管是什么情况下,你都无法跳到数组之外。

输入:arr = [4,2,3,0,3,1,2], start = 5
输出:true
解释:
到达值为 0 的下标 3 有以下可能方案: 
下标 5 -> 下标 4 -> 下标 1 -> 下标 3 
下标 5 -> 下标 6 -> 下标 4 -> 下标 1 -> 下标 3 

输入:arr = [4,2,3,0,3,1,2], start = 0
输出:true 
解释:
到达值为 0 的下标 3 有以下可能方案: 
下标 0 -> 下标 4 -> 下标 1 -> 下标 3

122. 买卖股票的最佳时机 II(中等/贪心)

给你一个整数数组 prices ,其中 prices[i] 表示某支股票第 i 天的价格。在每一天,你可以决定是否购买和/或出售股票。你在任何时候 最多 只能持有 一股 股票。你也可以先购买,然后在 同一天 出售。返回 你能获得的 最大 利润 。

输入:prices = [7,1,5,3,6,4]
输出:7
解释:在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
     随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6 - 3 = 3 。
     总利润为 4 + 3 = 7 。

输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5 - 1 = 4 。
     总利润为 4 。
/*
res =  (prices[3] - prices[2]) + (prices[2] - prices[1]) + (prices[1] - prices[0])
    =  prices[3] - prices[0]
按照贪心算法,在下标为 1、2、3 的这三天,我们做的操作应该是买进昨天的,卖出今天的。
等价于:在下标为 0 的那一天买入,在下标为 3 的那一天卖出。
贪心算法的直觉:由于不限制交易次数,只要今天股价比昨天高,就交易。
*/

func maxProfit(prices []int) (ans int) {
    for i := 1; i < len(prices); i++ {
        ans += max(0, prices[i]-prices[i-1])
    }
    return
}

561. 数组拆分(简单)

给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), ..., (an, bn) ,使得从 1 到 n 的 min(ai, bi) 总和最大。返回该 最大总和 。

输入:nums = [1,4,3,2]
输出:4
解释:所有可能的分法(忽略元素顺序)为:
1. (1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3
2. (1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3
3. (1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4
所以最大总和为 4

输入:nums = [6,2,6,5,1,2]
输出:9
解释:最优的分法为 (2, 1), (2, 5), (6, 6). min(2, 1) + min(2, 5) + min(6, 6) = 1 + 2 + 6 = 9

1710. 卡车上的最大单元数(简单/贪心)

请你将一些箱子装在 一辆卡车 上。给你一个二维数组 boxTypes ,其中 boxTypes[i] = [numberOfBoxesi, numberOfUnitsPerBoxi] :numberOfBoxesi 是类型 i 的箱子的数量。numberOfUnitsPerBoxi 是类型 i 每个箱子可以装载的单元数量。整数 truckSize 表示卡车上可以装载 箱子 的 最大数量 。只要箱子数量不超过 truckSize ,你就可以选择任意箱子装到卡车上。返回卡车可以装载 单元 的 最大 总数。

输入:boxTypes = [[1,3],[2,2],[3,1]], truckSize = 4
输出:8
解释:箱子的情况如下:
- 1 个第一类的箱子,里面含 3 个单元。
- 2 个第二类的箱子,每个里面含 2 个单元。
- 3 个第三类的箱子,每个里面含 1 个单元。
可以选择第一类和第二类的所有箱子,以及第三类的一个箱子。
单元总数 = (1 * 3) + (2 * 2) + (1 * 1) = 8

输入:boxTypes = [[5,10],[2,5],[4,7],[3,9]], truckSize = 10
输出:91
func maximumUnits(boxTypes [][]int, truckSize int) int {
    sort.Slice(boxTypes, func(i, j int) bool {
        return boxTypes[i][1] > boxTypes[j][1]
    })
    res := 0
    for _, v := range boxTypes {
        size := min(truckSize, v[0])
        res += size * v[1]
        truckSize -= size
    }
    return res
}

1217. 玩筹码(简单/贪心)

有 n 个筹码。第 i 个筹码的位置是 position[i] 。我们需要把所有筹码移到同一个位置。在一步中,我们可以将第 i 个筹码的位置从 position[i] 改变为:
position[i] + 2 或 position[i] - 2 ,此时 cost = 0
position[i] + 1 或 position[i] - 1 ,此时 cost = 1
返回将所有筹码移动到同一位置上所需要的 最小代价 。

输入:position = [1,2,3]
输出:1
解释:第一步:将位置3的筹码移动到位置1,成本为0。
第二步:将位置2的筹码移动到位置1,成本= 1。
总成本是1。

输入:position = [2,2,2,3,3]
输出:2
解释:我们可以把位置3的两个筹码移到位置2。每一步的成本为1。总成本= 2。

输入:position = [1,1000000000]
输出:1

1247. 交换字符使得字符串相同(中等)

有两个长度相同的字符串 s1 和 s2,且它们其中 只含有 字符 "x" 和 "y",你需要通过「交换字符」的方式使这两个字符串相同。每次「交换字符」的时候,你都可以在两个字符串中各选一个字符进行交换。交换只能发生在两个不同的字符串之间,绝对不能发生在同一个字符串内部。也就是说,我们可以交换 s1[i] 和 s2[j],但不能交换 s1[i] 和 s1[j]。最后,请你返回使 s1 和 s2 相同的最小交换次数,如果没有方法能够使得这两个字符串相同,则返回 -1 。

输入:s1 = "xx", s2 = "yy"
输出:1
解释:
交换 s1[0] 和 s2[1],得到 s1 = "yx",s2 = "yx"。

输入:s1 = "xy", s2 = "yx"
输出:2
解释:
交换 s1[0] 和 s2[0],得到 s1 = "yy",s2 = "xx" 。
交换 s1[0] 和 s2[1],得到 s1 = "xy",s2 = "xy" 。
注意,你不能交换 s1[0] 和 s1[1] 使得 s1 变成 "yx",因为我们只能交换属于两个不同字符串的字符。

输入:s1 = "xx", s2 = "xy"
输出:-1

输入:s1 = "xxyyxyxyxx", s2 = "xyyxyxxxyx"
输出:4

1400. 构造 K 个回文字符串(中等/贪心)

给你一个字符串 s 和一个整数 k 。请你用 s 字符串中 所有字符 构造 k 个非空 回文串 。如果你可以用 s 中所有字符构造 k 个回文字符串,那么请你返回 True ,否则返回 False 。

输入:s = "annabelle", k = 2
输出:true
解释:可以用 s 中所有字符构造 2 个回文字符串。
一些可行的构造方案包括:"anna" + "elble","anbna" + "elle","anellena" + "b"

输入:s = "leetcode", k = 3
输出:false
解释:无法用 s 中所有字符构造 3 个回文串。

输入:s = "true", k = 4
输出:true
解释:唯一可行的方案是让 s 中每个字符单独构成一个字符串。

输入:s = "yzyzyzyzyzyzyzy", k = 2
输出:true
解释:你只需要将所有的 z 放在一个字符串中,所有的 y 放在另一个字符串中。那么两个字符串都是回文串。

输入:s = "cr", k = 7
输出:false
解释:我们没有足够的字符去构造 7 个回文串。

921. 使括号有效的最少添加(中等/贪心)

只有满足下面几点之一,括号字符串才是有效的:
它是一个空字符串,或者
它可以被写成 AB (A 与 B 连接), 其中 A 和 B 都是有效字符串,或者
它可以被写作 (A),其中 A 是有效字符串。
给定一个括号字符串 s ,在每一次操作中,你都可以在字符串的任何位置插入一个括号

例如,如果 s = "()))" ,你可以插入一个开始括号为 "(()))" 或结束括号为 "())))" 。
返回 为使结果字符串 s 有效而必须添加的最少括号数。

输入:s = "())"
输出:1

输入:s = "((("
输出:3
func minAddToMakeValid(S string) int {
    //left, right 分别表示左右未配对的括号
    left, right := 0, 0
    for _, s := range S {
        if s == '(' {
            left++
        } else if left > 0 && s == ')' {
            left--
        } else {
            right++
        }
    }
    return left + right
}

1029. 两地调度(中等/贪心)

公司计划面试 2n 人。给你一个数组 costs ,其中 costs[i] = [aCosti, bCosti] 。第 i 人飞往 a 市的费用为 aCosti ,飞往 b 市的费用为 bCosti 。返回将每个人都飞到 a 、b 中某座城市的最低费用,要求每个城市都有 n 人抵达。

输入:costs = [[10,20],[30,200],[400,50],[30,20]]
输出:110
解释:
第一个人去 a 市,费用为 10。
第二个人去 a 市,费用为 30。
第三个人去 b 市,费用为 50。
第四个人去 b 市,费用为 20。

最低总费用为 10 + 30 + 50 + 20 = 110,每个城市都有一半的人在面试。

输入:costs = [[259,770],[448,54],[926,667],[184,139],[840,118],[577,469]]
输出:1859
// 思路很重要:
我们这样来看这个问题,公司首先将这 2N 个人全都安排飞往 B 市,
再选出 N 个人改变它们的行程,让他们飞往 A 市。
如果选择改变一个人的行程,那么公司将会额外付出 price_A - price_B 的费用,这个费用可正可负。
因此最优的方案是,选出 price_A - price_B 最小的 N 个人,让他们飞往 A 市,其余人飞往 B 市。
func twoCitySchedCost(costs [][]int) int {
    sort.Slice(costs, func(i, j int) bool {
        return (costs[i][0] - costs[i][1]) > (costs[j][0] - costs[j][1])
    })

    cost := 0
    for i := 0; i < len(costs)/2; i++ { // 去B城市的
        cost += costs[i][1]
    }

    for i := len(costs) / 2; i < len(costs); i++ { // 去A城市的
        cost += costs[i][0]
    }

    return cost
}

1605. 给定行和列的和求可行矩阵 (中等/贪心)

给你两个非负整数数组 rowSum 和 colSum ,其中 rowSum[i] 是二维矩阵中第 i 行元素的和, colSum[j] 是第 j 列元素的和。换言之你不知道矩阵里的每个元素,但是你知道每一行和每一列的和。请找到大小为 rowSum.length x colSum.length 的任意 非负整数 矩阵,且该矩阵满足 rowSum 和 colSum 的要求。请你返回任意一个满足题目要求的二维矩阵,题目保证存在 至少一个 可行矩阵。

输入:rowSum = [3,8], colSum = [4,7]
输出:[[3,0],
      [1,7]]
解释:
第 0 行:3 + 0 = 3 == rowSum[0]
第 1 行:1 + 7 = 8 == rowSum[1]
第 0 列:3 + 1 = 4 == colSum[0]
第 1 列:0 + 7 = 7 == colSum[1]
行和列的和都满足题目要求,且所有矩阵元素都是非负的。
另一个可行的矩阵为:[[1,2],
                  [3,5]]

输入:rowSum = [5,7,10], colSum = [8,6,8]
输出:[[0,5,0],
      [6,1,0],
      [2,0,8]]

输入:rowSum = [14,9], colSum = [6,9,8]
输出:[[0,9,5],
      [6,0,3]]

135. 分发糖果 (困难/贪心)

n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。你需要按照以下要求,给这些孩子分发糖果:
每个孩子至少分配到 1 个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个、第三个孩子分发 2、1、2 颗糖果。

输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发 1、2、1 颗糖果。
     第三个孩子只得到 1 颗糖果,这满足题面中的两个条件。

53. 最大子数组和(中等/贪心)

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

输入:nums = [5,4,-1,7,8]
输出:23

280. 摆动排序 (会员/中等/贪心)

给你一个的整数数组 nums, 将该数组重新排序后使 nums[0] <= nums[1] >= nums[2] <= nums[3]... 
输入数组总是有一个有效的答案。

输入:nums = [3,5,2,1,6,4]
输出:[3,5,1,6,2,4]
解释:[1,6,2,5,3,4]也是有效的答案

输入:nums = [6,6,5,6,3,8]
输出:[6,6,5,6,3,8]

738. 单调递增的数字(中等/贪心)

当且仅当每个相邻位数上的数字 x 和 y 满足 x <= y 时,我们称这个整数是单调递增的。给定一个整数 n ,返回 小于或等于 n 的最大数字,且数字呈 单调递增 。

输入: n = 10
输出: 9

输入: n = 1234
输出: 1234

输入: n = 332
输出: 299

402. 移掉 K 位数字(中等/贪心)

给你一个以字符串表示的非负整数 num 和一个整数 k ,移除这个数中的 k 位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。

输入:num = "1432219", k = 3
输出:"1219"
解释:移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219 。

输入:num = "10200", k = 1
输出:"200"
解释:移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。

输入:num = "10", k = 2
输出:"0"
解释:从原数字移除所有的数字,剩余为空就是 0 。

861. 翻转矩阵后的得分(中等/贪心)

有一个二维矩阵 A 其中每个元素的值为 0 或 1 。移动是指选择任一行或列,并转换该行或列中的每一个值:将所有 0 都更改为 1,将所有 1 都更改为 0。在做出任意次数的移动后,将该矩阵的每一行都按照二进制数来解释,矩阵的得分就是这些数字的总和。返回尽可能高的分数。

输入:[[0,0,1,1],[1,0,1,0],[1,1,0,0]]
输出:39
解释:
转换为 [[1,1,1,1],[1,0,0,1],[1,1,1,1]]
0b1111 + 0b1001 + 0b1111 = 15 + 9 + 15 = 39

555. 分割连接字符串(会员/中等/贪心)

给定一个字符串列表 strs,你可以将这些字符串连接成一个循环字符串,对于每个字符串,你可以选择是否翻转它。在所有可能的循环字符串中,你需要分割循环字符串(这将使循环字符串变成一个常规的字符串),然后找到字典序最大的字符串。具体来说,要找到字典序最大的字符串,你需要经历两个阶段:

将所有字符串连接成一个循环字符串,你可以选择是否翻转某些字符串,并按照给定的顺序连接它们。
在循环字符串的某个位置分割它,这将使循环字符串从分割点变成一个常规的字符串。
你的工作是在所有可能的常规字符串中找到字典序最大的一个。

输入: strs = ["abc","xyz"]
输出: "zyxcba"
解释: 你可以得到循环字符串 "-abcxyz-", "-abczyx-", "-cbaxyz-", "-cbazyx-",其中 '-' 代表循环状态。 
答案字符串来自第四个循环字符串, 你可以从中间字符 'a' 分割开然后得到 "zyxcba"。

输入: strs = ["abc"]
输出: "cba"

2144. 打折购买糖果的最小开销(简单/贪心)

一家商店正在打折销售糖果。每购买 两个 糖果,商店会 免费 送一个糖果。免费送的糖果唯一的限制是:它的价格需要小于等于购买的两个糖果价格的 较小值 。比方说,总共有 4 个糖果,价格分别为 1 ,2 ,3 和 4 ,一位顾客买了价格为 2 和 3 的糖果,那么他可以免费获得价格为 1 的糖果,但不能获得价格为 4 的糖果。
给你一个下标从 0 开始的整数数组 cost ,其中 cost[i] 表示第 i 个糖果的价格,请你返回获得 所有 糖果的 最小 总开销。

输入:cost = [1,2,3]
输出:5
解释:我们购买价格为 2 和 3 的糖果,然后免费获得价格为 1 的糖果。
总开销为 2 + 3 = 5 。这是开销最小的 唯一 方案。
注意,我们不能购买价格为 1 和 3 的糖果,并免费获得价格为 2 的糖果。
这是因为免费糖果的价格必须小于等于购买的 2 个糖果价格的较小值。

输入:cost = [6,5,7,9,2,2]
输出:23
解释:最小总开销购买糖果方案为:
- 购买价格为 9 和 7 的糖果
- 免费获得价格为 6 的糖果
- 购买价格为 5 和 2 的糖果
- 免费获得价格为 2 的最后一个糖果
因此,最小总开销为 9 + 7 + 5 + 2 = 23 。

输入:cost = [5,5]
输出:10
解释:由于只有 2 个糖果,我们需要将它们都购买,而且没有免费糖果。
所以总最小开销为 5 + 5 = 10 。
func minimumCost(cost []int) int {
    sort.Sort(sort.Reverse(sort.IntSlice(cost)))
    res := 0
    for i := range cost {
        if i%3 != 2 {
            res += cost[i]
        }
    }
    return res
}

2098. 长度为 K 的最大偶数和子序列(会员/中等/贪心)

给你一个整数数组 nums 和一个整数 k 。找出 nums 长度为 k 的所有子序列中的 最大偶数和 。
返回此总和,如果此总和不存在,则返回 -1。
子序列 是一个数组,可以通过删除一些元素或不删除任何元素而从另一个数组派生,而不改变其余元素的顺序。

输入: nums = [4,1,5,3,1], k = 3
输出: 12
解释:
具有最大可能偶数和的子序列是[4,5,3]。它的和为 4 + 5 + 3 = 12
/*
思路:
1、先降序排序,计算前K个数的和,如果是偶数,直接返回结果。如果是奇数,往下。
2、求前k个数中, 最小的奇数和偶数
3、求后n-k个数中最大的奇数和偶数
4、前k个数和为奇数,故减去其中最小的奇/偶数,加上后n-k个数中最大的偶/奇数,两个值返回最大
*/
func largestEvenSum(nums []int, k int) int64 {

    sort.Sort(sort.Reverse(sort.IntSlice(nums)))

    tempSum := 0
    for i := 0; i < k; i++ {
        tempSum += nums[i]
    }

    if tempSum%2 == 0 {
        return int64(tempSum)
    }

    minOdd := math.MaxInt32
    minEven := math.MaxInt32

    for i := 0; i < k; i++ {
        if nums[i]%2 != 0 && nums[i] < minOdd { //奇数
            minOdd = nums[i]
        }

        if nums[i]%2 == 0 && nums[i] < minEven {
            minEven = nums[i]
        }
    }

    maxOdd := math.MinInt32
    maxEven := math.MinInt32

    for i := k; i < len(nums); i++ {
        if nums[i]%2 != 0 && nums[i] > maxOdd { //奇数
            maxOdd = nums[i]
        }

        if nums[i]%2 == 0 && nums[i] > maxEven {
            maxEven = nums[i]
        }
    }

    // 前k个数和为奇数,故减去其中最小的奇/偶数,加上后n-k个数中最大的偶/奇数,两个值返回最大,这两个值也可能不存在
    ans1 := -1
    ans0 := -1
    if minOdd != math.MaxInt32 && maxEven != math.MinInt32 {
        ans1 = tempSum + maxEven - minOdd
    }
    if minEven != math.MaxInt32 && maxOdd != math.MinInt32 {
        ans0 = tempSum + maxOdd - minEven
    }
    return max(ans0, ans1)  // 不存在就返回-1
}

624. 数组列表中的最大距离(会员/中等/贪心)

给定 m 个数组,每个数组都已经按照升序排好序了。现在你需要从两个不同的数组中选择两个整数(每个数组选一个)并且计算它们的距离。两个整数 a 和 b 之间的距离定义为它们差的绝对值 |a-b| 。你的任务就是去找到最大距离

输入: 
[[1,2,3],
 [4,5],
 [1,2,3]]
输出: 4
解释:
一种得到答案 4 的方法是从第一个数组或者第三个数组中选择 1,同时从第二个数组中选择 5 。
 

1686. 石子游戏 VI(中等)

484. 寻找排列(会员/中等)

由范围 [1,n] 内所有整数组成的 n 个整数的排列 perm 可以表示为长度为 n - 1 的字符串 s ,其中:

如果 perm[i] < perm[i + 1] ,那么 s[i] == ' i '
如果 perm[i] > perm[i + 1] ,那么 s[i] == 'D' 。
给定一个字符串 s ,重构字典序上最小的排列 perm 并返回它。

输入: s = "I"
输出: [1,2]
解释: [1,2] 是唯一合法的可以生成秘密签名 "I" 的特定串,数字 1 和 2 构成递增关系。

输入: s = "DI"
输出: [2,1,3]
解释: [2,1,3] 和 [3,1,2] 可以生成秘密签名 "DI",
但是由于我们要找字典序最小的排列,因此你需要输出 [2,1,3]。

你可能感兴趣的:(四、leetcode刷题之【贪心算法】)