3. LintCode 二分法题目
2. LintCode双指针题目(二)
整理自九章。
- 第一境界
- 写出不会死循环的二分法
- 递归与非递归的权衡
- 第二境界
- 在排序的数据集上进行二分
- 找到满足某个条件的第一个位置或者最后一个位置
- 第三境界
- 在未排序的数据集上进行二分
- 保留有解的一半,或者去掉无解的一半
- 第四境界
- 在答案集上进行二分
- 二分答案并验证答案偏大还是偏小
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
样例
输入: A = [1, 2, 3], target = 2, k = 3
输出: [2, 1, 3]
输入: A = [1, 4, 6, 8], target = 3, k = 3
输出: [4, 1, 6]
解题思路
找到最接近target的一个数,然后向两边扩展,找到k个即可。二分法 + 双指针
AC代码
public 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;
}
// 返回左右指针谁更靠近target
private boolean isLeftClosest(int[] a, int target, int left, int right) {
// 判断临界条件
if (left < 0) {
return false;
}
if (right > a.length - 1) {
return true;
}
// 判断左右指针谁更靠近target
return 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 个数
样例
输入: [1, 2, 1, 3, 4, 5, 7, 6]
输出: 1 or 6
解释:
返回峰顶元素的下标
输入: [1,2,3,4,1]
输出: 3
解题思路
观察题目数据可以得知:
- 位置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至末尾肯定有峰值
由此可以二分查找得到答案
AC代码
public 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
输出: 114
Explanation: 我们可以把它分成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段len
public boolean check(int[] L, int len, int k) {
int count = 0;
for (int length : L) {
count += length / len
}
return count >= k ? true : false;
}