LiSheng's blog LiSheng's blog
首页
笔记
个人简历
随笔集
GitHub (opens new window)
首页
笔记
个人简历
随笔集
GitHub (opens new window)
  • golang

  • cplus

  • leetcode

    • 排序算法
    • 滑动窗口
    • 动态规划
    • 贪心算法
      • 回溯
      • 单调栈
      • 链表和树
      • 前缀和-栈-队列-堆等数据结构
      • 图论算法
    • 存储技术

    • 分布式系统

    • 计算机网络

    • Linux操作系统

    • Redis

    • 其他

    • 笔记
    • leetcode
    lisheng
    2024-09-10
    目录

    贪心算法

    # 贪心

    # 常规贪心

    # 分发饼干 (opens new window)

    假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

    • [ ] 局部最优-> 全局最优

    # 摆动序列 (opens new window)

    如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为** 摆动序列 。**第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

    • 例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
    • 相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

    子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。

    给你一个整数数组 nums ,返回 nums 中作为 **摆动序列 **的 最长子序列的长度 。

    • [x] 方法一:贪心
    • [ ] 方法二:动态规划
    func wiggleMaxLength(nums []int) int {
        res := 1
        preDiff, curDiff := 0 , 0
        for i:=0; i<len(nums)-1; i++ {
            curDiff = nums[i+1] - nums[i]
            if (preDiff >= 0 && curDiff < 0 ) || (preDiff <= 0 && curDiff > 0) {
                res++
                preDiff = curDiff
            }
        }
        return res
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    # 最大子数组和 (opens new window)

    给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。 子数组是数组中的一个连续部分。 输入:nums = [-2,1,-3,4,-1,2,1,-5,4] 输出:6 解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

    • [ ] 贪心:
    • [ ] 动态规划:
    func maxSubArray(nums []int) int {
        res := nums[0]
        for i:=1; i<len(nums);i++ {
            if nums[i-1] > 0 {
                nums[i] += nums[i-1]
            }
            res = max(res, nums[i])
        }
        return res
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    # 跳跃游戏 (opens new window)

    # 跳跃游戏 (opens new window)2(最小跳跃次数)

    给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

    • 0 <= j <= nums[i]
    • i + j < n

    返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。

    重点

    • [ ] 贪心:O(n)、O(1)
    • [ ] 动态规划:O(n*m)、O(n)
    // 贪心,O(n) O(1), 由于
    func jump(nums []int) int {
        if len(nums) == 1 {
            return 0
        }
        curDis, NextDis := 0, 0
        res := 0 
        for i:=0; i<len(nums); i++ {
            NextDis = max(NextDis, i+nums[i])  // 能到的最远的距离
            if i == curDis {                //起跳一次,
                res++                       //起跳一次,记录下
                if NextDis >= len(nums)-1 {
                    break
                }
                curDis = NextDis
            }
        }
        return res
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    // 动态规划
    func jump(nums []int) int {
        dp := make([]int, len(nums))
        dp[0] = 0
        for i:=1; i<len(nums); i++ {
            dp[i] = math.MaxInt32
        }
    
        for i:=0; i<len(nums); i++ {
            for j:=0; j<=nums[i]; j++ {
                if i+j<len(nums) {
                    dp[i+j] = min(dp[i+j], dp[i]+1)
                }
            }
        }
        return dp[len(nums)-1]
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    # K 次取反后最大化的数组和 (opens new window)

    给你一个整数数组 nums 和一个整数 k ,按以下方法修改该数组:

    • 选择某个下标 i 并将 nums[i] 替换为 -nums[i] 。

    重复这个过程恰好 k 次。可以多次选择同一个下标 i 。

    以这种方式修改数组后,返回数组 可能的最大和 。

    贪心规则:将最小的负数翻转,直到没有负数,再反转一个最小的负数(k%2==1)

    func largestSumAfterKNegations(nums []int, k int) int {
    
        sort.Slice(nums, func(i, j int) bool {
            return math.Abs(float64(nums[i])) > math.Abs(float64(nums[j]))
        })
        res := 0
        for i := 0; i < len(nums); i++ {
            if k > 0 && nums[i] < 0 {
                nums[i] = -nums[i]
                k--
            }
        }
    
        if k%2 == 1 {
            nums[len(nums)-1] = -nums[len(nums)-1]
        }
    
        for i := 0; i < len(nums); i++ {
            res += nums[i]
        }
    
        return res
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    # 加油站 (opens new window)

    在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i]_ 升。 你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] _升。你从其中的一个加油站出发,开始时油箱为空。 给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

    • [ ] 贪心1
    • [ ] 贪心2
    func canCompleteCircuit(gas []int, cost []int) int {
        rest, minGas := 0, math.MaxInt32 
        for i:=0; i<len(cost); i++ {
            rest += (gas[i] - cost[i])
            if rest < minGas {
                minGas = rest
            }
        }
        if rest < 0 {
            return -1
        }
        if minGas >= 0 {
            return 0
        }
    
        for i:=len(cost)-1; i>=0; i-- {
            minGas += (gas[i] - cost[i])
            if minGas >= 0 {            //大于0表示该次加的油能够覆盖最缺油的路程
                return i
            }
        }
        return -1
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    // 方法2,也是贪心算法
    func canCompleteCircuit(gas []int, cost []int) int {
        start, curSum, totalSum := 0, 0, 0
        for i:=0; i<len(gas); i++ {
            curSum += (gas[i] - cost[i])
            totalSum += (gas[i] - cost[i])
            if curSum < 0 {
                start = i+1     //小于零,表示此段之间均不能作为起点,即该前一段消耗远大于积累
                curSum = 0
            }
        }
        if totalSum < 0 {
            return -1
        }
        return start
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16

    # 分发糖果 (opens new window)

    n 个孩子站成一排。给你一个整数数组 ratings 表示每个孩子的评分。 你需要按照以下要求,给这些孩子分发糖果:

    • 每个孩子至少分配到 1 个糖果。
    • 相邻两个孩子评分更高的孩子会获得更多的糖果。

    请你给每个孩子分发糖果,计算并返回需要准备的 最少糖果数目 。

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

    左右两次扫描

    func candy(ratings []int) int {
        need := make([]int, len(ratings))
        for i := 0; i < len(ratings); i++ {
            need[i] = 1
        }
        for i := 1; i < len(ratings); i++ {
            if ratings[i] > ratings[i-1] {
                need[i] = need[i-1] + 1
            }
        }
    
        for i := len(ratings) - 1; i >= 1; i-- {
            if ratings[i-1] > ratings[i] {
                need[i-1] = max(need[i-1], need[i]+1)
            }
        }
    
        res := 0
        for i := 0; i < len(need); i++ {
            res += need[i]
        }
        return res
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    # 根据身高重建队列 (opens new window)

    假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。 请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

    • [ ] 贪心规则:等于或大于自己身高的会被统计到 k , 这是一个规则,那就先按照这个排好序;再根据 k 大小来调整顺序。多个规则时一个一个来
    func reconstructQueue(people [][]int) [][]int {
        sort.Slice(people, func(i, j int) bool {
            if people[i][0] == people[j][0] {
                return people[i][1] < people[j][1]
            }
            return people[i][0] > people[j][0]
        })
    
        for i, p := range people {
            copy(people[p[1]+1: i+1], people[p[1]:i+1])     //有点巧妙,将p[1]位置之后的数向后移动一位,空出p[1]的位置,来放置p
            people[p[1]] = p
        }
        return people
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    # 区间问题

    # 用最少数量的箭引爆气球 (opens new window)

    • [ ] 未解决

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

    func findMinArrowShots(points [][]int) int {
        var res int = 1  //弓箭数
        //先按照第一位排序
        sort.Slice(points, func (i,j int) bool {
            return points[i][0] < points[j][0]
        })
    
        for i := 1; i < len(points); i++ {
            if points[i-1][1] < points[i][0] {  //如果前一位的右边界小于后一位的左边界,则一定不重合
                res++
            } else {
                // 这里是关键点,寻找重叠气球最小右边界,重叠区得取最小值。
                points[i][1] = min(points[i - 1][1], points[i][1]); // 更新重叠气球最小右边界,覆盖该位置的值,留到下一步使用
            }
        }
        return res
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    # 无重叠区间 (opens new window)

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

    本质在求最大不重叠区间数

    func eraseOverlapIntervals(intervals [][]int) int {
        sort.Slice(intervals, func(i, j int) bool {
            return intervals[i][1] < intervals[j][1]
        })
    
        res := 1
        for i:=1; i<len(intervals); i++ {
            if intervals[i-1][1] <= intervals[i][0] {
                res++
            } else {
                intervals[i][1] = min(intervals[i][1], intervals[i-1][1])
            }
        }
        return len(intervals) - res
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    # 划分字母区间 (opens new window)

    给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。 注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s 。 返回一个表示每个字符串片段的长度的列表。

    • [ ] 贪心1:使用map记录每个元素最后出现的位置,第二次遍历str, 若s[i] == end,则划分一段 。
    • [ ] 贪心2:使用每个字符的出现的区间,排序,并且合并有重叠的区间。即找到所有的非重叠区间

    该方法需要额外空间来存储线段,且还要排序,空间消耗也较大

    // 贪心1
    func partitionLabels(s string) []int {
        m := map[byte]int{}
        for i:=0; i<len(s); i++ {
            m[s[i]] = i
        }
        res := []int{}
        num := 0
        end_p := m[s[0]]
    
        for i:=0; i<len(s); i++ {
            num += 1
            if m[s[i]] > end_p {
                end_p = m[s[i]]
            }
            if i == end_p {
                res = append(res, num)
                num = 0
            }
        }
        return res
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    func partitionLabels(s string) []int {
    	m := make(map[byte][2]int, 0)
    	for i := 0; i < len(s); i++ {
    		if v, ok := m[s[i]]; !ok {
    			m[s[i]] = [2]int{i, i}
    		} else {
    			m[s[i]] = [2]int{v[0], i}
    		}
    	}
    
    	vec := make([][2]int, 0)
    	for _, v := range m {
    		vec = append(vec, [2]int{v[0], v[1]})
    	}
    
    	res := []int{}
    
    	sort.Slice(vec, func(i, j int) bool {
    		if vec[i][0] == vec[j][0] {
    			return vec[i][1] < vec[j][1]
    		}
    		return vec[i][0] < vec[j][0]
    	})
    	start := 0
    	for i := 1; i < len(vec); i++ {
    		if vec[i-1][1] < vec[i][0] {
    			res = append(res, vec[i-1][1]-start+1)
    			start = vec[i][0]
    		} else {
    			vec[i] = [2]int{vec[i][0], max(vec[i][1], vec[i-1][1])}
    		}
    	}
        res = append(res, vec[len(vec)-1][1] - start + 1)
    	return res
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35

    # 合并区间 (opens new window)

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

    纯区间合并问题,较为简单

    func merge(intervals [][]int) [][]int {
        sort.Slice(intervals, func(i, j int)bool {
            return intervals[i][0] < intervals[j][0] 
        })
    
        res := [][]int{}
        start := intervals[0][0]
        for i:=1; i<len(intervals); i++ {
            if intervals[i-1][1] < intervals[i][0] {
                res = append(res, []int{start, intervals[i-1][1]})
                start = intervals[i][0]
            } else {
                intervals[i] = []int{intervals[i][0], max(intervals[i-1][1], intervals[i][1])}
            }
        }
        res = append(res, []int{start, intervals[len(intervals)-1][1]})
        return res
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    # 单调递增的数字 (opens new window)

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

    字符串转换:strconv.Itoa(n)、strconv.Atoi string不可修改,转化为切片:[]byte()

    func monotoneIncreasingDigits(n int) int {
    	s := strconv.Itoa(n)
    	ss := []byte(s)
    	if len(ss) <= 1 {
    		return n
    	}
    
    	for i := len(ss) - 1; i > 0; i-- {
    		if ss[i-1] > ss[i] {
    			ss[i-1] -= 1
                for j := i; j < len(ss); j++ { //后面的全部置为9
    			    ss[j] = '9'
    		    }
    		}
    	}
    	res, _ := strconv.Atoi(string(ss))
    	return res
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    # 监控二叉树 (opens new window)

    给定一个二叉树,我们在树的节点上安装摄像头。 节点上的每个摄影头都可以监视其父对象、自身及其直接子对象。 计算监控树的所有节点所需的最小摄像头数量。

    func minCameraCover(root *TreeNode) int {
    
    	res := 0
    	var dfs func(node *TreeNode) int
    	dfs = func(node *TreeNode) int {
    		if node == nil {
    			return 2
    		}
    		left := dfs(node.Left)
    		right := dfs(node.Right)
    
    		// # 情况1: 两字节点无需被考虑,则当前节点选择不装,并告知上层需要考虑我
    		if left == 2 && right == 2 {
    			return 0
    		}
    
    		// # 情况2: 子节点需要被影响,此时必须装
    		if left == 0 || right == 0 {
    			res++
    			return 1
    		}
    
    		// 子节点已安装,上层节点无需考虑我
    		//  # left == 1 && right == 2 左节点有摄像头,右节点有覆盖
    		//  # left == 2 && right == 1 左节点有覆盖,右节点有摄像头
    		//  # left == 1 && right == 1 左右节点都有摄像头
    		return 2
    	}
    
    	if dfs(root) == 0 {
    		res++
    	}
    	return res
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    编辑 (opens new window)
    上次更新: 2024/09/13, 11:59:12
    动态规划
    回溯

    ← 动态规划 回溯→

    最近更新
    01
    ceph分布式存储-对象存储(RGW)搭建
    10-27
    02
    ceph分布式存储-集群客户端连接
    10-27
    03
    ceph分布式存储-管理crushmap
    10-27
    更多文章>
    Theme by Vdoing
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式