Merge pull request #1820 from juguagua/leetcode-modify-the-code-of-the-greedy

更新贪心部分:至 “用最少数量的箭引爆气球”
This commit is contained in:
程序员Carl
2022-12-17 11:41:53 +08:00
committed by GitHub
6 changed files with 94 additions and 138 deletions

View File

@ -36,7 +36,7 @@
贪心的思路,局部最优:当前可移动距离尽可能多走,如果还没到终点,步数再加一。整体最优:一步尽可能多走,从而达到最小步数。 贪心的思路,局部最优:当前可移动距离尽可能多走,如果还没到终点,步数再加一。整体最优:一步尽可能多走,从而达到最小步数。
思路虽然是这样,但在写代码的时候还不能真的能跳多远远,那样就不知道下一步最远能跳到哪里了。 思路虽然是这样,但在写代码的时候还不能真的能跳多远就跳多远,那样就不知道下一步最远能跳到哪里了。
**所以真正解题的时候,要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最小步数!** **所以真正解题的时候,要从覆盖范围出发,不管怎么跳,覆盖范围内一定是可以跳到的,以最小的步数增加覆盖范围,覆盖范围一旦覆盖了终点,得到的就是最小步数!**
@ -234,31 +234,6 @@ class Solution:
### Go ### Go
```Go
func jump(nums []int) int {
dp := make([]int, len(nums))
dp[0] = 0//初始第一格跳跃数一定为0
for i := 1; i < len(nums); i++ {
dp[i] = i
for j := 0; j < i; j++ {
if nums[j] + j >= i {//nums[j]为起点,j为往右跳的覆盖范围,这行表示从j能跳到i
dp[i] = min(dp[j] + 1, dp[i])//更新最小能到i的跳跃次数
}
}
}
return dp[len(nums)-1]
}
func min(a, b int) int {
if a < b {
return a
} else {
return b
}
}
```
```go ```go
// 贪心版本一 // 贪心版本一
func jump(nums []int) int { func jump(nums []int) int {
@ -320,8 +295,6 @@ func max(a, b int) int {
} }
``` ```
### Javascript ### Javascript
```Javascript ```Javascript

View File

@ -176,13 +176,13 @@ public:
* 时间复杂度:$O(n)$ * 时间复杂度:$O(n)$
* 空间复杂度:$O(1)$ * 空间复杂度:$O(1)$
**说这种解法为贪心算法,才是有理有据的,因为全局最优解是根据局部最优推导出来的** **说这种解法为贪心算法,才是有理有据的,因为全局最优解是根据局部最优推导出来的**
## 总结 ## 总结
对于本题首先给出了暴力解法暴力解法模拟跑一圈的过程其实比较考验代码技巧的要对while使用的很熟练。 对于本题首先给出了暴力解法暴力解法模拟跑一圈的过程其实比较考验代码技巧的要对while使用的很熟练。
然后给出了两种贪心算法,对于第一种贪心方法,其实我认为就是一种直接从全局选取最优的模拟操作,思路还是巧妙的,值得学习一下。 然后给出了两种贪心算法,对于第一种贪心方法,其实我认为就是一种直接从全局选取最优的模拟操作,思路还是巧妙的,值得学习一下。
对于第二种贪心方法,才真正体现出贪心的精髓,用局部最优可以推出全局最优,进而求得起始位置。 对于第二种贪心方法,才真正体现出贪心的精髓,用局部最优可以推出全局最优,进而求得起始位置。

View File

@ -65,7 +65,7 @@ for (int i = 1; i < ratings.size(); i++) {
如果 ratings[i] > ratings[i + 1]此时candyVec[i]第i个小孩的糖果数量就有两个选择了一个是candyVec[i + 1] + 1从右边这个加1得到的糖果数量一个是candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。 如果 ratings[i] > ratings[i + 1]此时candyVec[i]第i个小孩的糖果数量就有两个选择了一个是candyVec[i + 1] + 1从右边这个加1得到的糖果数量一个是candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。
那么又要贪心了局部最优取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量保证第i个小孩的糖果数量大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。 那么又要贪心了局部最优取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量保证第i个小孩的糖果数量大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。
局部最优可以推出全局最优。 局部最优可以推出全局最优。
@ -172,63 +172,44 @@ class Solution:
``` ```
### Go ### Go
```golang ```go
func candy(ratings []int) int { func candy(ratings []int) int {
/**先确定一边,再确定另外一边 /**先确定一边,再确定另外一边
1.先从左到右当右边的大于左边的就加1 1.先从左到右当右边的大于左边的就加1
2.再从右到左当左边的大于右边的就再加1 2.再从右到左当左边的大于右边的就再加1
**/ **/
need:=make([]int,len(ratings)) need := make([]int, len(ratings))
sum:=0 sum := 0
//初始化(每个人至少一个糖果) // 初始化(每个人至少一个糖果)
for i:=0;i<len(ratings);i++{ for i := 0; i < len(ratings); i++ {
need[i]=1 need[i] = 1
} }
//1.先从左到右当右边的大于左边的就加1 // 1.先从左到右当右边的大于左边的就加1
for i:=0;i<len(ratings)-1;i++{ for i := 0; i < len(ratings) - 1; i++ {
if ratings[i]<ratings[i+1]{ if ratings[i] < ratings[i+1] {
need[i+1]=need[i]+1 need[i+1] = need[i] + 1
} }
} }
//2.再从右到左当左边的大于右边的就右边加1但要花费糖果最少所以需要做下判断 // 2.再从右到左当左边的大于右边的就右边加1但要花费糖果最少所以需要做下判断
for i:=len(ratings)-1;i>0;i--{ for i := len(ratings)-1; i > 0; i-- {
if ratings[i-1]>ratings[i]{ if ratings[i-1] > ratings[i] {
need[i-1]=findMax(need[i-1],need[i]+1) need[i-1] = findMax(need[i-1], need[i]+1)
} }
} }
//计算总共糖果 //计算总共糖果
for i:=0;i<len(ratings);i++{ for i := 0; i < len(ratings); i++ {
sum+=need[i] sum += need[i]
} }
return sum return sum
} }
func findMax(num1 int ,num2 int) int{ func findMax(num1 int, num2 int) int {
if num1>num2{ if num1 > num2 {
return num1 return num1
} }
return num2 return num2
} }
``` ```
### Rust
```rust
pub fn candy(ratings: Vec<i32>) -> i32 {
let mut candies = vec![1i32; ratings.len()];
for i in 1..ratings.len() {
if ratings[i - 1] < ratings[i] {
candies[i] = candies[i - 1] + 1;
}
}
for i in (0..ratings.len()-1).rev() {
if ratings[i] > ratings[i + 1] {
candies[i] = candies[i].max(candies[i + 1] + 1);
}
}
candies.iter().sum()
}
```
### Javascript: ### Javascript:
```Javascript ```Javascript
var candy = function(ratings) { var candy = function(ratings) {
@ -255,6 +236,25 @@ var candy = function(ratings) {
``` ```
### Rust
```rust
pub fn candy(ratings: Vec<i32>) -> i32 {
let mut candies = vec![1i32; ratings.len()];
for i in 1..ratings.len() {
if ratings[i - 1] < ratings[i] {
candies[i] = candies[i - 1] + 1;
}
}
for i in (0..ratings.len()-1).rev() {
if ratings[i] > ratings[i + 1] {
candies[i] = candies[i].max(candies[i + 1] + 1);
}
}
candies.iter().sum()
}
```
### C ### C
```c ```c
#define max(a, b) (((a) > (b)) ? (a) : (b)) #define max(a, b) (((a) > (b)) ? (a) : (b))

View File

@ -39,7 +39,7 @@
## 思路 ## 思路
本题有两个维度h和k看到这种题目一定要想如何确定一个维度然后按照另一个维度重新排列 本题有两个维度h和k看到这种题目一定要想如何确定一个维度然后按照另一个维度重新排列
其实如果大家认真做了[135. 分发糖果](https://programmercarl.com/0135.分发糖果.html)就会发现和此题有点点的像 其实如果大家认真做了[135. 分发糖果](https://programmercarl.com/0135.分发糖果.html)就会发现和此题有点点的像
@ -47,7 +47,7 @@
**如果两个维度一起考虑一定会顾此失彼** **如果两个维度一起考虑一定会顾此失彼**
对于本题相信大家困惑的点是先确定k还是先确定h呢也就是究竟先按h排序呢还先按照k排序呢 对于本题相信大家困惑的点是先确定k还是先确定h呢也就是究竟先按h排序呢先按照k排序呢
如果按照k来从小到大排序排完之后会发现k的排列并不符合条件身高也不符合条件两个维度哪一个都没确定下来 如果按照k来从小到大排序排完之后会发现k的排列并不符合条件身高也不符合条件两个维度哪一个都没确定下来
@ -222,48 +222,46 @@ class Solution:
### Go ### Go
```go ```go
func reconstructQueue(people [][]int) [][]int { func reconstructQueue(people [][]int) [][]int {
//先将身高从大到小排序,确定最大个子的相对位置 // 先将身高从大到小排序,确定最大个子的相对位置
sort.Slice(people,func(i,j int)bool{ sort.Slice(people, func(i, j int) bool {
if people[i][0]==people[j][0]{ if people[i][0] == people[j][0] {
return people[i][1]<people[j][1]//这个才是当身高相同时将K按照从小到大排序 return people[i][1] < people[j][1] // 当身高相同时将K按照从小到大排序
} }
return people[i][0]>people[j][0]//这个只是确保身高按照由大到小的顺序来排并不确定K是按照从小到大排序的 return people[i][0] > people[j][0] // 身高按照由大到小的顺序来排
}) })
//再按照K进行插入排序优先插入K小的
result := make([][]int, 0) // 再按照K进行插入排序优先插入K小的
for _, info := range people { for i, p := range people {
result = append(result, info) copy(people[p[1]+1 : i+1], people[p[1] : i+1]) // 空出一个位置
copy(result[info[1] +1:], result[info[1]:])//将插入位置之后的元素后移动一位(意思是腾出空间) people[p[1]] = p
result[info[1]] = info//将插入元素位置插入元素
} }
return result return people
} }
``` ```
```go ```go
//链表 // 链表实现
func reconstructQueue(people [][]int) [][]int { func reconstructQueue(people [][]int) [][]int {
sort.Slice(people,func (i,j int) bool { sort.Slice(people,func (i,j int) bool {
if people[i][0]==people[j][0]{ if people[i][0] == people[j][0] {
return people[i][1]<people[j][1]//当身高相同时将K按照从小到大排序 return people[i][1] < people[j][1] //当身高相同时将K按照从小到大排序
} }
//先将身高从大到小排序,确定最大个子的相对位置 return people[i][0] > people[j][0]
return people[i][0]>people[j][0]
}) })
l:=list.New()//创建链表 l := list.New() //创建链表
for i:=0;i<len(people);i++{ for i:=0; i < len(people); i++ {
position:=people[i][1] position := people[i][1]
mark:=l.PushBack(people[i])//插入元素 mark := l.PushBack(people[i]) //插入元素
e:=l.Front() e := l.Front()
for position!=0{//获取相对位置 for position != 0 { //获取相对位置
position-- position--
e=e.Next() e = e.Next()
} }
l.MoveBefore(mark,e)//移动位置 l.MoveBefore(mark, e) //移动位置
} }
res:=[][]int{} res := [][]int{}
for e:=l.Front();e!=nil;e=e.Next(){ for e := l.Front(); e != nil; e = e.Next() {
res=append(res,e.Value.([]int)) res = append(res, e.Value.([]int))
} }
return res return res
} }

View File

@ -56,7 +56,7 @@
如果真实的模拟射气球的过程应该射一个气球数组就remove一个元素这样最直观毕竟气球被射了 如果真实的模拟射气球的过程应该射一个气球数组就remove一个元素这样最直观毕竟气球被射了
但仔细思考一下就发现如果把气球排序之后从前到后遍历气球被射过的气球仅仅跳过就行了没有必要让气球数组remote气球只要记录一下箭的数量就可以了 但仔细思考一下就发现如果把气球排序之后从前到后遍历气球被射过的气球仅仅跳过就行了没有必要让气球数组remove气球只要记录一下箭的数量就可以了
以上为思考过程已经确定下来使用贪心了那么开始解题 以上为思考过程已经确定下来使用贪心了那么开始解题
@ -175,25 +175,25 @@ class Solution:
``` ```
### Go ### Go
```golang ```go
func findMinArrowShots(points [][]int) int { func findMinArrowShots(points [][]int) int {
var res int =1//弓箭数 var res int = 1 //弓箭数
//先按照第一位排序 //先按照第一位排序
sort.Slice(points,func (i,j int) bool{ sort.Slice(points, func (i,j int) bool {
return points[i][0]<points[j][0] return points[i][0] < points[j][0]
}) })
for i:=1;i<len(points);i++{ for i := 1; i < len(points); i++ {
if points[i-1][1]<points[i][0]{//如果前一位的右边界小于后一位的左边界,则一定不重合 if points[i-1][1] < points[i][0] { //如果前一位的右边界小于后一位的左边界,则一定不重合
res++ res++
}else{ } else {
points[i][1] = min(points[i - 1][1], points[i][1]); // 更新重叠气球最小右边界,覆盖该位置的值,留到下一步使用 points[i][1] = min(points[i - 1][1], points[i][1]); // 更新重叠气球最小右边界,覆盖该位置的值,留到下一步使用
} }
} }
return res return res
} }
func min(a,b int) int{ func min(a, b int) int {
if a>b{ if a > b {
return b return b
} }
return a return a

View File

@ -54,7 +54,7 @@
这是前几天的leetcode每日一题感觉不错给大家讲一下。 这是前几天的leetcode每日一题感觉不错给大家讲一下。
这道题目刚一看,可能会有点懵,这要怎么找零才能保证完全部账单的找零呢? 这道题目刚一看,可能会有点懵,这要怎么找零才能保证完全部账单的找零呢?
**但仔细一琢磨就会发现,可供我们做判断的空间非常少!** **但仔细一琢磨就会发现,可供我们做判断的空间非常少!**
@ -179,38 +179,23 @@ class Solution:
### Go ### Go
```golang ```go
func lemonadeChange(bills []int) bool { func lemonadeChange(bills []int) bool {
//left表示还剩多少 下标0位5元的个数 下标1为10元的个数 ten, five := 0, 0
left:=[2]int{0,0} for i := 0; i < len(bills); i++ {
//第一个元素不为5直接退出 if bills[i] == 5 {
if bills[0]!=5{ five++
return false } else if bills[i] == 10 {
} if five == 0 {
for i:=0;i<len(bills);i++{
//先统计5元和10元的个数
if bills[i]==5{
left[0]+=1
}
if bills[i]==10{
left[1]+=1
}
//接着处理找零的
tmp:=bills[i]-5
if tmp==5{
if left[0]>0{
left[0]-=1
}else {
return false return false
} }
} ten++; five--
if tmp==15{ } else {
if left[1]>0&&left[0]>0{ if ten >= 1 && five >= 1 {
left[0]-=1 ten--; five--
left[1]-=1 } else if five >= 3 {
}else if left[1]==0&&left[0]>2{ five -= 3
left[0]-=3 } else {
}else{
return false return false
} }
} }