二分搜索进阶用法

下面,我将从leetcode的一种高频题型来讲述一下关于我对二分搜索的一种进阶用法。从刷题以来,这种题型我经常遇见,由于经常遇见,所以说看见这种题我基本上就是直接ac了,不用怀疑,面对这种题型,完全不用动脑子,一套框架,直接带走,当然前提是你得知道这道题用这种方法可解才行,那么如何知道?下面我会告诉你们。

题目描述

这是一道来自leetcode的周赛第三题。

1482. 制作 m 束花所需的最少天数

题目描述,可以自行点击链接查看,这里不做多的赘述。

如果是第一次做这种题型的人,很容易写出O(n*m)时间复杂度的代码,也就是暴力解法,我贴出代码大家体会一下。

class Solution {
public:
    int minDays(vector<int>& bloomDay, int m, int k) {
        if (m * k > bloomDay.size()) return -1;
        int day = -1;       
        int cnt = 0;
        while (cnt < m) {
            int bloom = 0;
            cnt = 0;
            day++;
            for (int i = 0; i < bloomDay.size(); ++i) {
                if (bloomDay[i] <= day) {
                    bloom++;
                    if (bloom == k) {
                        cnt++;
                        bloom = 0;
                    }
                } else {
                    bloom = 0;
                }
            }
            cout << cnt << "  " << day << endl;
        }
        return day;
    }
};

所谓的暴力解法,也就是我们遍历出每一种可能,直到某一种可能满足条件,我们就返回这个可能。

在这里,我们依次将需要等待的天数从day = 0开始递增,然后循环数组,看一下等待day天后可以制作多少花,如果满足条件就可以退出循环返回答案了。

在这里 n 表示 bloomDay的最大天数,m就表示bloomDay的数组长度,所以时间复杂度不言而喻。

题目明确规范了数据范围:

  • bloomDay.length == n
  • 1 <= n <= 10^5
  • 1 <= bloomDay[i] <= 10^9
  • 1 <= m <= 10^6
  • 1 <= k <= n

难点就在于 n 可能会很大,会超时。

比如题中的case 4:

bloomDay = [1000000000,1000000000], m = 1, k = 1

在这里的话,我们的day会遍历到1000000000,很明显,超时了,暴力法行不通。

接下来看二分搜索的进阶用法。

二分check法

为什么会叫二分check法?看完后,你就会明白。

对于这种题,其实官方很明显的提示我们该怎么做,接下来你就需要总结出经验,形成一种feel,下次看见了就可以直接秒了。

一般而言,leetcode的提示都会在数据范围里找得到。

我这里举几个case

  1. 数组里的值在 0 … n 的范围内,n表示数组长度
  2. 数组里的数据不会重复
  3. 数组长度 大于 0 小于 多少多少

第一个提示官方在告诉我们可以用 位运算 或者 哈希数组 来做

第二个提示代表我们不用担心算出来的答案会重复,比如说用回溯法 DFS的时候会考虑结果集重复问题。

第三个提示就是这道题的提示了,很明显,看见这种提示我们可以考虑用暴力法了,那为什么不能用暴力法呢?

官方还提示了一点,也就是 1 <= bloomDay[i] <= 10^9

其实我们应该可以想出来该怎么做了,优化 day 的遍历,其实答案不会超过bloomDay数组的最大值,所以我们不用从 **0 -> max(bloomDay) **依次遍历,只需要二分来找就是了,perfect

思路:

  • 二分搜索0.... max(bloomDay),每次搜索时检查(check)数组
  • 检查结果做对比,不断的缩小搜索范围

思路得出后,下面我总结出一套对于二分check法的框架,以后对于这种题直接秒就行了,当然你得知其所以然。

伪代码框架:

int binar_search_check(int[] arr) {
	int right = max(arr);	// 找到right值,也就是数组最大值
	int left = 0;
	// 下面就是二分搜索的框架了
	while (left < right) {
 		int mid = left + (right - left) / 2;
 		int check = get_check(arr, mid);
 		if (条件) {
 			left = mid + 1;
 		} else if (条件) {
 			right = mid;
 		}
	}
	return left;
}

int get_check(int[] arr, int k) {
	int ans = 0;
	for (遍历数组) {
		if (条件) {
			ans++;
		} else {
			处理代码
		}
	}
	return ans;
}

然后,我们处理一下框架,让这套框架成为题解。

添加一点点细节

class Solution {
public:
    int minDays(vector<int>& bloomDay, int m, int k) {
        if (m * k > bloomDay.size()) {
            return -1;
        }
        int left = 0;
        int right = 0;
        for (int v : bloomDay) {
            right = max(v, right);
        }
        // 二分搜索
        while (left < right) {
            int mid = left + (right - left) / 2;
            int cnt = getCount(bloomDay, mid, k);
            if (cnt < m) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left;
    }
	// check (其实就是遍历一边数组,保存满足条件的)
    int getCount(vector<int>& bloomDay, int day, int k) {
        int ans = 0;
        int cnt = 0;
        for (int i = 0; i < bloomDay.size(); ++i) {
            if (bloomDay[i] <= day) {
                cnt++;
            } else {
                cnt = 0;
            }
            if (cnt == k) {
                cnt = 0;
                ans++;
            }
        }
        return ans;
    }

};

为了方便大家理解,我找了一下这种题型的题,感兴趣的都可做一下,切实感受这种解法的奥妙。

其他类型题目:

1095我曾在git上提交过题解,有兴趣的可以看一下我的理解:

完结撒花

其实也没啥好说的,也就是继续捞一手我的微信公众号。

  • 微信公众号:NonCover
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值