Binary Search
class Solution {
public int search(int[] nums, int target) {
if(nums.length == 0) return -1;
int l = 0, r = nums.length -1;
while(l <= r) {
int mid = (l + r) / 2;
if(nums[mid] == target) {
return mid;
} else if(nums[mid] < target) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return -1;
}
}
TC: O(logn)
- Sqrt(x)
link: https://leetcode.com/problems/sqrtx/
Solution: binary search
class Solution {
public int mySqrt(int x) {
if(x < 2) return x;
int l = 2, r = x/2;
int mid;
while(l <= r) {
mid = (l + r) / 2;
if(Math.pow(mid, 2) == x) {
return mid;
} else if(Math.pow(mid, 2) < x) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return r;
}
}
TC: O(logn)
Note: 1. 当x小于2时,返回x本身;2. 最后返回右边值的原因是,最终值的平方必然小于x,则在while体内left总是会在最后+1,因此只有right是正确的。
- Guess Number Higher or Lower
link: https://leetcode.com/problems/guess-number-higher-or-lower/
Solution 1: 二分
public class Solution extends GuessGame {
public int guessNumber(int n) {
int l = 1, r = n;
int p1;
while(l <= r) {
p1 = l + (r - l) / 2;
int res = guess(p1);
if(res == 0) {
return p1;
} else if (res < 0) {
r = p1 - 1;
} else {
l = p1 + 1;
}
}
return -1;
}
}
Solution 2: 三分
public class Solution extends GuessGame {
public int guessNumber(int n) {
int l = 1, r = n;
int p1, p2;
while(l <= r) {
p1 = l + (r - l) / 3;
p2 = r - (r - l) / 3;
int r1 = guess(p1);
int r2 = guess(p2);
if(r1 == 0) {
return p1;
}
if(r2 == 0){
return p2;
} else if(r1 == -1) {
r = p1 - 1;
} else if (r2 == 1) {
l = p2 + 1;
} else {
l = p1 + 1;
r = p2 - 1;
}
}
return -1;
}
}
(left + right) / 3, (left + right) * 2 / 3
和
left + (right - left) / 3, right - (right - left) / 3
前者会产生越界:程序首先计算left + right的值,和会越界。
- Search in Rotated Sorted Array
link: https://leetcode.com/problems/search-in-rotated-sorted-array/
Solution:
class Solution {
int[] nums;
int target;
// 先二分找到pivot,再从pivot的左/右二分查找
public int findPivot(int left, int right) {
if(nums[left] < nums[right]) {
return 0;
}
while(left <= right) {
int mid = left + (right - left) / 2;
if(nums[mid] > nums[mid + 1]) {
return mid + 1;
} else {
if(nums[mid] < nums[left]) { // 此处如果用nums[mid] > nums[left]判断则会出错,举例[8,9,1,2,3]
right = mid - 1;
} else {
left = mid + 1;
}
}
}
return 0;
}
public int helper(int left, int right) {
int res;
while(left <= right) {
res = left + (right - left) / 2;
if(nums[res] > target) {
right = res - 1;
} else if(nums[res] < target) {
left = res + 1;
} else {
return res;
}
}
return -1;
}
public int search(int[] nums, int target) {
if(nums.length == 0) return -1;
if(nums.length == 1) return nums[0] == target ? 0 : -1;
this.nums = nums;
this.target = target;
int len = nums.length;
// 二分找到pivot
int p = findPivot(0, len - 1);
// nums[p]是全数列的最小值
// 如果target等于nums[p],则在[0:p], 比最末位小在[p+1:],
// target is the smallest number
if(target == nums[p]) {
return p;
}
// nums is not rotated
if(p == 0)
return helper(0, len - 1);
if(target < nums[0]) // 如果用target > nums[0]判断会出现问题,corner case[3, 1]
return helper(p, len - 1);
return helper(0, p);
}
}
- First Bad Version
link: https://leetcode.com/problems/first-bad-version/
Solution:
/* The isBadVersion API is defined in the parent class VersionControl.
boolean isBadVersion(int version); */
public class Solution extends VersionControl {
public int firstBadVersion(int n) {
if(n == 0) return 0;
if(n == 1) return isBadVersion(1) == true ? 1 : 0;
int left = 1, right = n;
while(left < right) {
int mid = left + (right - left) / 2;
if(isBadVersion(mid) == false) {
left = mid + 1;
} else {
right = mid;
}
}
if(isBadVersion(left) == true)
return left;
return 0;
}
}
- Find Peak Element
link: https://leetcode.com/problems/find-peak-element/
Solution 1:
class Solution {
public int findPeakElement(int[] nums) {
if(nums.length == 0) return 0;
// if(nums.length == 1) return 0;
int left = 0, right = nums.length - 1;
while(left < right) {
int mid = left + (right - left) / 2;
if(nums[mid] < nums[mid + 1]) {
left = mid + 1;
} else {
right = mid;
}
}
return left;
}
}
Solution 2:
不同的二分方法
class Solution {
public int findPeakElement(int[] nums) {
if(nums == null || nums.length == 0) return 0;
if(nums.length == 1) return 0;
if(nums.length == 2) return nums[0] > nums[1] ? 0 : 1;
int left = 0, right = nums.length - 1;
while(left + 1 < right) {
int mid = left + (right - left) / 2;
if(nums[mid] < nums[mid + 1]) {
left = mid;
} else {
right = mid;
}
}
return nums[right] > nums[left] ? right : left;
}
}
Note:
- 要注意右边位置的选择,究竟是len还是len - 1
- mid的值总是靠左,因此mid - 1会越界,而mid + 1不会
153 Find Minimum in Rotated Sorted Array
link: https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/
Solution:
class Solution {
public int findMin(int[] nums) {
int len = nums.length;
if(len == 0) return 0;
if(len == 1) return nums[0];
int l = 0, r = len - 1;
if(nums[l] < nums[r])
return nums[l];
while(l < r) {
int mid = l + (r - l) / 2;
if(nums[mid] < nums[0])
r = mid;
else
l = mid + 1;
}
return nums[r];
}
}
Note: 注意中间值的判断和返回值的选择
- Find K Closest Elements
link: https://leetcode.com/problems/find-k-closest-elements/
Solution:
如果目标值小于等于最小或者大于等于最大,则取数组两端的k个值;
目标值在数组中间,那么进行二分搜索,找到目标值的索引,或者距离目标值最近的数字的索引;
取索引前后的 k - 1 个值作为区间,一共2k - 1个值,最后结果一定在这个区间内。此处需要注意区间边界,左边不能小于0,右边不能大于数组长度;
循环比较两端距离x的远近,渐渐缩小区间直到区间长度为k。
class Solution {
public int binarySearch(int[] arr, int target) {
int l = 0, r = arr.length - 1;
while(l <= r) {
int mid = l + (r - l) / 2;
if(arr[mid] == target) {
return mid;
} else if(arr[mid] > target) {
r = mid - 1;
} else {
l = mid + 1;
}
}
// 比较哪边的差值更小,取更小的那个
return arr[l] - target < target - arr[r] ? l : r;
}
public List<Integer> findClosestElements(int[] arr, int k, int x) {
List<Integer> res = new ArrayList<>();
for(int i=0; i<arr.length; i++) {
res.add(arr[i]);
}
// x小于等于最小或大于等于最大,直接取两端k个值
if(x <= arr[0]) {
return res.subList(0, k);
} else if(x >= arr[arr.length - 1]) {
return res.subList(arr.length - k, arr.length);
} else {
// 首先找到k,或者是距离k最近的值
int idx = binarySearch(arr, x);
int leftIdx = Math.max(idx - k + 1, 0);
int rightIdx = Math.min(idx + k - 1, arr.length - 1);
while(rightIdx - leftIdx + 1 != k) {
int leftDiff = x - arr[leftIdx];
int rightDiff = arr[rightIdx] - x;
if(leftDiff > rightDiff) {
leftIdx++;
} else {
rightIdx--;
}
}
return res.subList(leftIdx, rightIdx+1);
}
}
}
- Closest Binary Search Tree Value
link: https://leetcode.com/problems/closest-binary-search-tree-value/
Solution:
class Solution {
public int closestValue(TreeNode root, double target) {
TreeNode res = root;
double diffMin = Double.MAX_VALUE;
while(root != null) {
double diffRo = root.val - target;
if(Math.abs(diffRo) < Math.abs(diffMin)) {
diffMin = diffRo;
res = root;
}
if(diffRo > 0) {
root = root.left;
} else if(diffRo < 0) {
root = root.right;
} else {
break;
}
}
return res.val;
}
}
- Search in a Sorted Array of Unknown Size
link: https://leetcode.com/problems/search-in-a-sorted-array-of-unknown-size/
Solution:
判断右边是否越界,如果越界,则二分
如果不越界,可能出现过度二分的情况,检查值的大小
如果小于target,说明已经过度二分,left设为当前值,同时right保留上一个值
如果大于target,则进行正常二分
class Solution {
public int search(ArrayReader reader, int target) {
if(reader.get(0) == 2147483647) return -1;
int left = 0, right = 19999;
while(left < right) {
int mid = left + (right - left) / 2;
if(reader.get(mid) == 2147483647) {
right = mid;
} else {
if(reader.get(mid) < target) {
left = mid + 1;
} else {
right = mid;
}
}
}
return reader.get(right) == target ? right : -1;
}
}
- Pow(x, n)
link: https://leetcode.com/problems/powx-n/
Solution:
迭代
第三行不可以用 if(n == 1) return x
corner case:当n = -2 ^ 31时,会越界
class Solution {
public double helper(double x, int n) {
if(n == 0) return 1.0;
double temp = helper(x, n/2);
if(n % 2 == 0) {
return temp * temp;
} else {
return temp * temp * x;
}
}
public double myPow(double x, int n) {
if(n == 0) return 1.0;
if(x == 0) return 0.0;
double res;
if(n > 0) {
res = helper(x, n);
} else {
res = helper(1/x, -n);
}
return res;
// return n > 0 ? res : 1/res;
}
}
- Valid Perfect Square
link: https://leetcode.com/problems/valid-perfect-square/
Solution1: 简单二分
需要注意计算平方时可能会产生越界
class Solution {
public boolean isPerfectSquare(int num) {
if(num == 0 || num == 1) return true;
int l = 1, r = num / 2;
while(l < r) {
int m = l + (r - l) / 2;
if((long)m * m == (long)num) {
return true;
} else if((long)m * m < (long)num) {
l = m + 1;
} else {
r = m - 1;
}
}
if(l * l == num) return true;
else return false;
}
}
Solution2: 牛顿法
具体推算方法见:https://blog.csdn.net/chenrenxiang/article/details/78286599
class Solution {
public boolean isPerfectSquare(int num) {
if(num == 0 || num == 1) return true;
long x = num / 2;
while(x * x > num) {
x = (x + num / x) / 2;
}
return (x * x == num);
}
}
- Find Smallest Letter Greater Than Target
Solution:
class Solution {
public char nextGreatestLetter(char[] letters, char target) {
int len = letters.length;
if(target - 'a' >= letters[len - 1] - 'a' || target - 'a' < letters[0] - 'a')
return letters[0];
int left = 0, right = len - 1;
while(left < right) {
int mid = left + (right - left) / 2;
if(letters[mid] - 'a' > target - 'a') {
right = mid;
} else {
left = mid + 1;
}
}
return letters[right];
}
}
- Find Minimum in Rotated Sorted Array II
link: https://leetcode.com/problems/find-minimum-in-rotated-sorted-array-ii/
Solution:
注意在遇到重复值的时候对边界的处理方式
不可以和nums[0]进行比较
class Solution {
public int findMin(int[] nums) {
if(nums.length == 1) return nums[0];
int len = nums.length;
// 没有rotate
if(nums[0] < nums[len - 1]) return nums[0];
int left = 0, right = len - 1;
while(left < right) {
int mid = left + (right - left) / 2;
if(nums[mid] > nums[right]) {
left = mid + 1;
} else if (nums[mid] < nums[right]){
right = mid;
} else {
right--;
}
}
return nums[right];
}
}
Worst case TC: O(N)
- Intersection of Two Arrays
link: https://leetcode.com/problems/intersection-of-two-arrays/
Solution1:
使用两个set并求交集
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
HashSet<Integer> set1 = new HashSet<>();
HashSet<Integer> set2 = new HashSet<>();
List<Integer> res = new ArrayList<>();
if(nums1.length == 0 || nums2.length == 0)
return new int[0];
for(int i=0; i<nums1.length; i++) {
set1.add(nums1[i]);
}
for(int i=0; i<nums2.length; i++) {
set2.add(nums2[i]);
}
for(Integer s : set1) {
if(set2.contains(s)) {
res.add(s);
}
}
int[] res1 = new int[res.size()];
for(int i=0; i<res.size(); i++) {
res1[i] = res.get(i);
}
return res1;
}
}
Solution2: sort以后二分查找
class Solution {
public boolean binarySearch(int x, int[] nums) {
int l = 0, r = nums.length - 1;
while(l <= r) {
int mid = l + (r - l) / 2;
if(nums[mid] == x) {
return true;
} else if(nums[mid] < x) {
l = mid + 1;
} else {
r = mid - 1;
}
}
return false;
}
public int[] intersection(int[] nums1, int[] nums2) {
Arrays.sort(nums1);
Arrays.sort(nums2);
int len1 = nums1.length, len2 = nums2.length;
List<Integer> res = new ArrayList<>();
if(len1 == 0 || len2 == 0 || nums1[0] > nums2[len2-1] || nums1[len1-1] < nums2[0]) {
return new int[0];
}
int l = 0, r = 1;
while(r < len1) {
if(nums1[l] == nums1[r]) {
r++;
} else {
if(binarySearch(nums1[l], nums2)) {
res.add(nums1[l]);
}
l = r;
r++;
}
}
if(binarySearch(nums1[l], nums2)) {
res.add(nums1[l]);
}
int[] res1 = new int[res.size()];
for(int i=0; i<res.size(); i++) {
res1[i] = res.get(i);
}
return res1;
}
}
TC:sort: O(nlogn), 遍历数组O(n),二分O(logn)
Solution3: Sort以后双箭头线性查找
class Solution {
public int[] intersection(int[] nums1, int[] nums2) {
if(nums1.length == 0 || nums2.length == 0) return new int[0];
Arrays.sort(nums1);
Arrays.sort(nums2);
int idx = 0, p1 = 0, p2 = 0;
while(p1 < nums1.length && p2 < nums2.length) {
if(nums1[p1] == nums2[p2]) {
if(idx == 0) {
nums1[idx] = nums1[p1];
idx++;
} else {
if(nums1[p1] != nums1[idx - 1]) {
nums1[idx] = nums1[p1];
idx++;
}
}
p1++;
p2++;
} else if(nums1[p1] < nums2[p2]) {
p1++;
} else {
p2++;
}
return Arrays.copyOf(nums1, idx);
}
}
TC: 线性查找O(m+n)
Note: 在同样是sort的情况下,如果m,n相差较大,则使用二分更快,如果m,n较为接近,则线性查找更快。
m = 2, n = 100 : 102 > 2log100
m = 100, n = 100: 200 < 100log100
- Find the Duplicate Number
link: https://leetcode.com/problems/find-the-duplicate-number/
Solution: 佛洛依德算法(龟兔赛跑),此算法常用于判断linkedlist是否存在环
算法过程:
- 兔子以乌龟的两倍速前进,直到两者相遇
- 乌龟回到原点,兔子速度降到与乌龟同速,两者再次相遇的点即为重复的数字/环的起点
推导过程:
假设经过F step到达环的起点,环周长为C step,
第一次相遇,乌龟走过了F + a,兔子走过了F + nC + a,因为兔子的速度是乌龟的两倍,由此推出:
2(F + a) = (F + nC + a)
化简得到:
F + a = nC
第二次相遇,乌龟回到起点,经过F step到达环的起点
兔子在第一次相遇的位置,即F+a(即在环起点后的a个位置),经过F step,兔子到达a+F的位置,又因为F = nC - a,所以兔子其实到达了nC的位置,即环的起点
class Solution {
public int findDuplicate(int[] nums) {
int t = nums[0], h = nums[nums[0]];
while(t != h) {
t = nums[t];
h = nums[nums[h]];
}
t = 0;
while(t != h) {
t = nums[t];
h = nums[h];
}
return h;
}
}
- Median of Two Sorted Arrays
link: https://leetcode.com/problems/median-of-two-sorted-arrays/
数组长度分别为n1和n2,中位数的位置 k 为:
奇数个数字:(n1+n2+1) / 2
偶数个数字:(n1+n2+1) / 2为左中位数
从两个数组中分别取出m和n个数,使得 m+n = k
需要满足的条件是 A[m - 1] < B[n] && A[m] > B[n-1]
左中位数为A[m - 1],B[n-1]中较大的一个
右中位数为A[m], B[n]中较小的一个
对一个数组进行二分求出m
Solution:
class Solution {
public double findMedianSortedArrays(int[] nums1, int[] nums2) {
int n1 = nums1.length;
int n2 = nums2.length;
if(n1 > n2) return findMedianSortedArrays(nums2, nums1);
// n1 + n2 is odd: k就是解
// n1 + n2 is even: k是左中位数
int k = (n1 + n2 + 1) / 2;
int m, n;
int left = 0, right = n1; // 重要:此处的right表示的是数组的长度
while(left <= right) {
m = left + (right - left) / 2;
n = k - m;
if (m > 0 && n < n2 && nums1[m-1] > nums2[n]) {
right = m - 1;
} else if (m < n1 && n > 0 && nums1[m] < nums2[n-1]) {
left = m + 1;
} else {
int maxLeft;
if(m == 0) {
maxLeft = nums2[n-1];
} else if(n == 0) {
maxLeft = nums1[m-1];
} else {
maxLeft = Math.max(nums1[m-1], nums2[n-1]);
}
if((n1 + n2) % 2 == 1) {
return maxLeft;
} else {
int minRight;
if(m == n1) {
minRight = nums2[n];
} else if(n == n2) {
minRight = nums1[m];
} else {
minRight = Math.min(nums1[m], nums2[n]);
}
return (maxLeft + minRight) / 2.0;
}
}
}
return -1;
}
}
Note:
-
对较短的数组进行二分,会提高计算速度
-
lc的解法比花花的解法好在,如果第一次就已经找到了正确的中位数位置,则可以直接进入最终计算,少了一个二分查找的时间
-
本题需要特别注意各种边界条件,比如right是数组的长度而非index,当数组触碰到边界(小于0或者大于长度)时的处理。
-
Find K-th Smallest Pair Distance
link: https://leetcode.com/problems/find-k-th-smallest-pair-distance/
Solution:
首先找到差的区间,对此区间进行二分
将数组排序,二分取到中点差值diff,对于第 j 位的数字,计算nums[j] - nums[0 … j-1]的差并与diff比较,记录小于diff的差 / pairs的个数。
如果个数大于k,则中点diff取值过大,反之则过小。
class Solution {
public int smallestDistancePair(int[] nums, int k) {
Arrays.sort(nums);
int low = 0, high = nums[nums.length - 1] - nums[0];
int count = 0;
while(low <= high) {
int mid = low + (high - low) / 2;
int j = 0;
for(int i=0; i < nums.length; i++) {
while(j < nums.length && nums[j] - nums[i] <= mid) j++;
count += j - i - 1;
if(count < k) {low = mid + 1;}
else if(count >= k) {high = mid - 1;}
}
}
return low;
}
}