整理自九章。
- 第一境界
- 写出不会死循环的二分法
- 递归与非递归的权衡
- 第二境界
- 在排序的数据集上进行二分
- 找到满足某个条件的第一个位置或者最后一个位置
- 第三境界
- 在未排序的数据集上进行二分
- 保留有解的一半,或者去掉无解的一半
- 第四境界
- 在答案集上进行二分
- 二分答案并验证答案偏大还是偏小
LintCode 457:经典二分查找问题
https://www.lintcode.com/problem/classical-binary-search
描述
在一个排序数组中找一个数,返回该数出现的任意位置,如果不存在,返回-1。
样例
输入:nums = [1,2,2,4,5,5], target = 2输出:1 或者 2输入:nums = [1,2,2,4,5,5], target = 6输出:-1
解题思路
AC代码
public int findPosition(int[] nums, int target) {// 空数组直接返回-1;if (nums.length < 1) {return -1;}int start = 0, end = nums.length - 1;while (start + 1 < end) {int mid = start + (end - start) / 2;if (nums[mid] == target) {return mid;} else if (nums[mid] < target) {start = mid;} else {end = mid;}}if (nums[start] == target) {return start;}if (nums[end] == target) {return end;}return -1;}
LintCode 460:在排序数组中找最接近的K个数
https://www.lintcode.com/problem/find-k-closest-elements
描述
给一个目标数target,一个非负整数K,一个按照升序排列的数组A。在A中找到与target最接近的K个整数。
返回这K个数并按照与target的接近程度从小到大排序,如果接近程度相当,那么小的数排在前面。
k是一个非负整数,并且总是小于已排序数组的长度。- 给定数组的长度是正整数, 不会超过 10^4104
- 数组中元素的绝对值不会超过 10^4104
样例
```java 输入: A = [1, 2, 3], target = 2, k = 3 输出: [2, 1, 3]
输入: A = [1, 4, 6, 8], target = 3, k = 3 输出: [4, 1, 6]
<a name="vGPI2"></a>#### 解题思路找到最接近target的一个数,然后向两边扩展,找到k个即可。二分法 + 双指针<a name="MePcJ"></a>#### AC代码```javapublic int[] kClosestNumbers(int[] A, int target, int k) {// right为最接近target的坐标,left为right-1,从两边扩展找到K个最接近的数int right = findUpperClosest(A, target);int left = right - 1;// ans保存答案int[] ans = new int[k];for (int i = 0; i < k; i++) {// 左指针更靠近target则添加左指针上的数if (isLeftClosest(A, target, left, right)) {ans[i] = A[left--];} else {// 右指针更靠近target则添加右指针上的数ans[i] = A[right++];}}return ans;}// 返回左右指针谁更靠近targetprivate boolean isLeftClosest(int[] a, int target, int left, int right) {// 判断临界条件if (left < 0) {return false;}if (right > a.length - 1) {return true;}// 判断左右指针谁更靠近targetreturn Math.abs(a[left] - target) <= Math.abs(a[right] - target) ? true : false;}// 找到最接近target的坐标private int findUpperClosest(int[] a, int target) {// 就是经典二分查找int left = 0, right = a.length - 1;while (left + 1 < right) {int mid = (left + right) / 2;if (a[mid] == target) {return mid;} else if (a[mid] < target) {left = mid;} else {right = mid;}}// 返回最接近的坐标return Math.abs(a[left] - target) <= Math.abs(a[right] - target) ? left : right;}
LintCode 159:寻找旋转排序数组中的最小值
https://www.lintcode.com/problem/find-minimum-in-rotated-sorted-array
描述
假设一个排好序的数组在其某一未知点发生了旋转(比如0 1 2 4 5 6 7 可能变成4 5 6 7 0 1 2)。你需要找到其中最小的元素
你可以假设数组中不存在重复元素
样例
输入:[4, 5, 6, 7, 0, 1, 2]输出:0解释:数组中的最小值为0输入:[2,1]输出:1解释:数组中的最小值为1
解题思路
根据题目可以找到旋转后数组的特点。
- 如果这个数小于它前面的数,那它就是最小的那个,可以直接返回答案。
- 如果它大于数组第一个元素,那么最小的元素肯定在它的右边。
- 小于数组第一个元素,则最小的元素肯定在它的左边。
AC代码
public int findMin(int[] nums) {// 没有旋转直接返回最小元素,也就是nums[0]if (nums[0] < nums[nums.length - 1]) {return nums[0];}int left = 0, right = nums.length - 1;while (left + 1 < right) {int mid = left + (right - left) / 2;// 小于了前面的数表示就是最小数if (nums[mid] < nums[mid - 1]) {return nums[mid];}// 大于首元素,left往右移动if (nums[mid] > nums[0]) {left = mid;} else if (nums[mid] < nums[nums.length - 1]) {// 小于首元素,right往左移动right = mid;}}return Math.min(nums[left], nums[right]);}
LintCode 75:寻找峰值
https://www.lintcode.com/problem/find-peak-element
描述
你给出一个整数数组(size为n),其具有以下特点:
- 相邻位置的数字是不同的
- A[0] < A[1] 并且 A[n - 2] > A[n - 1]
假定P是峰值的位置则满足A[P] > A[P-1]且A[P] > A[P+1],返回数组中任意一个峰值的位置。
- 数组保证至少存在一个峰
- 如果数组存在多个峰,返回其中任意一个就行
- 数组至少包含 3 个数
样例
```java 输入: [1, 2, 1, 3, 4, 5, 7, 6] 输出: 1 or 6 解释: 返回峰顶元素的下标
输入: [1,2,3,4,1] 输出: 3
<a name="BJLPr"></a>#### 解题思路观察题目数据可以得知:- 位置P满足,A[P] > A[P - 1] && A[P] > A[P + 1],那P就是峰值- 若 A[P - 1] > A[P] > A[P + 1],又因为A[0] < A[1],所以在0 ~ P中肯定有峰值- 若 A[P - 1] < A[P] < A[P + 1],又因为A[n - 2] > A[n - 1],所以在P至末尾肯定有峰值由此可以二分查找得到答案<a name="H9cpX"></a>#### AC代码```javapublic int findPeak(int[] A) {int left = 0, right = A.length - 1;while (left + 1 < right) {int mid = left + (right - left) / 2;if (A[mid] > A[mid - 1]) {// 满足A[P] > A[P - 1] && A[P] > A[P + 1],即为峰值if (A[mid] > A[mid + 1]) {return mid;}// A[P - 1] < A[P] < A[P + 1],左指针右移left = mid;} else {// 右指针左移right = mid;}}// 返回峰值return Math.max(A[left], A[right]);}
LintCode 183:木材加工
https://www.lintcode.com/problem/wood-cut
描述
有一些原木,现在想把这些木头切割成一些长度相同的小段木头,需要得到的小段的数目至少为 k。当然,我们希望得到的小段越长越好,你需要计算能够得到的小段木头的最大长度。
木头长度的单位是厘米。原木的长度都是正整数,我们要求切割得到的小段木头的长度也要求是整数。无法切出要求至少 k 段的,则返回 0 即可。
挑战:O(n log Len), Len为 n 段原木中最大的长度
样例
输入:L = [232, 124, 456]k = 7输出: 114Explanation: 我们可以把它分成114cm的7段,而115cm不可以输入:L = [1, 2, 3]k = 7输出: 0说明:很显然我们不能按照题目要求完成。
解题思路
题目中有提到用O(n log Len)的算法,能够实现logN的算法之一便是二分法,所以可以考虑二分。
二分枚举木头长度,从0到长度最长的木头长度,从答案验证是否能够切成K段。能够切成K段左指针右移,不能右指针左移。
AC代码
public int woodCut(int[] L, int k) {// 临界直接返回if (L.length < 1){return 0;}// 找到长度最长的木头int maxLen = L[0];for (int i = 1; i < L.length; i++) {maxLen = Math.max(maxLen, L[i]);}// 二分枚举木头长度int ans = 0;int left = maxLen / k, right = maxLen;while (left + 1< right) {int mid = left + (right - left) / 2;if (check(L, mid, k)) {left = mid;} else {right = mid;}}if (check(L, right, k)) {return right;}return left;}// 判断是否能够切成K段lenpublic boolean check(int[] L, int len, int k) {int count = 0;for (int length : L) {count += length / len}return count >= k ? true : false;}
