文章目录
1. 贪心算法概述
贪心算法(又称贪婪算法)是指,通过局部最优策略以达成全局最优解。
贪心算法思路:
2. 分配问题
2.1 LeetCode-No.455-Easy
解题思路:
总体目的: 使尽可能多的孩子吃到饼干。
局部子问题: 在吃饱的前提下,每个孩子只吃一个尺寸最小的饼干。
总体流程: 当前孩子中胃口最小的孩子应优先吃饼干。将孩子们按胃口由小到大的顺序排序,进行一次遍历(吃饼干)。
算法终止条件: 遍历完成后算法即终止,此时每个孩子都吃到了饼干或者饼干全部被吃完。
2.2 LeetCode-No.135-Hard
解题思路:
总体目的: 使所有孩子满足如下公式:
c
a
n
d
y
[
i
]
=
m
a
x
(
c
a
n
d
y
[
i
−
1
]
,
c
a
n
d
y
[
i
+
1
]
)
+
1
,
i
f
r
a
t
i
n
g
s
[
i
]
>
r
a
t
i
n
g
s
[
i
−
1
]
a
n
d
r
a
t
i
n
g
s
[
i
]
>
[
i
−
1
]
(2.2.1)
candy[i]=max(candy[i-1],candy[i+1])+1, \\if\ ratings[i] >ratings[i-1]\ and\ ratings[i] >[i-1]\tag{2.2.1}
candy[i]=max(candy[i−1],candy[i+1])+1,if ratings[i]>ratings[i−1] and ratings[i]>[i−1](2.2.1)
其中
c
a
n
d
y
candy
candy数组中的元素均初始化为1,求
s
u
m
(
c
a
n
d
y
)
sum(candy)
sum(candy)。
局部子问题: 根据上述公式,可以分解如下为两个子问题:
子问题1:对于每一个孩子,应满足:
c
a
n
d
y
[
i
]
−
c
a
n
d
y
[
i
−
1
]
≥
1
,
i
f
r
a
t
i
n
g
s
[
i
]
>
r
a
t
i
n
g
s
[
i
−
1
]
(2.2.2)
candy[i]-candy[i-1]\ge1, \\if\ ratings[i] >ratings[i-1]\tag{2.2.2}
candy[i]−candy[i−1]≥1,if ratings[i]>ratings[i−1](2.2.2)
子问题2:对于每一个孩子,应满足:
c
a
n
d
y
[
i
]
−
c
a
n
d
y
[
i
+
1
]
≥
1
,
i
f
r
a
t
i
n
g
s
[
i
]
>
r
a
t
i
n
g
s
[
i
−
1
]
(2.2.3)
candy[i]-candy[i+1]\ge1, \\if\ ratings[i] >ratings[i-1]\tag{2.2.3}
candy[i]−candy[i+1]≥1,if ratings[i]>ratings[i−1](2.2.3)
总体流程: 两次遍历,每次遍历各解决一个子问题。
遍历1:从前向后遍历,解决子问题1。
遍历2:从后向前遍历,解决子问题2。
遍历时,首端和尾端的孩子需特殊处理。
两次遍历,使得每一个孩子与其相邻的两个孩子各进行了一次比较,比较完成后均满足公式2.2.1。
算法终止条件: 两次遍历完成后算法即终止,此时求出
s
u
m
(
c
a
n
d
y
)
sum(candy)
sum(candy)即为输出的结果。
2.3 LeetCode-No.605-Easy
解题思路:
总体目的: 在花坛中种植尽可能多的花,理论上,一个空花坛中能种植的最大数量如下:
⌈
f
l
o
w
e
r
b
e
d
.
l
e
n
g
t
h
/
2
⌉
(2.3.1)
\lceil flowerbed.length/2\rceil\tag{2.3.1}
⌈flowerbed.length/2⌉(2.3.1)
局部子问题: 若花坛未种花,且其相邻的花坛均为种花,则将该花坛种植上花,否则跳过。
总体流程: 从前向后对所有花坛进行一次遍历。
算法终止条件: 遍历完成后算法即终止,此时可知该花坛中还能种植的最大数量。
2.4 LeetCode-No.122-Easy
解题思路:
总体目的: 使获得的利润最大。
局部子问题: 若前一天的股票价格低于今天,则应当完成一次买卖交易(前一天买入,今天卖出)。由于不限制交易次数,可以在某天卖出后再买入,因此这种策略不会影响最终结果。例:三天的股票价格为
[
1
,
2
,
5
]
[1,2,5]
[1,2,5],则在第一天买入->第二天卖出->第二天再买入->第三天卖出的利润和在第一天买入->第三天卖出的利润是相同的:
(
2
−
1
)
+
(
5
−
2
)
=
5
−
1
=
4
(2-1)+(5-2)=5-1=4
(2−1)+(5−2)=5−1=4。
总体流程: 是否能进行一次买卖交易,需要比较当天和前一天的价格,则应从第二天开始对股票价格进行一次遍历。
算法终止条件: 遍历完成后算法即终止。
2.5 LeetCode-No.406-Medium
解题思路:
总体目的: 按要求重建队列。
局部子问题: 插入新的人员时,若此人前面有
k
k
k个人比他高,则重建的索引位置之前必须预留
k
k
k个位置给比他高的人。
总体流程: 优先插入身高较高的人,则插入一个新的人员时,由于比他高的人员都已经插入到队列中,因此只需要将该人员插入到索引位置
k
k
k处,后面人员的位置相应往后顺延即可。将输入数组
p
e
o
p
l
e
people
people按身高由大到小、
k
k
k值由小到大的顺序排序,进行一次遍历,对于Java可采用List容器,遍历时直接将当前人员插入到容器的索引位置
k
k
k处,后面人员的位置会自动往后顺延。
算法终止条件: 遍历完成后算法即终止,将容器转为二维数组作为输出。
2.6 LeetCode-No.665-Medium
解题思路:
总体目的: 使输入数组变为非递减数组的改变次数尽可能少。
局部子问题: 若在索引位置
i
i
i处出现
n
u
m
s
[
i
−
1
]
>
n
u
m
s
[
i
]
nums[i-1]>nums[i]
nums[i−1]>nums[i]的情况说明需要改变一次元素,改变的元素可以是
n
u
m
s
[
i
−
1
]
nums[i-1]
nums[i−1]或
n
u
m
s
[
i
]
nums[i]
nums[i],由于数组是非递减的,改动的思路是尽量将
n
u
m
s
[
i
−
1
]
nums[i-1]
nums[i−1]改为较小的值,考虑到各种情况,元素的改动公式如下:
{
n
u
m
s
[
i
−
1
]
=
n
u
m
s
[
i
]
nums[i-2]不存在
n
u
m
s
[
i
−
1
]
=
n
u
m
s
[
i
−
2
]
n
u
m
s
[
i
−
2
]
≤
n
u
m
s
[
i
]
n
u
m
s
[
i
]
=
n
u
m
s
[
i
−
1
]
n
u
m
s
[
i
−
2
]
>
n
u
m
s
[
i
]
(2.6.1)
\begin{cases} nums[i-1]=nums[i]& \text{nums[i-2]不存在}\\ nums[i-1]=nums[i-2]& nums[i-2]\le nums[i]\\ nums[i]=nums[i-1]& nums[i-2]>nums[i] \end{cases}\tag{2.6.1}
⎩⎪⎨⎪⎧nums[i−1]=nums[i]nums[i−1]=nums[i−2]nums[i]=nums[i−1]nums[i-2]不存在nums[i−2]≤nums[i]nums[i−2]>nums[i](2.6.1)
总体流程: 从第二个元素开始对输入数组进行一次遍历,每当需要改变时,需改变一个数组元素后才能继续遍历,因为改变后的数组元素将会影响到后续数组元素的判断。
算法终止条件: 遍历完成或已经确定改变次数大于1时算法即终止,另外,如果数组长度为1,则该数组天然就是非递减数组,不需要改变,不会进行遍历。
3. 区间问题
3.1 LeetCode-No.435-Medium
解题思路:
总体目的: 尽量多保留不重叠的区域,使需要被移除的区间最少。
局部子问题: 若上一个区间与当前区间不相交,则保留当前区间,否则移除当前区间。
总体流程: 优先保留结尾较小的区间,因为结尾越小的区间留给其他区间的余地越大。将所有区间按结尾由小到大的顺序排序,进行一次遍历。其中,排序后第一个区间的结尾最小,必定保留。
算法终止条件: 遍历完成后算法即终止,此时可知被移除的区间数量。
3.2 LeetCode-No.452-Medium
解题思路:
总体目的: 若两个区间相交,则定义它们属于同一个区间块。本题的目的是使需要的区间块数量尽可能少。
局部子问题: 区间
[
a
,
b
]
[a,b]
[a,b]与当前区间块(结尾为
x
x
x)比较,若满足:
x
≥
a
x\ge a
x≥a,说明该区间属于当前区间块,否则说明当前区间块已结束,该区间属于一个新的区间块(结尾为
b
b
b)。
总体流程: 若根据区间块的结尾判断各区间的相交关系,则所有区间需按结尾由小到大的顺序排序,以排序后第一个区间的结尾作为第一个区间块的结尾,进行一次遍历。
算法终止条件: 遍历完成后算法即终止,此时可知需要的区间块数量。
3.3 LeetCode-No.763-Medium
解题思路:
总体目的: 字符串数组
[
a
1
,
a
2
,
a
3
,
.
.
.
,
a
n
]
[a_1,a_2,a_3,...,a_n]
[a1,a2,a3,...,an]中的字母在字符串中最后出现的位置索引为
[
x
1
,
x
2
,
x
3
,
.
.
.
,
x
n
]
[x_1,x_2,x_3,...,x_n]
[x1,x2,x3,...,xn],使划分的片段尽可能多。
局部子问题: 一旦满足划分要求,则划分出一个片段。字母
a
a
a(最后出现的位置索引为
x
x
x)与当前片段(区间为
[
x
s
t
a
r
t
,
x
e
n
d
]
[x_{start},x_{end}]
[xstart,xend])比较,若
x
=
x
e
n
d
x= x_{end}
x=xend,说明字母
a
a
a已被该片段完全包含,后面没有重复的字母
a
a
a,该片段结束,否则应将
x
e
n
d
x_{end}
xend更新为
x
x
x使该片段包含当前的字母
a
a
a。
总体流程: 统计各字母在字符串中最后出现的位置索引,再对字符串数组进行一次遍历。第一个片段初始区间为
[
0
,
0
]
[0,0]
[0,0],若一个片段在索引
i
i
i处结束,则下一个片段初始区间为
[
i
+
1
,
i
]
[i+1,i]
[i+1,i]。
算法终止条件: 遍历完成后算法即终止,对于每一个片段,在遍历中可由片段最终区间
[
x
s
t
a
r
t
,
x
e
n
d
]
[x_{start},x_{end}]
[xstart,xend]得知该片段的长度为:
x
e
n
d
−
x
s
t
a
r
t
+
1
x_{end}-x_{start}+1
xend−xstart+1。