跳到主要内容

二分法

思想

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

题目

输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [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];
}

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

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

思路

  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中缺失的数字

一个长度为 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
}

最长上升子序列

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

示例:

输入:[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

编写一个高效的算法来搜索 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)

实现 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)
}

求交集

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];
}