二分查找题目:制作 m 束花所需的最少天数
文章目录
- 题目
- 标题和出处
- 难度
- 题目描述
- 要求
- 示例
- 数据范围
- 解法
- 思路和算法
- 代码
- 复杂度分析
题目
标题和出处
标题:制作 m 束花所需的最少天数
出处:1482. 制作 m 束花所需的最少天数
难度
5 级
题目描述
要求
给定一个整数数组 bloomDay \texttt{bloomDay} bloomDay,以及两个整数 m \texttt{m} m 和 k \texttt{k} k。
现需要制作 m \texttt{m} m 束花。制作花束时,需要使用花园中相邻的 k \texttt{k} k 朵花。
花园中有 n \texttt{n} n 朵花,第 i \texttt{i} i 朵花会在第 bloomDay[i] \texttt{bloomDay[i]} bloomDay[i] 天盛开,恰好可以用于一束花中。
返回从花园中制作 m \texttt{m} m 束花需要等待的最少的天数。如果不能制作 m \texttt{m} m 束花则返回 -1 \texttt{-1} -1。
示例
示例 1:
输入:
bloomDay
=
[1,10,3,10,2],
m
=
3,
k
=
1
\texttt{bloomDay = [1,10,3,10,2], m = 3, k = 1}
bloomDay = [1,10,3,10,2], m = 3, k = 1
输出:
3
\texttt{3}
3
解释:以下是前三天的花开过程,
x
\texttt{x}
x 表示花开,而
_
\texttt{\_}
_ 表示花还未开。
现在需要制作
3
\texttt{3}
3 束花,每束需要
1
\texttt{1}
1 朵花。
1
\texttt{1}
1 天后:
[x,
_,
_,
_,
_]
\texttt{[x, \_, \_, \_, \_]}
[x, _, _, _, _] // 只能制作
1
\texttt{1}
1 束花。
2
\texttt{2}
2 天后:
[x,
_,
_,
_,
x]
\texttt{[x, \_, \_, \_, x]}
[x, _, _, _, x] // 只能制作
2
\texttt{2}
2 束花。
3
\texttt{3}
3 天后:
[x,
_,
x,
_,
x]
\texttt{[x, \_, x, \_, x]}
[x, _, x, _, x] // 可以制作
3
\texttt{3}
3 束花,答案为
3
\texttt{3}
3。
示例 2:
输入:
bloomDay
=
[1,10,3,10,2],
m
=
3,
k
=
2
\texttt{bloomDay = [1,10,3,10,2], m = 3, k = 2}
bloomDay = [1,10,3,10,2], m = 3, k = 2
输出:
-1
\texttt{-1}
-1
解释:要制作
3
\texttt{3}
3 束花,每束需要
2
\texttt{2}
2 朵花,因此一共需要
6
\texttt{6}
6 朵花。而花园中只有
5
\texttt{5}
5 朵花,无法满足制作要求,返回
-1
\texttt{-1}
-1。
示例 3:
输入:
bloomDay
=
[7,7,7,7,12,7,7],
m
=
2,
k
=
3
\texttt{bloomDay = [7,7,7,7,12,7,7], m = 2, k = 3}
bloomDay = [7,7,7,7,12,7,7], m = 2, k = 3
输出:
12
\texttt{12}
12
解释:要制作
2
\texttt{2}
2 束花,每束需要
3
\texttt{3}
3 朵花。
花园在
7
\texttt{7}
7 天后和
12
\texttt{12}
12 天后的情况如下:
7
\texttt{7}
7 天后:
[x,
x,
x,
x,
_,
x,
x]
\texttt{[x, x, x, x, \_, x, x]}
[x, x, x, x, _, x, x]
可以用前
3
\texttt{3}
3 朵盛开的花制作第一束花。但不能使用后
3
\texttt{3}
3 朵盛开的花,因为它们不相邻。
12
\texttt{12}
12 天后:
[x,
x,
x,
x,
x,
x,
x]
\texttt{[x, x, x, x, x, x, x]}
[x, x, x, x, x, x, x]
显然,我们可以用不同的方式制作两束花。
数据范围
- bloomDay.length = n \texttt{bloomDay.length} = \texttt{n} bloomDay.length=n
- 1 ≤ n ≤ 10 5 \texttt{1} \le \texttt{n} \le \texttt{10}^\texttt{5} 1≤n≤105
- 1 ≤ bloomDay[i] ≤ 10 9 \texttt{1} \le \texttt{bloomDay[i]} \le \texttt{10}^\texttt{9} 1≤bloomDay[i]≤109
- 1 ≤ m ≤ 10 6 \texttt{1} \le \texttt{m} \le \texttt{10}^\texttt{6} 1≤m≤106
- 1 ≤ k ≤ n \texttt{1} \le \texttt{k} \le \texttt{n} 1≤k≤n
解法
思路和算法
为了制作 m m m 束花,每束需要 k k k 朵花,共需要 m × k m \times k m×k 朵花。花园中有 n n n 朵花,如果 m × k > n m \times k > n m×k>n,则即使将全部 n n n 朵花都用于制作花束也无法满足制作要求,因此返回 − 1 -1 −1。
当 m × k ≤ n m \times k \le n m×k≤n 时,制作全部花束需要的花不超过 n n n 朵,因此当所有花都盛开时,一定可以制作全部花束。
如果等待天数大于等于最少天数,则可以制作全部花束;如果等待天数小于最少天数,则不能制作全部花束。因此,这道题是二分查找判定问题,需要找到最少天数。
用 low \textit{low} low 和 high \textit{high} high 分别表示二分查找的下界和上界。由于 m m m 和 k k k 都至少是 1 1 1,且每朵花盛开的天数都至少是 1 1 1 天,因此 low \textit{low} low 的初始值等于 1 1 1;由于当所有花都盛开时一定可以制作全部花束,因此 high \textit{high} high 的初始值等于 bloomDay \textit{bloomDay} bloomDay 中的最大值。
当等待天数是 days \textit{days} days 时, bloomDay \textit{bloomDay} bloomDay 中的小于等于 days \textit{days} days 的元素对应的花已经盛开, bloomDay \textit{bloomDay} bloomDay 中的大于 days \textit{days} days 的元素对应的花尚未盛开。由此可以得到每朵花是否盛开,并根据连续盛开的花朵数计算可以制作多少束花。
每次查找时,取 mid \textit{mid} mid 为 low \textit{low} low 和 high \textit{high} high 的平均数向下取整,将 mid \textit{mid} mid 作为等待天数,判断是否可以制作全部花束,执行如下操作。
-
如果等待天数是 mid \textit{mid} mid 时可以制作全部花束,则最少天数小于等于 mid \textit{mid} mid,因此在 [ low , mid ] [\textit{low}, \textit{mid}] [low,mid] 中继续查找。
-
如果等待天数是 mid \textit{mid} mid 时不能制作全部花束,则最少天数大于 mid \textit{mid} mid,因此在 [ mid + 1 , high ] [\textit{mid} + 1, \textit{high}] [mid+1,high] 中继续查找。
当 low = high \textit{low} = \textit{high} low=high 时,查找结束,此时 low \textit{low} low 即为最少天数。
代码
class Solution {
public int minDays(int[] bloomDay, int m, int k) {
if ((long) m * k > bloomDay.length) {
return -1;
}
int low = Arrays.stream(bloomDay).min().getAsInt(), high = Arrays.stream(bloomDay).max().getAsInt();
while (low < high) {
int mid = low + (high - low) / 2;
if (canMakeMBouquets(bloomDay, m, k, mid)) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
public boolean canMakeMBouquets(int[] bloomDay, int m, int k, int days) {
int bouquets = 0;
int consecutive = 0;
int n = bloomDay.length;
for (int i = 0; i < n; i++) {
if (bloomDay[i] <= days) {
consecutive++;
if (consecutive % k == 0) {
bouquets++;
if (bouquets == m) {
return true;
}
}
} else {
consecutive = 0;
}
}
return false;
}
}
复杂度分析
-
时间复杂度: O ( n log m ) O(n \log m) O(nlogm),其中 n n n 是数组 bloomDay \textit{bloomDay} bloomDay 的长度, m m m 是数组 bloomDay \textit{bloomDay} bloomDay 中的最大值。需要执行 O ( log m ) O(\log m) O(logm) 次二分查找,每次二分查找需要 O ( n ) O(n) O(n) 的时间遍历数组 bloomDay \textit{bloomDay} bloomDay 判断是否可以制作全部花束,时间复杂度是 O ( n log m ) O(n \log m) O(nlogm)。
-
空间复杂度: O ( 1 ) O(1) O(1)。