LeetBook-二分查找

100 阅读10分钟

二分查找

二分查找 - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台

二分查找

解法1

二分查找

class Solution {
    public int search(int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return -1;
    }
}

x的平方根

解法1

呃呃呃

class Solution {
    public int mySqrt(int x) {
        return (int)Math.sqrt(x);
    }
}

解法2

二分查找,很简单就不写了

class Solution {
    public int mySqrt(int x) {
        int left = 0;
        int right = x;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            long pow = (long) mid * mid;
            if (pow == x) {
                return mid;
            } else if (pow > x) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return right;
    }
}

猜数字大小

解法1

二分查找

/** 
 * Forward declaration of guess API.
 * @param  num   your guess
 * @return 	     -1 if num is higher than the picked number
 *			      1 if num is lower than the picked number
 *               otherwise return 0
 * int guess(int num);
 */

public class Solution extends GuessGame {
    public int guessNumber(int n) {
        int left = 0;
        int right = n;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (guess(mid) == 0) {
                return mid;
            } else if (guess(mid) == 1) {
                left = mid + 1;
            } else {
                right = mid -1;
            }
        }
        return 1;
    }
}

搜索旋转排序数组

解法1

找断点+二分查找

class Solution {
    public int search(int[] nums, int target) {
        if (nums.length == 1) {
            if (nums[0] == target) {
                return 0;
            } else {
                return -1;
            }
        }
        int left = 0;
        int right = nums.length - 1;
        if (nums[left] == target) {
            return left;
        }
        if (nums[right] == target) {
            return right;
        }
        if (nums[right] < nums[left]){
            for (int i = 0; i < nums.length; i++) {
                if (nums[i + 1] < nums[i]) {
                    if (target > nums[0]) {
                        right = i;
                        break;
                    } else {
                        left = i + 1;
                        break;
                    }
                }
            }
        }
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                return mid;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return -1;
    }
}

第一个错误版本

解法1

二分查找

/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left = 1;
        int right = n;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (!isBadVersion(mid - 1) && isBadVersion(mid)) {
                return mid;
            } else if (isBadVersion(mid)) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return 0;
    }
}

解法2

解法1的基础优化

  • 如果求第一个错误版本,那么取到正确版本就给left赋值mid + 1,循环结束后left即为第一个错误版本
/* The isBadVersion API is defined in the parent class VersionControl.
      boolean isBadVersion(int version); */

public class Solution extends VersionControl {
    public int firstBadVersion(int n) {
        int left = 1;
        int right = n;
        int mid = 0;
        while (left < right) {
            mid = left + (right - left) / 2;
            if (isBadVersion(mid)) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
}

寻找峰值

解法1

暴力循环

class Solution {
    public int findPeakElement(int[] nums) {
        for (int i = 1; i < nums.length - 1 ;i++) {
            if (nums[i - 1] < nums[i] && nums[i + 1] < nums[i]) {
                return i;
            }
        }
        if (nums[0] > nums[nums.length - 1]) {
            return 0;
        }
        return nums.length - 1;
    }
}

解法2

二分查找

class Solution {
    public int findPeakElement(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid + 1] < nums[mid]) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
}

寻找旋转排序数组中的最小值

解法1

  1. 假设数组第一个值为最小值
  2. 进行一次循环,如果碰到某个值k小于第一个值的,直接返回k
  3. 否则返回数组第一个值
 class Solution {
     public int findMin(int[] nums) {
         for (int i = 1; i < nums.length; i++) {
             if (nums[i] < nums[0]) {
                 return nums[i];
             }
         }
         return nums[0];
     }
 }

在排序数组中查找元素的第一个和最后一个位置

解法1

二分查找

  • 碰到相等的元素,把其确定为中点,向两侧遍历寻找临界点并返回
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int[] result = {-1, -1};
        if (nums.length == 0) {
            return result;
        }
        if (nums.length == 1 && nums[0] == target) {
            result[0] = 0;
            result[1] = 0;
            return result;
        }
        int left = 0; 
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) {
                left = mid;
                right = mid;
                while (left != 0 && nums[left - 1] == target) {
                    left--;
                }
                while (right != nums.length - 1 && nums[right + 1] == target) {
                    right++;
                }
                result[0] = left;
                result[1] = right;
                break;
            } else if (nums[mid] > target) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return result;
    }
}

