文章目录
2024.7.17Day1和2024.7.18两天完成数组部分。题目有:二分查找、移除元素、有序数组的平方、长度最小的子数组、螺旋矩阵。
1、数组的基础知识
- 数组是存放在连续内存空间上的相同类型数据的集合,数组下标都是从0开始的,数组内存空间的地址是连续的。
- 数组的元素是不能删的,只能覆盖
2、 题目
2.1 二分查找
思路:
使用二分查找的前提:数组有序、无重复元素
定义区间left、right、mid,通过不断比较目标值和mid值大小来缩小范围区间,每次比较后更新区间值。在写法的过程中需要注意保持区间的不变性,如果采用写法1,即[left,right],整个过程需要保持不变。区间影响left和right的取值,看代码应该清楚。
写法1(左闭右闭):因为定义target在**[left, right]区间,(1)while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=(2)if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1。
写法1(左闭右开):为定义target在[left, right)**区间,(1)while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的;(2)if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
题目:
答案:
使用JS版本
(版本一)左闭右闭区间 [left, right]
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
// right是数组最后一个数的下标,num[right]在查找范围内,是左闭右闭区间
let mid, left = 0, right = nums.length - 1;
// 当left=right时,由于nums[right]在查找范围内,所以要包括此情况
while (left <= right) {
// 位运算 + 防止大数溢出
mid = left + ((right - left) >> 1);
// 如果中间数大于目标值,要把中间数排除查找范围,所以右边界更新为mid-1;如果右边界更新为mid,那中间数还在下次查找范围内
if (nums[mid] > target) {
right = mid - 1; // 去左面闭区间寻找
} else if (nums[mid] < target) {
left = mid + 1; // 去右面闭区间寻找
} else {
return mid;
}
}
return -1;
};
(版本二)左闭右开区间 [left, right)
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
// right是数组最后一个数的下标+1,nums[right]不在查找范围内,是左闭右开区间
let mid, left = 0, right = nums.length;
// 当left=right时,由于nums[right]不在查找范围,所以不必包括此情况
while (left < right) {
// 位运算 + 防止大数溢出
mid = left + ((right - left) >> 1);
// 如果中间值大于目标值,中间值不应在下次查找的范围内,但中间值的前一个值应在;
// 由于right本来就不在查找范围内,所以将右边界更新为中间值,如果更新右边界为mid-1则将中间值的前一个值也踢出了下次寻找范围
if (nums[mid] > target) {
right = mid; // 去左区间寻找
} else if (nums[mid] < target) {
left = mid + 1; // 去右区间寻找
} else {
return mid;
}
}
return -1;
};
2.2 移除元素
思路:
题目:
答案:
2.2 移除元素
思路:
本篇主题是数组,记住数组只能覆盖,不能删除。注意到这点就有思路了,首先是暴力求解,两层for循环,一个for循环遍历数组元素 ,第二个for循环覆盖更新数组。
第二种方法重点掌握:快慢指针。本质是通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。
快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
慢指针:指向更新 新数组下标的位置
看下面这张动图应能理解,刚开始快慢指针的索引相同,当快指针所在的元素为删除元素的值时,慢指针索引保持不变,快指针索引位置加一,并赋值给慢指针所在位置。总的来说,fastIndex所在的值与目标值不等,fastIndex++ ,nums[slowIndex] = nums[fastIndex]; slowIndex++ ;fastIndex所在的值与目标值相等,fastIndex++。因此双指针又称“快慢指针”。
题目:
27.移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
答案:
//时间复杂度:O(n)
//空间复杂度:O(1)
var removeElement = (nums, val) => {
let k = 0;
for(let i = 0;i < nums.length;i++){
if(nums[i] != val){
nums[k++] = nums[i]
}
}
return k;
};
2.3 搜索插入位置
题目:
34.搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。
思路:
这个题目分为两步,第一步通过二分查找判断是否存在数组中,第二步不存在则按顺序插入位置。解决这道题的关键思路是理清楚二分查找最后两步left和right的位置关系,通过left和right的位置关系就能确定新元素按顺序插入的位置。
举一个简单的数组,去模拟算法执行的过程,不难发现新元素的顺序位置就是二分查找后的left或者right+1的位置(while循环停止的条件是left>right)
答案:
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
let mid , left = 0, right = nums.length -1;
let p;
while (left <= right){
mid = left + ((right -left)>>1)
if(nums[mid] > target){
right = mid - 1
}else if(nums[mid] < target){
left = mid + 1
}else{
return mid
}
}
return left
};
2.5 有序数组的平方
题目:
977.有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
思路:
暴力求解大家应该很容易想到,可以array.map.sort(funtion(a, b){return a-b})。双指针的做法可以通过一个for循环解决,平方后的大小与原数组绝对值大小相对应,因此分别比较平方后两端的大小即可。使用
答案:
版本一:map+sort
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortedSquares = function(nums) {
var newArr = nums.map(function(item){
return item*item
})
return newArr.sort(function(a, b){
return a-b
})
};
版本二:快慢指针
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortedSquares = function(nums) {
let result = [];
let k = nums.length -1
for(i=0, j=nums.length-1;i<=j;){
//取大的值 result[k]=nums[i]*nums[i] ;k--
if(nums[i]*nums[i] > nums[j]*nums[j]){
result[k--] = nums[i]*nums[i];
i++;
}else{
result[k--] = nums[j]*nums[j];
j--;
}
}
return result
};
2.6 长度最小的子数组
题目:
209.长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
思路:
需要使用到滑动窗口的思想。所谓滑动窗口,就是不断的调节子序列的起始位置和终止位置,从而得出我们要想的结果。
窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于等于s了,窗口就要向前移动了(也就是该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。
答案:
var minSubArrayLen = function(target, nums) {
let start, end
start = end = 0
let sum = 0
let len = nums.length
let ans = Infinity
while(end < len){
sum += nums[end];
while (sum >= target) {
ans = Math.min(ans, end - start + 1);
sum -= nums[start];
start++;
}
end++;
}
return ans === Infinity ? 0 : ans
};
2.2 螺旋矩阵II
题目:
59螺旋矩阵
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix
思路:
这个题目没有特别多的技巧,需要找到每一圈的套路。
需要注意的是,n为奇数时,最后一圈就是一个数字。掌握每一圈的规律,以及每一圈之间的变化(offset,startX,startY)即可。
答案:
var generateMatrix = function(n) {
let startX = startY = 0; // 起始位置
let loop = Math.floor(n/2); // 旋转圈数
let mid = Math.floor(n/2); // 中间位置
let offset = 1; // 控制每一层填充元素个数
let count = 1; // 更新填充数字
let res = new Array(n).fill(0).map(() => new Array(n).fill(0));
while (loop--) {
let row = startX, col = startY;
// 上行从左到右(左闭右开)
for (; col < n - offset; col++) {
res[row][col] = count++;
}
// 右列从上到下(左闭右开)
for (; row < n - offset; row++) {
res[row][col] = count++;
}
// 下行从右到左(左闭右开)
for (; col > startY; col--) {
res[row][col] = count++;
}
// 左列做下到上(左闭右开)
for (; row > startX; row--) {
res[row][col] = count++;
}
// 更新起始位置
startX++;
startY++;
// 更新offset
offset += 1;
}
// 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
if (n % 2 === 1) {
res[mid][mid] = count;
}
return res;
};