随想录日记part28
t i m e : time: time: 2024.03.26
主要内容:今天深入学习贪心算法,接下来是针对题目的讲解:1.柠檬水找零 ;2. 根据身高重建队列;3.用最少数量的箭引爆气球
- 860.柠檬水找零
- 406.根据身高重建队列
- 452. 用最少数量的箭引爆气球
Topic1柠檬水找零
题目:
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
给你一个整数数组 b i l l s bills bills ,其中 b i l l s [ i ] bills[i] bills[i] 是第 i i i 位顾客付的账。如果你能给每位顾客正确找零,返回 t r u e true true ,否则返回 f a l s e false false 。
输入:
b
i
l
l
s
=
[
5
,
5
,
5
,
10
,
20
]
bills = [5,5,5,10,20]
bills=[5,5,5,10,20]
输出:
t
r
u
e
true
true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。
思路:
只需要维护三种金额的数量,5,10和20。
有如下三种情况:
情况一:账单是5,直接收下。
情况二:账单是10,消耗一个5,增加一个10
情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5
代码如下:
class Solution {
public boolean lemonadeChange(int[] bills) {
int num5 = 0;
int num10 = 0;
int num20 = 0;
for (int i = 0; i < bills.length; i++) {
// 情况1:收到一个5元
if (bills[i] == 5) {
num5++;
}
// 情况2:收到一个10元
if (bills[i] == 10) {
if (num5 > 0) {
num5--;
num10++;
} else {
return false;
}
}
// 情况3:收到一个20元
if (bills[i] == 20) {
if (num10 > 0 && num5 > 0) {
num5--;
num10--;
num20++;
} else if (num5 > 2) {
num5 = num5 - 3;
num20++;
} else {
return false;
}
}
}
return true;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
1
)
O(1)
O(1)
Topic2根据身高重建队列
题目:
假设有打乱顺序的一群人站成一个队列,数组 p e o p l e people people 表示队列中一些人的属性(不一定按顺序)。每个 p e o p l e [ i ] = [ h i , k i ] people[i] = [hi, ki] people[i]=[hi,ki] 表示第 i i i 个人的身高为 h i hi hi ,前面正好有 k i ki ki 个身高大于或等于 h i hi hi 的人。
请你重新构造并返回输入数组 p e o p l e people people 所表示的队列。返回的队列应该格式化为数组 q u e u e queue queue ,其中 q u e u e [ j ] = [ h j , k j ] queue[j] = [hj, kj] queue[j]=[hj,kj] 是队列中第 j j j 个人的属性( q u e u e [ 0 ] queue[0] queue[0] 是排在队列前面的人)。
输入:
p
e
o
p
l
e
=
[
[
7
,
0
]
,
[
4
,
4
]
,
[
7
,
1
]
,
[
5
,
0
]
,
[
6
,
1
]
,
[
5
,
2
]
]
people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
people=[[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]]
输出:
[
[
5
,
0
]
,
[
7
,
0
]
,
[
5
,
2
]
,
[
6
,
1
]
,
[
4
,
4
]
,
[
7
,
1
]
]
[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
解释:
解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
思路:
在按照身高从大到小排序后:
局部最优:优先按身高高的 p e o p l e people people 的 k k k 来插入。插入操作过后的 p e o p l e people people 满足队列属性
全局最优:最后都做完插入操作,整个队列满足题目队列属性,局部最优可推出全局最优
回归本题,整个插入过程如下:
排序完的 p e o p l e : [ [ 7 , 0 ] , [ 7 , 1 ] , [ 6 , 1 ] , [ 5 , 0 ] , [ 5 , 2 ] , [ 4 , 4 ] ] people: [[7,0], [7,1], [6,1], [5,0], [5,2],[4,4]] people:[[7,0],[7,1],[6,1],[5,0],[5,2],[4,4]]
插入的过程:
- 插入 [ 7 , 0 ] : [ [ 7 , 0 ] ] [7,0]:[[7,0]] [7,0]:[[7,0]]
- 插入 [ 7 , 1 ] : [ [ 7 , 0 ] , [ 7 , 1 ] ] [7,1]:[[7,0],[7,1]] [7,1]:[[7,0],[7,1]]
- 插入 [ 6 , 1 ] : [ [ 7 , 0 ] , [ 6 , 1 ] , [ 7 , 1 ] ] [6,1]:[[7,0],[6,1],[7,1]] [6,1]:[[7,0],[6,1],[7,1]]
- 插入 [ 5 , 0 ] : [ [ 5 , 0 ] , [ 7 , 0 ] , [ 6 , 1 ] , [ 7 , 1 ] ] [5,0]:[[5,0],[7,0],[6,1],[7,1]] [5,0]:[[5,0],[7,0],[6,1],[7,1]]
- 插入 [ 5 , 2 ] : [ [ 5 , 0 ] , [ 7 , 0 ] , [ 5 , 2 ] , [ 6 , 1 ] , [ 7 , 1 ] ] [5,2]:[[5,0],[7,0],[5,2],[6,1],[7,1]] [5,2]:[[5,0],[7,0],[5,2],[6,1],[7,1]]
- 插入 [ 4 , 4 ] : [ [ 5 , 0 ] , [ 7 , 0 ] , [ 5 , 2 ] , [ 6 , 1 ] , [ 4 , 4 ] , [ 7 , 1 ] ] [4,4]:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] [4,4]:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]
整体代码如下:
class Solution {
public int[][] reconstructQueue(int[][] people) {
// 先对原先的数组排序
Arrays.sort(people, (a, b) -> {
if (a[0] == b[0])
return a[1] - b[1];
else
return b[0] - a[0];
});
LinkedList<int[]> queue = new LinkedList<>();
for (int[] p : people) {
queue.add(p[1], p);
}
return queue.toArray(new int[people.length][]);
}
}
时间复杂度:
O
(
n
l
o
g
n
+
n
2
)
O(nlog n + n^2)
O(nlogn+n2)
空间复杂度:
O
(
n
)
O(n)
O(n)
Topic3用最少数量的箭引爆气球
题目:
有一些球形气球贴在一堵用
X
Y
XY
XY 平面表示的墙面上。墙面上的气球记录在整数数组
p
o
i
n
t
s
points
points ,其中
p
o
i
n
t
s
[
i
]
=
[
x
s
t
a
r
t
,
x
e
n
d
]
points[i] = [xstart, xend]
points[i]=[xstart,xend] 表示水平直径在
x
s
t
a
r
t
xstart
xstart 和
x
e
n
d
xend
xend 之间的气球。你不知道气球的确切
y
y
y 坐标。一支弓箭可以沿着
x
x
x 轴从不同点完全垂直地射出。在坐标
x
x
x 处射出一支箭,若有一个气球的直径的开始和结束坐标为
x
s
t
a
r
t
,
x
e
n
d
xstart,xend
xstart,xend, 且满足
x
s
t
a
r
t
≤
x
≤
x
e
n
d
xstart ≤ x ≤ xend
xstart≤x≤xend,则该气球会被引爆 。可以射出的弓箭的数量没有限制 。 弓箭一旦被射出之后,可以无限地前进。给你一个数组
p
o
i
n
t
s
points
points ,返回引爆所有气球所必须射出的最小弓箭数 。
输入:
p
o
i
n
t
s
=
[
[
10
,
16
]
,
[
2
,
8
]
,
[
1
,
6
]
,
[
7
,
12
]
]
points = [[10,16],[2,8],[1,6],[7,12]]
points=[[10,16],[2,8],[1,6],[7,12]]
输出:
2
2
2
解释:
气球可以用2支箭来爆破:
- 在x = 6处射出箭,击破气球[2,8]和[1,6]。
- 在x = 11处发射箭,击破气球[10,16]和[7,12]。
思路:
为了让气球尽可能的重叠,需要对数组进行排序。
如果气球重叠了,重叠气球中右边边界的最小值 之前的区间一定需要一个弓箭。
以题目示例: [[10,16],[2,8],[1,6],[7,12]]为例,如图:(方便起见,已经排序)
代码实现如下:
class Solution {
public int findMinArrowShots(int[][] points) {
if (points.length == 0)
return 0;
Arrays.sort(points, (a, b) -> Integer.compare(a[0], b[0]));
int result = 1;
for (int i = 1; i < points.length; i++) {
if (points[i][0] > points[i - 1][1])
result++;
else {
points[i][1] = Math.min(points[i][1], points[i - 1][1]);
}
}
return result;
}
}
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),因为有一个快排
空间复杂度:
O
(
1
)
O(1)
O(1),有一个快排,最差情况(倒序)时,需要
n
n
n 次递归调用。因此确实需要
O
(
n
)
O(n)
O(n) 的栈空间