找到K个最接近的元素

解法1

官解思路

找到 K 个最接近的元素 - 找到 K 个最接近的元素 - 力扣(LeetCode)

class Solution {
    public List<Integer> findClosestElements(int[] arr, int k, int x) {
        int left = 0;
        int right = arr.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (arr[mid] >= x) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        right = left;
        left = right - 1;
        while (k > 0) {
            if (left < 0) {
                right++;
            } else if (right >= arr.length) {
                left--;
            } else if (x - arr[left] <= arr[right] - x) {
                left--;
            } else {
                right++;
            }
            k--;
        }
        List<Integer> result = new ArrayList<>();
        for (int i = left + 1; i < right; i++) {
            result.add(arr[i]);
        }
        return result;
    }
}

寻找峰值

解法1

暴力循环

 class Solution {
     public int findPeakElement(int[] nums) {
         for (int i = 1; i < nums.length - 1 ;i++) {
             if (nums[i - 1] < nums[i] && nums[i + 1] < nums[i]) {
                 return i;
             }
         }
         if (nums[0] > nums[nums.length - 1]) {
             return 0;
         }
         return nums.length - 1;
     }
 }

解法2

二分查找

 class Solution {
     public int findPeakElement(int[] nums) {
         int left = 0;
         int right = nums.length - 1;
         while (left < right) {
             int mid = left + (right - left) / 2;
             if (nums[mid + 1] < nums[mid]) {
                 right = mid;
             } else {
                 left = mid + 1;
             }
         }
         return left;
     }
 }

Pow(x, n)

解法1

hhhhhh,写着玩

class Solution {
    public double myPow(double x, int n) {
        return Math.pow(x, n);
    }
}

解法2

递归乘法

class Solution {
    public double myPow(double x, int n) {
        if (n == 0) {
            return 1;
        }
        int N = n > 0 ? n : 0 - n;
        double value = dododo(x, N);
        return n > 0 ? value : 1 / value;
    }
    public double dododo(double x, int n) {
        if (n == 0) {
            return 1;
        }
        double value = dododo(x, n / 2);
        if (n % 2 == 0) {
            return value * value;
        }
        return value * value * x;
    }
}

解法3

  • 折半计算,每次把n缩小一半,奇数少乘一次x
class Solution {
    public double myPow(double x, int n) {
        if (n == 0 || x == 1) {
            return 1;
        }
        if (n < 0) {
            x = 1 / x;
            n = -1 * n;
        }
        double result = 1;
        for (int i = n; i != 0; i /= 2) {
            if (i % 2 != 0) {
                result *= x;
            }
            x *= x;
        }
        return result;
    }
}

有效的完全平方数

解法1

写着玩,hhhh

class Solution {
    public boolean isPerfectSquare(int num) {
        int result = (int)Math.sqrt(num) * (int)Math.sqrt(num);
        return num == result;
    }
}

解法2

二分查找

class Solution {
    public boolean isPerfectSquare(int num) {
        int left = 1;
        int right = num;
        while (left < right) {
            long mid = left + (right - left + 1) / 2;
            if (mid * mid == num) {
                return true;
            }
            if (mid * mid <= num) {
                left = (int) mid;
            } else {
                right = (int) mid - 1;
            }
        }
        return left * left == num;
    }
}

寻找比目标字母大的最小字母

解法1

暴力循环

class Solution {
    public char nextGreatestLetter(char[] letters, char target) {
        for (int i = 0; i < letters.length; i++) {
            if (letters[i] > target) {
                return letters[i];
            }
        }
        return letters[0];
    }
}

解法2

二分查找

class Solution {
    public char nextGreatestLetter(char[] letters, char target) {
        if (target >= letters[letters.length - 1]) {
            return letters[0];
        }
        int left = 0;
        int right = letters.length - 1;
        int mid = 0;
        char result = ' ';
        while (left <= right) {
            mid = left + (right - left) / 2;
            if (letters[mid] > target) {
                result = letters[mid];
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return result;
    }
}

寻找旋转排序数组中的最小值

解法1

  1. 假设数组第一个值为最小值
  2. 进行一次循环,如果碰到某个值k小于第一个值的,直接返回k
  3. 否则返回数组第一个值
 class Solution {
     public int findMin(int[] nums) {
         for (int i = 1; i < nums.length; i++) {
             if (nums[i] < nums[0]) {
                 return nums[i];
             }
         }
         return nums[0];
     }
 }

寻找旋转排序数组中的最小值Ⅱ

解法1

  1. sort排序
  2. 返回0号元素
class Solution {
    public int findMin(int[] nums) {
        Arrays.sort(nums);
        return nums[0];
    }
}

解法2

二分查找

class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == nums[right]) {
                right--;
            } else if (nums[mid] < nums[right]) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return nums[left];
    }
}

