1、134. 加油站
解题核心思路
-
如果整个路程中,汽油总量比总消耗量还少,则不可能绕一圈,直接返回
-1
。- 因为油不够,怎么换起点都没用。
-
如果汽油总量 >= 总消耗量,那么一定存在一个唯一的起点,可以完成整个旅程。
- 我们只需要找到这个唯一的起点。
详细步骤
Step 1: 计算总油量是否足够
- 计算 总汽油量
sum(gas)
和 总消耗量sum(cost)
。 - 如果
sum(gas) < sum(cost)
,说明总油不够,不管从哪个加油站出发都不可能绕完一圈,返回-1
。
Step 2: 寻找合适的起点
-
维护当前油量
tank
(从某个加油站出发,油量不能变负)。-
遍历每个加油站,计算 当前站点的净收益:
净收益=gas[i]−cost[i]
-
-
如果
tank
变成负数,说明从当前起点start
不能到达i+1
,我们需要从i+1
重新开始。- 这个时候,我们把
start
设为i+1
,并清空tank
重新计算。 - 原因:从
start
到i
之间的任何站点都不能作为新的起点,否则tank
还是会变成负数,根本走不下去。
- 这个时候,我们把
-
遍历完整个数组后,最终的
start
就是我们要的结果。
func canCompleteCircuit(gas []int, cost []int) int {
currentGas := 0
totalGas := 0
start := 0
for i := 0; i < len(gas); i++ {
currentGas += gas[i] - cost[i]
totalGas += gas[i] - cost[i]
if currentGas < 0 {
start = i + 1
currentGas = 0
}
}
if totalGas < 0 {
return - 1
}
return start
}
2、 135. 分发糖果
解题核心思路
1. 规则回顾
- 每个孩子至少分 1 颗糖果。
- 如果某个孩子的评分比左(右)边的孩子高,则他的糖果数也要比左(右)边的孩子多。
2. 采用贪心法,两次遍历
- 从左到右遍历: 确保右边评分更高的孩子比左边多拿。
- 从右到左遍历: 确保左边评分更高的孩子比右边多拿(同时保证最终的最少糖果数)。
详细步骤
Step 1: 先初始化所有孩子的糖果数为 1
- 每个孩子至少分 1 颗糖果,所以创建一个数组
candies
,所有元素初始化为1
。
Step 2: 从左到右遍历,处理「递增关系」
- 如果
ratings[i] > ratings[i-1]
,说明i
号孩子的评分比左边高,所以i
号孩子的糖果数比i-1
号孩子多 1: candies[i]=candies[i−1]+1candies[i] = candies[i-1] + 1candies[i]=candies[i−1]+1
Step 3: 从右到左遍历,处理「递减关系」
- 如果
ratings[i] > ratings[i+1]
,说明i
号孩子的评分比右边高,所以i
号孩子的糖果数也要比i+1
号孩子多: candies[i]=max(candies[i],candies[i+1]+1)candies[i] = \max(candies[i], candies[i+1] + 1)candies[i]=max(candies[i],candies[i+1]+1)- 要取最大值,因为
candies[i]
可能已经在左到右遍历时分配了较大的值。
- 要取最大值,因为
Step 4: 计算总糖果数
- 遍历
candies
数组,求和即可。
func candy(ratings []int) int {
if len(ratings) == 0 {
return 0
}
n := len(ratings)
candies := make([]int, len(ratings))
for i := range candies {
candies[i] = 1
}
// 从左到右遍历
// 右边大的话,加一
for i := 1; i < len(ratings); i++ {
if ratings[i] > ratings[i - 1] {
candies[i] = candies[i - 1] + 1
}
}
// 从右到左遍历
// 左边大的话,如果已经加过了
for i := n - 2; i >= 0; i-- {
if ratings[i] > ratings[i + 1] {
candies[i] = max(candies[i], candies[i + 1] + 1)
}
}
sum := 0
for _, c := range candies {
sum += c
}
return sum
}
3、 柠檬水找零
优先找回大的面值
func lemonadeChange(bills []int) bool {
count5:= 0
count10 := 0
count20 := 0
for i := 0; i < len(bills); i++ {
if bills[i] == 5{
count5++
}
if bills[i] == 10 {
count10++
if count5 > 0{
count5--
}else {
return false
}
}
if bills[i] == 20 {
count20++
if count10 > 0 && count5 > 0 {
count5--
count10--
}else if count5 >= 3{
count5 = count5 - 3
}else {
return false
}
}
}
return true
}
4、406.根据身高重建队列
关键观察
-
先考虑身高最高的人
- 他们的
k
值直接决定了他们在队列中的位置,因为没有比他们更高的人可以影响他们的k
计数。
- 他们的
-
身高相同时,k 小的人先排
- 因为
k
代表这个人前面必须有k
个不矮于他的人的数量,身高相同但k
小的人必须先安排,否则会影响后续插入。
- 因为
具体步骤
Step 1: 先排序
- 按身高
h
从高到低排序(因为高的人先确定位置,不受矮的人的影响)。 - 如果
h
相同,则按k
从小到大排序(因为k
小的人必须先插入)。
Step 2: 依次插入
- 遍历排好序的数组,将元素 按
k
值插入到队列中的第k
位置(索引从 0 开始)。 - 由于之前已经排序,当前插入的人 不会影响已经插入的人的
k
值,所以插入后队列仍是合法的。
import "sort"
func reconstructQueue(people [][]int) [][]int {
// Step 1: 排序规则
sort.Slice(people, func(i, j int) bool {
if people[i][0] == people[j][0] {
return people[i][1] < people[j][1] // k 小的排前面
}
return people[i][0] > people[j][0] // h 高的排前面
})
// Step 2: 依次插入到结果数组
result := [][]int{}
for _, p := range people {
index := p[1]
// 在 index 位置插入 p
result = append(result[:index], append([][]int{p}, result[index:]...)...)
}
return result
}