1482. 制作 m 束花所需的最少天数
给你一个整数数组 bloomDay,以及两个整数 m 和 k 。
现需要制作 m 束花。制作花束时,需要使用花园中 相邻的 k 朵花 。
花园中有 n 朵花,第 i 朵花会在 bloomDay[i] 时盛开,恰好 可以用于 一束 花中。
请你返回从花园中摘 m 束花需要等待的最少的天数。如果不能摘到 m 束花则返回 -1 。
示例一:
输入:bloomDay = [1,10,3,10,2], m = 3, k = 1
输出:3
解释:让我们一起观察这三天的花开过程,x 表示花开,而 _ 表示花还未开。
现在需要制作 3 束花,每束只需要 1 朵。
1 天后:[x, _, _, _, _] // 只能制作 1 束花
2 天后:[x, _, _, _, x] // 只能制作 2 束花
3 天后:[x, _, x, _, x] // 可以制作 3 束花,答案为 3
解法一、二分+排序
天数具有二段性和排他性,假设答案天数是day : [0,day)的范围内都是不满足制作m束花的,(day,inf)的范围内都可以制作,但是day这个天数是可以确定的。 这就是二分的左边界查找day或右边界粗略查找来找day-1;
- 看题目中的解释, 找的是一个天数可以满足制作m束花,并加了一个限定要确定最少天数。就自然想到了在一个有序的天数序列中二分左边界找符合制作m束花的天数。
- 检查函数就是遍历整个bloomDay遇到开花的就统计能制作多少束花,最后返回相应天数制作的总数。
二分左边界模板:
// 找值为target的处于最左位置的索引
public int leftBound(int[] nums,int l, int r, int target){
while(l<=r){
int mid = l + (r-l)/2;
if(nums[mid]>=target){
r = mid-1; //开区间
}else{
l = mid+1;//开区间
}
}
// 越界或target不存在
if(l>=nums.length || nums[l] != target)
return -1;
return l;
}
class Solution {
int[] blooms;
public int minDays(int[] bloomDay, int m, int k) {
if(m*k > bloomDay.length) return -1; //需要的花多大于实际的花朵
Set<Integer> sortedDay = new TreeSet<Integer>((a,b) -> {return a-b;});
for(int b : bloomDay){
sortedDay.add(b);
}
Integer[] days = (Integer[] ) sortedDay.toArray(new Integer[0]);
//在days中二分查找合适的day
if(m*k == bloomDay.length) return days[days.length-1].intValue(); //需要全部的花,在最大的日子
blooms = bloomDay;
return leftBound(days, m, k);
}
/**
* 左边界二分查找
* @param days 排序过后的花开的日子
* @param m 制作m束花
* @param k 多少多花一束花
* @return 最小的日子制作m束花
*/
public int leftBound(Integer[] days, int m, int k)
{
int left = 0, right = days.length-1;
while(left <= right){
int mid = left + (right - left)/2;
int num = check(days[mid], m, k); // mid这个日子里可以制作多少束花.
//System.out.println(mid + " : " + num);
if( num >= m){
right = mid-1;
}else{
left = mid+1;
}
}
if(left >= days.length) return -1;
return days[left];
}
// 检查day天后可以制作花的数量
public int check(int day, int m, int k){
int i = 0, res = 0;
while(i < blooms.length){
if(blooms[i]<=day){
//这朵花可以开
int tmp = 0;
while(i < blooms.length && blooms[i] <= day){
tmp++;
if(tmp == k){
//连续开花达到制作一束花的水平
res++;
tmp = 0;
}
i++;
}
}
++i;
}
return res;
}
}
时间复杂度: Treeset添加n个数是O(logn) , 二分是O(logn),检查函数是O(n), 总的时间复杂度是O(nlogn)???但是力扣跑出来比解法二慢。不晓得为什么。
空间复杂度:O(n)
解法二、对1-1e9的范围进行二分
class Solution {
int[] blooms;
public int minDays(int[] bloomDay, int m, int k) {
//在days中二分查找合适的day
if( m*k > bloomDay.length) return -1;
blooms = bloomDay;
return leftBound( m, k);
}
/**
* 左边界二分查找
* @param m 制作m束花
* @param k 多少多花一束花
* @return 最小的日子制作m束花
*/
public int leftBound( int m, int k)
{
int left = 0, right = (int)1e9;
while(left <= right){
int mid = left + (right - left)/2;
int num = check(mid, m, k); // mid这个日子里可以制作多少束花.
//System.out.println(mid + " : " + num);
if( num >= m){
right = mid-1;
}else{
left = mid+1;
}
}
return left;
}
public int check(int day, int m, int k){
int i = 0, res = 0;
while(i < blooms.length){
if(blooms[i]<=day){
//这朵花可以开
int tmp = 0;
while(i < blooms.length && blooms[i] <= day){
tmp++;
if(tmp == k){
//连续开花达到制作一束花的水平
res++;
tmp = 0;
}
i++;
}
}
++i;
}
return res;
}
}
- 时间复杂度:
check
函数的复杂度为 O(n)。整体复杂度为 O(nlog(1e9)) - 空间复杂度:O(n)
优化:
对二分时的边界做优化,left=min{bloomday},right = max{bloomday}