解法3

复用寻找旋转排序数组中的最小值的解法(上一题)

class Solution {
    public int findMin(int[] nums) {
        for (int i = 1; i < nums.length; i++) {
            if (nums[i] < nums[0]) {
                return nums[i];
            }
        }
        return nums[0];
    }
}

两个数组的交集

解法1

  1. new两个hashset,第一个hashset存放第一个数组
  2. 第二个数组存放进第二个hashset,放入之前判断是否在第一个hashset中存在(hashset不可重复)
  3. 将第二个hashset放入数组返回
 class Solution {
     public int[] intersection(int[] nums1, int[] nums2) {
         Set<Integer> set1 = new HashSet<>();
         Set<Integer> set2 = new HashSet<>();
         for (Integer number : nums1) {
             set1.add(number);
         }
         for (Integer number : nums2) {
             if (set1.contains(number)) {
                 set2.add(number);
             }
         }
         int[] result = new int[set2.size()];
         int count = 0;
         for (Integer number : set2) {
             result[count] = number;
             count++;
         }
         return result;
     }
 }

两个数组的交集Ⅱ

解法1

暴力循环

class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        int index = 0;
        for (int i = 0; i < nums1.length; i++) {
            for (int j = 0; j < nums2.length; j++){
                if (nums1[i] == nums2[j]) {
                    nums1[index] = nums1[i];
                    nums2[j] = -1;
                    index++;
                    break;
                }
            }
        }
        int[] result = new int[index];
        for (int i = 0; i < index; i++) {
            result[i] = nums1[i];
        }
        return result;
    }
}

解法2

  1. 新建一个数组
  2. 遍历list1,对于list1中的每个元素,都在新数组中对应下标自增1
  3. 新建一个result数组用于存放结果
  4. 遍历list2,对于list2中每个元素,对应新数组中的下标不为0的元素存放入result数组,并把新数组中元素自减1
  5. 用Arrays的copyOfRange方法返回结果效率较高
class Solution {
    public int[] intersect(int[] nums1, int[] nums2) {
        int[] account = new int[1001];
        for (int i = 0; i < nums1.length; i++) {
            account[nums1[i]]++;
        }
        int[] result = new int[Math.min(nums1.length, nums2.length)];
        int index = 0;
        for (int i = 0; i < nums2.length; i++) {
            int val = nums2[i];
            if(account[val] > 0) {
                account[val]--;
                result[index] = val;
                index++;
            }
        }
        return Arrays.copyOfRange(result, 0, index);
    }
}

两数之和Ⅱ-输入有序数组

解法1

暴力循环

  1. 遍历整个数组
  2. 如果两个数相加等于target,返回下标
class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int[] result = new int[2];
        for (int i = 0; i < numbers.length; i++) {
            for (int j = i + 1; j < numbers.length; j++) {
                if (numbers[i] + numbers[j] == target) {
                    result[0] = i + 1;
                    result[1] = j + 1;
                    return result;
                }
            }
        }
        return result;
    }
}

解法2

双指针

  1. 由于数组是从小到大排列的
  2. 一个指针指向数组头,零一个指针指向数组末尾,进行循环判断,两个指针指向的数的和是否等于target
    • 如果相等,则返回下标
    • 如果大于target,则后面的指针-1
    • 如果小于target,则前面的指针+1
class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int i = 0;
        int j = numbers.length - 1;
        int[] result = new int[2];
        while (i < j) {
            if (numbers[i] + numbers[j] == target) {
                result[0] = i + 1;
                result[1] = j + 1;
                return result;
            } else if (numbers[i] + numbers[j] > target) {
                j--;
            } else {
                i++;
            }
        }
        return result;
    }
}

寻找重复数

解法1

  1. HashSet
  2. 遍历数组,将所有数放入HashSet
  3. 如果add返回为false,return这个数
