# 二分法

# 思想

针对有序数组进行查找时,优先考虑二分法。

# 题目

# 输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素 (opens new window)

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。

# 思路

  1. 原数组为旋转数组,所以分界点前后都是有序的
  2. 进行二分查找,注意因为找最小值,high 赋值时应该从 mid 开始取,mid 可能是最小值
  3. (right + left) / 2 === left + (right - left) / 2,左边的项需要考虑溢出问题,右边则利用减法规避

# 答案

function minArray(numbers: number[]): number {
  const n = numbers.length;
  let left = 0;
  let right = n - 1;

  while (left < right) {
    const mid = Math.floor(left + (right - left) / 2);
    if (numbers[mid] > numbers[right]) {
      left = mid + 1;
    } else if (numbers[mid] < numbers[right]) {
      right = mid;
    } else {
      right--;
    }
  }

  return numbers[left];
}

# 统计一个数字在排序数组中出现的次数 (opens new window)

统计一个数字在排序数组中出现的次数。

# 思路

  1. 先找出目标数字在数组中的位置
  2. 因为是排序数组,所以从目标位置开始从两边遍历,只要一不相等就可以退出循环

# 答案

function search(nums: number[], target: number): number {
  let count = 0;
  const n = nums.length;
  let left = 0;
  let right = n - 1;

  while (left < right) {
    const mid = Math.floor((left + right) / 2);
    if (nums[mid] === target) {
      left = mid;
      break;
    } else if (nums[mid] < target) {
      left = mid + 1;
    } else {
      right = mid - 1;
    }
  }

  if (nums[left] !== target) return 0;

  let cl = left - 1;
  let cr = left + 1;
  count++;
  while (true) {
    if (nums[cl--] === target) count++;
    else break;
  }

  while (true) {
    if (nums[cr++] === target) count++;
    else break;
  }

  return count;
}

# 0~n-1中缺失的数字 (opens new window)

一个长度为 n-1 的递增排序数组中所有数字都是唯一的,并且每个数字都在范围 0 ~ n-1 之内。在范围 0~n-1 内的 n 个数字中有且只有一个数字不在该数组,请找出这个数字。

# 思路

  • left 指向0,right 指向最后一个元素
  • 计算中间坐标 mid:
    • 如果 mid = nums[mid],说明 [0, mid] 范围内不缺失数字,left 更新为 mid + 1
    • 如果 mid < nums[mid],说明 [mid, right] 范围内不缺失数字,right 更新为 mid - 1
  • 检查 left 是否小于等于 mid,若成立,返回第二步,否则继续往下执行
  • 返回 left

# 答案

function missingNumber(nums: number[]) {
  let left = 0, right = nums.length - 1
  while(left <= right) {
    const mid = Math.floor((left + right) / 2)
    if(mid === nums[mid]) {
      left = mid + 1
    } else {
      right = mid -1
    }
  }
  return left
}

# 最长上升子序列 (opens new window)

给定一个无序的整数数组,找到其中最长上升子序列的长度。

示例:

输入:[10, 9, 2, 5, 3, 7, 101, 18]

输出:4

解释:最长上升的子序列是 [2, 3, 7, 101],它的长度是 4

# 思路

  1. 维护一个子序列存放当前的上升序列
  2. 将当前数与子序列最大值比较,如果比最大值大直接加入队尾,如果更新则找一个合适的位置替换当前的元素

# 答案

function lengthOfLIS(nums: number[]): number {
  const n = nums.length
  if(n <= 1) return n
  
  const tail = [nums[0]]
  for(let i = 0; i < n; i++) {
    if(nums[i] > tail[tail.length - 1]) {
      tail.push(nums[i]) // 思路 1
    } else {
      let left = 0
      let right = tail.length - 1
      while(left < right) {
        let mid = Math.floor((left + right) / 2)
        if(tail[mid] < nums[i]) {
          left = mid + 1
        } else {
          right = mid
        }
      }
      tail[left] = nums[i]
    }
  }
  return tail.length
}

# 搜索二维矩阵II (opens new window)

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:

  • 每行的元素从左到右升序排列
  • 每列的元素从上到下升序排列

# 思路

输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

  1. 选取左下角的值作为初始值 key
  2. 如果目标值大于 key,因为是最左边的值(最小),所以 col++
  3. 如果目标值小于 key,那么更小的值只能是上一行,所以 row--

# 答案

function searchMatrix(matrix: number[][], target: number): boolean {
  const rows = matrix.length
  if(rows <= 0) return false
  const cols = matrix[0].length
  if(cols <= 0) return false
  
  let row = rows - 1
  let col = 0
  while(row >= 0 && col < cols) {
    if(matrix[row][col] > target) {
      row--
    } else if(matrix[row][col] < target) {
      col++
    } else return true
  }
  return false
}

# Pow(x, n) (opens new window)

实现 pow(x, n) ,即计算 x 的 n 次幂函数

function myPow(x: number, n: number): number {
  if(!x) return 0
  if(n < 0) return 1 / myPow(x, -n)
  if(n % 2) return x * myPow(x, n - 1)
  return myPow(x * x, n / 2)
}

# 求交集 (opens new window)

function intersection(nums1: number[], nums2: number[]): number[] {
  let res = new Set<number>();
  nums2.sort((a, b) => a - b);

  function binarySearch(arr: number[], val: number): boolean {
    let left = 0;
    let right = arr.length - 1;

    while (left <= right) {
      let mid = Math.floor((left + right) / 2);
      if (arr[mid] === val) return true;
      else if (arr[mid] > val) right = mid - 1;
      else left = mid + 1;
    }
    return false;
  }

  for (let i = 0; i < nums1.length; i++) {
    if (binarySearch(nums2, nums1[i])) {
      res.add(nums1[i]);
    }
  }

  return [...res];
}