有时精度不容易确定,因此直接固定循环次数进行二分,即第二种方法 。与第一种方法仅有「循环条件」的区别,代码示例如下:
for (int i = 0; i < 100; i++) {double mid = (l + r) / 2;if (check(mid)) l = mid;else r = mid;}
三、二分答案二分算法成立的关键在于单调性,因此任何具有单调性的问题,我们都可以考虑在其上使用二分算法的可能性 。正是基于这一点,当问题的答案具有单调性时,我们可以通过二分将「问题的求解」转化为「问题的判定」,该方法即为「二分答案」 。
接下来我们将通过两道例题进一步地讲解该算法 。
287. 寻找重复数题目描述给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数 。
假设 nums 只有一个重复的整数,请找出这个重复的数,并只使用 O(1) 的额外空间 。
示例 1输入:nums = [1,3,4,2,2]输出:2
示例 2输入:nums = [3,1,3,4,2]输出:3
示例 3输入:nums = [1,1]输出:1
示例 4输入:nums = [1,1,2]输出:1
提示
- 2 <= n <= 3e4
- nums.length == n + 1
- 1 <= nums[i] <= n
- nums 中只有一个整数出现两次或多次,其余整数均只出现一次
一道题想要使用「二分答案」,其最大的前提是「答案有单调性」,而本题所求解的答案是显然的,即「重复数的值」 。所以我们需要找到一个「判定条件」,使得对于二分区间 [l, r],我们可以根据其中值 mid 是否满足「判定条件」,来确定下一个搜索区间是左半部分还是右半部分 。
在本题中,我们令 f(x) 表示数组中 <= x 的个数,则不难发现 f(x) 是关于 x 的单调函数,即随着 x 的变大,f(x) 要么变大要么不变 。
具体来说,假设重复数为 A,则对于所有小于 A 的数 x,f(x) = x;而所有大于等于 A 的数 y,f(y) > y 。例如对于数组 [1 2 2 2 3],n = 4,f(1) = 1,f(2) = 4,f(3) = 5,f(4) = 5 。
由此我们可以通过「二分答案」来解决本题,对于二分区间 [l, r],其中值为 mid,若 f(mid) > mid,则说明重复数小于等于 mid,因此更新 ans = mid,且将二分区间变为 [l, mid - 1];否则不更新 ans 且二分区间变为 [mid + 1, r] 。
二分答案的时间复杂度为 O(log(n)),对于特定的 mid,求解 f(mid) 的时间复杂度为 O(n),因此本题总的时间复杂度为 O(nlog(n)),具体代码如下所示:
class Solution {public:bool check(vector<int>& nums, int mid) {int cnt = 0;for (int x:nums) {if (x <= mid) cnt++;}if (cnt > mid) return 1;else return 0;}int findDuplicate(vector<int>& nums) {int l = 1, r = nums.size(), ans = 0;while (l <= r) {int mid = (l + r) / 2;if (check(nums, mid)) {ans = mid;r = mid - 1;}else l = mid + 1;}return ans;}};
其中 while 循环中的 check 函数即为「二分答案」问题中的「判定条件」 。对于「二分答案」所适用的题目,我们通常只需对 check 函数进行修改,while 循环中的部分基本不变 。410. 分割数组的最大值题目描述给定一个非负整数数组 nums 和一个整数 m,你需要将这个数组分成 m 个非空的连续子数组 。
设计一个算法使得这 m 个子数组各自和的最大值最小 。
示例 1
输入:nums = [7,2,5,10,8], m = 2输出:18解释:一共有四种方法将 nums 分割为 2 个子数组 。其中最好的方式是将其分为 [7,2,5] 和 [10,8]。因为此时这两个子数组各自的和的最大值为18,在所有情况中最小 。
示例 2输入:nums = [1,2,3,4,5], m = 2输出:9
示例 3输入:nums = [1,4,4], m = 3输出:4
提示- 1 <= nums.length <= 1000
- 0 <= nums[i] <= 1e6
- 1 <= m <= min(50, nums.length)
具体来说,在本题中,若「子数组和的最大值小于等于 x」符合数组分割要求,则比 x 更大的答案也一定符合要求 。即当 x 为负数时,数组分割要求无法满足,但随着 x 的不断变大,会有一个分界点 A,当 x 大于等于 A 时,数组分割要求即可满足,而这个 A 即为本题答案 。
由此本题从「求解 A」转换为了「二分 A 并判断数组分割要求是否可以满足」 。对于二分区间 [l, r],其中值为 mid,若 mid 满足数组分割要求,则更新 ans = mid,二分区间变为 [l, mid - 1];否则不更新 ans 且二分区间变为 [mid + 1, r] 。
推荐阅读
- 都Linux的老司机了,连个echo都用不好,大概火候差在这
- 快速上手几个 Linux 命令:每家公司都有自己的黑话
- 大学必备含金量高的几大证书是什么?
- 马里奥最初登场的游戏作品是什么?
- 熟猪蹄常温能放多久?
- 一般公司或者团队是怎么进行代码开发并且部署到服务器上的?
- M.2硬盘这么插速度才是最快的
- golang 多协程的同步方法总结
- 简单易用的数据库哪个比较好?
- 世界品牌不粘锅?不粘锅鼻祖