class Solution {
    public int findDuplicate(int[] nums) {
        Set<Integer> set = new HashSet<>();
        for (int i = 0; i < nums.length; i++) {
            if (!set.add(nums[i])) {
                return nums[i];
            }
        }
        return 0;
    }
}

解法2

  1. 思路同解法1
  2. 用int数组替换set提高速度
class Solution {
    public int findDuplicate(int[] nums) {
        int[] acc = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            if (acc[nums[i]] == 1) {
                return nums[i];
            }
            acc[nums[i]]++;
        }
        return 0;
    }
}

解法3

  1. 环形链表的解法
  2. 把数组当作链表,找环
  3. 思路见环形链表Ⅱ(链表)
class Solution {
    public int findDuplicate(int[] nums) {
        int slow = 0;
        int fast = 0;
        do {
            slow = nums[slow];
            fast = nums[nums[fast]];
        } while(slow != fast);
        slow = 0;
        while (slow != fast) {
            slow = nums[slow];
            fast = nums[fast];
        }
        return slow;
    }
}

寻找两个正序数组的中位数

解法1

  1. 暴力合并
  2. 排序
  3. 取中位数
class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int[] num = new int[nums1.length + nums2.length];
        for (int i = 0; i < nums1.length; i++) {
            num[i] = nums1[i];
        }
        for (int i = nums1.length; i < nums1.length + nums2.length; i++) {
            num[i] = nums2[i - nums1.length];
        }
        Arrays.sort(num);
        if (num.length % 2 == 1) {
            return num[num.length / 2];
        }
        return ((double)num[num.length / 2] + num[num.length / 2 - 1]) / 2;
    }
}

解法2

官解

寻找两个有序数组的中位数 - 寻找两个正序数组的中位数 - 力扣(LeetCode)

class Solution {
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        if (nums1.length > nums2.length) {
            return findMedianSortedArrays(nums2, nums1);
        }
        int m = nums1.length;
        int n = nums2.length;
        int left = 0;
        int right = nums1.length;
        int mid1 = 0;
        int mid2 = 0;
        while (left <= right) {
            int i = (left + right) / 2;
            int j = (m + n + 1) / 2 - i;
            int nums_i_ = (i == 0 ? Integer.MIN_VALUE : nums1[i - 1]);
            int nums_i = (i == m ? Integer.MAX_VALUE : nums1[i]);
            int nums_j_ = (j == 0 ? Integer.MIN_VALUE : nums2[j - 1]);
            int nums_j = (j == n ? Integer.MAX_VALUE : nums2[j]);
            if (nums_i_ <= nums_j) {
                mid1 = Math.max(nums_i_, nums_j_);
                mid2 = Math.min(nums_i, nums_j);
                left = i + 1;
            } else {
                right = i - 1;
            }
        }
        return (m + n) % 2 == 0 ? (mid1 + mid2) / 2.0 : mid1;
    }
}

找出第k小的距离对

解法1

宫水三叶大佬的解答

【宫水三叶の特别篇】二分答案 + 双指针 check 运用题 - 找出第 K 小的数对距离 - 力扣(LeetCode)

class Solution {
    public int smallestDistancePair(int[] nums, int k) {
        Arrays.sort(nums);
        int left = 0;
        int right = 1000000;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (check(nums, mid) >= k) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return right;
    }
    public int check (int[] nums, int x) {
        int n = nums.length;
        int result = 0;
        for (int i = 0, j = 1; i < n; i++) {
            while (j < n && nums[j] - nums[i] <= x) {
                j++;
            }
            result += j - i - 1;
        }
        return result;
    }
}

分割数组的最大值

解法1

官解

分割数组的最大值 - 分割数组的最大值 - 力扣(LeetCode)

class Solution {
    public int splitArray(int[] nums, int k) {
        int left = 0;
        int right = 0;
        for (int i = 0; i < nums.length; i++) {
            right += nums[i];
            if (left < nums[i]) {
                left = nums[i];
            }
        }
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (check(nums, mid, k)) {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
    public boolean check (int[] nums, int x, int m) {
        int sum = 0;
        int cnt = 1;
        for (int i = 0; i < nums.length; i++) {
            if (sum + nums[i] > x) {
                cnt++;
                sum = nums[i];
            } else {
                sum += nums[i];
            }
        }
        return cnt <= m;
    }
}