数组是一种线性结构,是存放在连续内存空间上的相同类型数据的集合
二分查找
二分查找元素
二分查找基础条件:有序数组
704. 二分查找
解法一:把目标元素target定义在一个双闭区间(左闭右闭)
function search(num: number[], target: number) {
// 初始化双闭区间
let i = 0;
let j = num.length - 1;
//循环结束条件 i > j
while (i <= j) {
// 中点索引,向下取整
const mid = Math.floor((j - i) / 2) + i;
if (num[mid] > target) {
// target 在索引[i,mid-1]区间
j = mid - 1;
} else if (num[mid] < target) {
// target 在索引[mid+1,j]区间
i = mid + 1;
} else {
return mid;
}
}
return -1;
}
解法二:把目标元素taregt定义在(左闭右开)区间
function search(num: number[], target: number) {
// 初始化左闭右开区间,j就要初始化为num.length,因为num[j]不在查找范围
let i = 0;
let j = num.length;
//循环结束条件 i >= j
while (i < j) {
// 中点索引,向下取整
const mid = Math.floor((j - i) / 2) + i;
if (num[mid] > target) {
// target 在索引[i,mid)区间
j = mid;
} else if (num[mid] < target) {
// target 在索引[mid+1,j)区间
i = mid + 1;
} else {
return mid;
}
}
return -1;
}
69. x 的平方根
var mySqrt = function (x) {
let l = 1;
let r = x;
// 除了0外整数x的平方根一定是在1到x的范围内
while (l <= r) {
const m = Math.floor((r - l) / 2) + l;
if (m <= x / m) {
// x的平方根大于等于m
if (m + 1 > x / (m + 1)) {
// x的平方根小于m+1,此时确定m就是x的平方根
return m;
}
// x的平方根大于m+1,在[m+1,r]之间
l = m + 1;
} else {
// x的平方根小于m,在[l,m-1]之间
r = m - 1;
}
}
return 0;
};
367. 有效的完全平方数
var isPerfectSquare = function (x) {
let l = 1;
let r = x;
// 除了0外整数x的平方根一定是在1到x的范围内
while (l <= r) {
const m = Math.floor((r - l) / 2) + l;
if (m < x / m) {
// x的平方根大于m,在[m+1,r]之间
l = m + 1;
} else if (m > x / m) {
// x的平方根小于m,在[l,m-1]之间
r = m - 1;
} else {
return true;
}
}
return false;
};
二分查找插入点
35. 搜索插入位置 (无重复元素)
解法一:闭区间
function searchInsert(num: number[], target: number) {
// 初始化双闭区间
let i = 0;
let j = num.length - 1;
//循环结束条件 i > j
while (i <= j) {
// 中点索引,向下取整
const mid = Math.floor((j - i) / 2) + i;
if (num[mid] > target) {
// target 在索引[i,mid-1]区间
// j向小于等于 target 的元素靠近
j = mid - 1;
} else if (num[mid] < target) {
// target 在索引[mid+1,j]区间
// i向大于等于 target 的元素靠近
i = mid + 1;
} else {
// 找到target,返回索引
return mid;
}
}
// 此时i>j,i指向首个大于等于target的元素,j指向首个小于target的元素
// target 应该插入i或者j+1这个位置
return i;
}
解法二:左闭右开区间
function searchInsert(num: number[], target: number) {
// 初始化左闭右开区间
let i = 0;
let j = num.length;
//循环结束条件 i >= j
while (i < j) {
// 中点索引,向下取整
const mid = Math.floor((j - i) / 2) + i;
if (num[mid] > target) {
// target 在索引[i,mid)区间
j = mid;
} else if (num[mid] < target) {
// target 在索引[mid+1,j)区间
i = mid + 1;
} else {
// 找到 target ,返回插入点
return mid;
}
}
// 此时i===j,i指向首个大于等于target的元素,j指向首个小于等于target的元素
// target 应该插入i或者j这个位置
return j;
}
有重复元素
把上题改为有重复元素的升序排列数组,插入按顺序的最左边。
function searchLeftInsert(num: number[], target: number) {
// 初始化双闭区间
let i = 0;
let j = num.length - 1;
// 循环结束条件 i > j
while (i <= j) {
// 中点索引,向下取整
const mid = Math.floor((j - i) / 2) + i;
if (num[mid] > target) {
// target 在索引[i,mid-1]区间
j = mid - 1;
} else if (num[mid] < target) {
// target 在索引[mid+1,j]区间
i = mid + 1;
} else {
// 当num[mid]===target时,说明小于target的元素在区间[i,m−1]中,
// 因此采用j=m−1来缩小区间,从而使指针j向小于target的元素靠近
j = mid - 1;
}
}
// 循环完成后,i指向最左边的 target,j指向首个小于target的元素
return i;
}
二分查找边界
34. 在排序数组中查找元素的第一个和最后一个位置
function searchRange (num, target) {
// 复用上面查找有重复元素的左边界
const l = searchLeftInsert(num, target);
// l指向最左边的target
// l===num.length说明target大于num中所有元素
// num[l]!==target 说明num中没有与target相等的元素
if (l === num.length || num[l] !== target) return [-1, -1];
//查找target+1的左边界
const r = getLeftBorder(num, target + 1);
// target的右边界
const j = r - 1;
// j===-1,target小于num中所有元素
if (j === -1 || num[j] !== target) return [-1, -1];
return [l, j];
};
哈希:空间换时间
1. 两数之和
function twoSum(nums, target) {
const map = new Map();
for (let i = 0; i < nums.length; i++) {
const index = map.get(target - nums[i]);
// 注意索引0;所以条件要不等于undefined
if (index !== undefined) {
return [index, i];
} else {
map.set(nums[i], i);
}
}
}
1365. 有多少小于当前数字的数字
// 使用哈希
var smallerNumbersThanCurrent = function (nums) {
//记录数字 nums[i] 有多少个比它小的数字
const map = new Map();
const res = [...nums];
res.sort((a, b) => a - b);
for (let i = 0; i < res.length; i++) {
// 遇到相同的不需要处理
if (!map.has(res[i])) {
map.set(res[i], i);
}
}
// 此时map里保存小于nums[i]这个数值的个数
for (let i = 0; i < res.length; i++) {
res[i] = map.get(nums[i]);
}
return res;
};
1207. 独一无二的出现次数
var uniqueOccurrences = function (arr) {
const map = new Map();
for (let i = 0; i < arr.length; i++) {
map.set(arr[i], (map.get(arr[i]) ?? 0) + 1);
}
// 去重后还是否数量一致
return map.size === new Set(map.values()).size;
};
双指针
27. 移除元素
var removeElement = function (nums, val) {
// 左指针l指向下一个赋值元素
let l = 0;
// 右指针r指向当前元素
for (let r = 0; r < nums.length; r++) {
// 如果r指向的元素不等于val,将r指向的元素复制到l位置,然后将左右指针同时右移
if (nums[r] !== val) {
nums[l] = nums[r];
l++;
}
// 如果等于,l不动,r指向下一位
}
return l;
};
26. 删除有序数组中的重复项
var removeDuplicates = function (nums) {
if (!nums.length) return 0;
// 慢指针s指向数组第一位
let s = 0;
// 快指针f指向数组第二位
for (let f = 1; f < nums.length; f++) {
if (nums[s] !== nums[f]) {
// 快慢指针位置不等时,
// 慢指针后移一位,快指针的值赋值给慢指针
nums[++s] = nums[f];
}
}
return s + 1;
};
283. 移动零
var moveZeroes = function (nums) {
// 慢指针指向要更新的位置
let s = 0;
// 快指针寻找不为0的元素
for (let f = 0; f < nums.length; f++) {
if (nums[f] !== 0) {
if (s !== f) {
[nums[s], nums[f]] = [nums[f], nums[s]];
}
// 指向下一个更新位置
s++;
}
}
};
941. 有效的山脉数组
var validMountainArray = function (arr) {
if (arr.length < 3) {
return false;
}
let l = 0;
let r = arr.length - 1;
// 左指针往右移到最高
while (arr[l] < arr[l + 1]) {
l++;
}
// 右指针往左移到最高
while (arr[r - 1] > arr[r]) {
r--;
}
// 顶峰相见,并且不是单调,那就是山脉
if (l === r && l !== 0 && r !== arr.length - 1) {
return true;
}
return false;
};
977. 有序数组的平方
var sortedSquares = function (nums) {
const res = Array(nums.length).fill(0);
const n = nums.length;
// 指针l指向开头
let l = 0;
// 指针r指向结尾
let r = n - 1;
// 指针k指向res结尾
let k = n - 1;
while (l <= r) {
const left = nums[l] * nums[l];
const right = nums[r] * nums[r];
if (left < right) {
res[k--] = right;
r--;
} else {
res[k--] = left;
l++;
}
}
return res;
};
844. 比较含退格的字符串
var backspaceCompare = function (s, t) {
let i = s.length - 1;
let j = t.length - 1;
// "#"个数
let sCount = 0;
let tCount = 0;
// 比较s和t 用"#"出现的个数,并退格后,其余字符是否相等
while (i >= 0 || j >= 0) {
// 循环s
// i 指向不是"#"且已经退格后的位置
while (i >= 0) {
if (s[i] === "#") {
sCount++;
i--;
} else if (sCount > 0) {
sCount--;
i--;
} else {
break;
}
}
// 循环t
// j 指向不是"#"且已经退格后的位置
while (j >= 0) {
if (t[j] === "#") {
tCount++;
j--;
} else if (tCount > 0) {
tCount--;
j--;
} else {
break;
}
}
if (s[i] !== t[j]) {
return false;
}
// 如果s和t循环退格后,s[i]===t[j],继续比较
i--;
j--;
}
return true;
};
滑动窗口(双指针法一种)
209. 长度最小的子数组
var minSubArrayLen = function (target, nums) {
let minLen = Infinity; // 最小长度
let l = 0; // 左指针
let r = 0; // 右指针
let sum = 0;
while (r < nums.length) {
sum += nums[r];
while (sum >= target) {
minLen = Math.min(minLen, r - l + 1);
sum -= nums[l];
// 左指针移动,看是否有更小的 minLen
l++;
}
// 右指针移动
r++;
}
return minLen === Infinity ? 0 : minLen;
};
904. 水果成篮
var totalFruit = function (fruits) {
// map 存储 fruits 中水果种类和出现次数
const map = new Map();
// 左指针
let l = 0;
let maxCount = 0;
// 右指针 r
for (let r = 0; r < fruits.length; r++) {
// 存 fruits 中水果种类和出现次数
map.set(fruits[r], (map.get(fruits[r]) || 0) + 1);
// 只有2个篮子,当种类超过 2
while (map.size > 2) {
// 左指针的水果种类出现次数减 1
map.set(fruits[l], map.get(fruits[l]) - 1);
if (map.get[fruits[l]] === 0) {
// 出现次数为0就删掉
map.delete(fruits[l]);
}
// 移动左指针
l++;
}
maxCount = Math.max(maxCount, r - l + 1);
}
return maxCount;
};
76. 最小覆盖子串
var minWindow = function (s, t) {
//左边界
let l = 0;
//右边界
let r = 0;
//存储t的每个目标字符所需数
const need = new Map();
for (let c of t) {
need.set(c, (need.get(c) || 0) + 1);
}
//目标字符种类数
let types = need.size;
//符合的最小子串
let res = "";
//右指针向右扩展窗口
while (r < s.length) {
const c = s[r];
if (need.has(c)) {
// 右指针找到目标字符,需求数减1
need.set(c, need.get(c) - 1);
// 目标字符需求数为0 ,就不需要这个字符种类了
if (need.get(c) === 0) {
types -= 1;
}
}
// 如果窗口包含所有目标字符,缩小窗口找是否有更小的目标子串
while (types === 0) {
const newRes = s.substring(l, r + 1);
// 更新目标子串
if (!res || newRes.length < res.length) {
res = newRes;
}
const c = s[l];
if (need.has(c)) {
//左指针找到目标字符,但由于缩小窗口,目标字符数增加1
need.set(c, need.get(c) + 1);
// 目标字符需求数大于0,目标字符种类数增加1
if (need.get(c) === 1) {
types += 1;
}
}
l += 1;
}
r += 1;
}
return res;
};
其他
189. 轮转数组
var rotate = function (nums, k) {
function reverse(l, r) {
while (l < r) {
[nums[l], nums[r]] = [nums[r], nums[l]];
l++;
r--;
}
}
// k如果大于size
k %= nums.length;
if (k) {
reverse(0, nums.length - 1);
reverse(0, k - 1);
reverse(k, nums.length - 1);
}
};
724. 寻找数组的中心下标
var pivotIndex = function (nums) {
const sum = nums.reduce((a, b) => a + b);
// 中心索引左半和 中心索引右半和
let leftSum = 0;
let rightSum = 0;
for (let i = 0; i < nums.length; i++) {
leftSum += nums[i];
// leftSum 里面已经有 nums[i]
rightSum = sum - leftSum + nums[i];
if (leftSum === rightSum) {
return i;
}
}
return -1;
};
922. 按奇偶排序数组 II
var sortArrayByParityII = function (nums) {
const res = [];
let even = 0;
let odd = 1;
for (let i = 0; i < nums.length; i++) {
if (!(nums[i] % 2)) {
res[even] = nums[i];
even += 2;
} else {
res[odd] = nums[i];
odd += 2;
}
}
return res;
};
59. 螺旋矩阵 II
var generateMatrix = function (n) {
const matrix = Array(n)
.fill(0)
.map(() => []);
let t = (l = 0);
let b = (r = n - 1);
let count = 1;
while (true) {
//从左到右
for (let i = l; i <= r; i++) {
matrix[t][i] = count++;
}
if (++t > b) {
// 第一层遍历完了上边界应自增1后与下边界比较若大于下边界则说明遍历完毕
break;
}
//从上到下
for (let i = t; i <= b; i++) {
matrix[i][r] = count++;
}
if (--r < l) {
// 最后一列遍历完了右边界应自减1后与左边界比较若小于左边界则说明遍历完毕
break;
}
//从右到左
for (let i = r; i >= l; i--) {
matrix[b][i] = count++;
}
if (--b < t) {
//最后一层遍历完了下边界应自减1后与上边界比较若小于上边界则说明遍历完毕
break;
}
//从下到上
for (let i = b; i >= t; i--) {
matrix[i][l] = count++;
}
if (++l > r) {
// 第一行遍历完了左边界应自增1后与右边界比较若大于右边界则说明遍历完毕
break;
}
}
return matrix;
};
54. 螺旋矩阵
var spiralOrder = function (matrix) {
if (matrix.length === 0) return [];
const res = [];
//上边
let t = 0;
//右列
let r = matrix[0].length - 1;
//下边
let b = matrix.length - 1;
//左列
let l = 0;
while (t <= b && l <= r) {
// 从左到右
for (let i = l; i <= r; i++) {
res.push(matrix[t][i]);
}
t++;
// 从上到下
for (let i = t; i <= b; i++) {
res.push(matrix[i][r]);
}
r--;
// 不是n*n的情况下
if (t > b || l > r) {
break;
}
// 从右到左
for (let i = r; i >= l; i--) {
res.push(matrix[b][i]);
}
b--;
// 从下到上
for (let i = b; i >= t; i--) {
res.push(matrix[i][l]);
}
l++;
}
return res;
};
LCR 146. 螺旋遍历二维数组
var spiralArray = function (matrix) {
if (matrix.length === 0) return [];
const res = [];
//上边
let t = 0;
//右列
let r = matrix[0].length - 1;
//下边
let b = matrix.length - 1;
//左列
let l = 0;
while (t <= b && l <= r) {
// 从左到右
for (let i = l; i <= r; i++) {
res.push(matrix[t][i]);
}
t++;
// 从上到下
for (let i = t; i <= b; i++) {
res.push(matrix[i][r]);
}
r--;
// 不是n*n的情况下
if (t > b || l > r) {
break;
}
// 从右到左
for (let i = r; i >= l; i--) {
res.push(matrix[b][i]);
}
b--;
// 从下到上
for (let i = b; i >= t; i--) {
res.push(matrix[i][l]);
}
l++;
}
return res;
};