按照代码随想录进行学习数组部分:(建议分两天学习)
一、数组理论基础
二、LeetCode 704. 二分查找
三、LeetCode 27. 移除元素
四、LeetCode 977.有序数组的平方
五、LeetCode 209.长度最小的子数组
六、LeetCode 59.螺旋矩阵II
七、数组总结
文章链接
数组是存放在连续内存空间上的相同类型数据的集合。
下标索引的方式获取到下标下对应的数据。
特点:
1.下标都是从0开始的。
2.内存空间的地址是连续的。
是连续的,所以我们在删除或者增添元素的时候,就难免要移动其他元素的地址;
是连续的,所以数组的元素是不能删的,只能覆盖。
文章链接
手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找
熟悉根据 “左闭右开”,“左闭右闭” 两种区间规则 写出来的二分法。
其实是一种定边界的思想,能够很好的帮助我们确定<还是<=,以及+1或者-1。
定了一种思想,就从一而终。
【左闭右开】
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
let left= 0;
let right = nums.length;
while (left < right) {
let middleIndex = left + (right - left >> 1);
let middle = nums[middleIndex];
if (middle < target) {
left = middleIndex + 1;
} else if (middle > target) {
right = middleIndex;
} else {
return middleIndex;
}
}
return -1;
};
【左闭右闭】
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var search = function(nums, target) {
let left= 0;
let right = nums.length - 1;
while (left <= right) {
let middleIndex = left + (right - left >> 1);
let middle = nums[middleIndex];
if (middle < target) {
left = middleIndex + 1;
} else if (middle > target) {
right = middleIndex - 1;
} else {
return middleIndex;
}
}
return -1;
};
文章讲解
数组中移除元素并不容易! | LeetCode:27. 移除元素
题目比较简单,但是比较考验对数组底层原理理论的理解。
其实数组物理空间是无法删除的,只是后一位移动覆盖。
题目建议: 暴力的解法,可以锻炼一下我们的代码实现能力,建议先把暴力写法写一遍。
双指针法 是本题的精髓。 库函数是不推荐在学算法时候使用的。
【暴力解】
一层for循环用于遍历数组元素
二层for循环用于更新数组
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function(nums, val) {
let size = nums.length;
for (let i = 0; i < size; i++) {
if (nums[i] === val) {
for (let j = i + 1; j < size; j++) {
nums[j-1] = nums[j];
}
i--;
size--;
}
}
return size;
};
【双指针】
快慢指针,均是从0开始
slow慢指针:用于指向更新后的数组的下标的位置(“坑”)
quick快指针:用于寻找新数组中的元素(“萝卜”)
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function(nums, val) {
let slow = 0;
for (let quick = 0; quick < nums.length; quick++) {
if (nums[quick] != val) {
nums[slow] = nums[quick];
slow++;
}
}
return slow;
};
文章讲解
数组有序,但是数组中有负数,可能负数平方之后就成最大数了。但是原数组按原来的位置每个数平方之后,大数一定在两边(也就是由大到小的趋势一定是向中间合拢的)。
所以可以用 双指针O(n) 的解法。 i: 起始位置,j: 终止位置。
i <= j 循环就可以一直继续下去:(i = j时候指向的元素也是需要更新的)
i++ 或 j- - 是有条件的,也就是下标对应元素较大的那一方,放入最后结果数组中后,再对应那一方i++或j- -
res数组是从最后往前排的(后大前小——从大到小排序),所以res的下标是一直- -的
双指针法经典题目 | LeetCode:977.有序数组的平方
/**
* @param {number[]} nums
* @return {number[]}
*/
var sortedSquares = function(nums) {
let i = 0;
let j = nums.length - 1;
let k = nums.length - 1;
let res = new Array(nums.length).fill(0);
while (i <= j) {
let left = nums[i] * nums[i];
let right = nums[j] * nums[j];
if (left < right) {
res[k--] = right;
j--;
} else {
res[k--] = left;
i++;
}
// 如果直接没在res[k--],也可以在这k--
}
return res;
};
输入:n个正整数的数组。正整数target
输出: 数组中和>=target的长度最小的连续子数组[num1, nums2…],并返回其长度(若不存在子数组则返回0)
数组 | target | 输出 | 解释 |
---|---|---|---|
[2, 3, 1, 2, 4, 3] | 7 | 2 | 子数组是[4. 3] |
[1, 4, 4] | 4 | 1 | 子数组是[4] |
[1, 1, 1, 1, 1, 1, 1, 8] | 11 | 0 | 无子数组 |
题目建议: 本题关键在于理解滑动窗口,这个滑动窗口看文字讲解还挺难理解的,建议大家先看视频讲解。
拿下滑动窗口! | LeetCode 209 长度最小的子数组
文章讲解
滑动窗口O(n)(本质上还是双指针)
精髓在于:如何移动起始位置
外层循环不断移动的是 右指针的位置right
而left指针在动态不断移动(如何移动呢?一旦符合取res数组长度的条件:sum >= target,更新最后结果后,就可以left++了)
res数组的长度,最开始是最大值,在左指针的不断移动下逐渐缩小
/**
* @param {number} target
* @param {number[]} nums
* @return {number}
*/
var minSubArrayLen = function(target, nums) {
let left, right;
left = right = 0;
let len = nums.length;
let sum = 0; // 滑动窗口中元素和
let resLen = Infinity;
while(right < len) {
sum += nums[right];
while (sum >= target) {
resLen = Math.min(right - left + 1, resLen);
sum -= nums[left]; // left向右移动前,sum要减去一位
left++;
}
right++;
}
return resLen === Infinity ? 0 : resLen;
};
文章讲解
边界处理:想想二分搜索中的“区间”思想(循环不变量)(不变量:处理规则(左闭右开/左闭右闭))
最外层while循环,每一条边用一个遍历for。定义好边界。
以下代码示例每一个for循环:左闭右开(只处理左边界点,不处理右边界点)
输入 | 输出 |
---|---|
3 | [[1, 2, 3], [8, 9, 4], [7, 6, 5]] |
1 | [[1]] |
一入循环深似海 | LeetCode:59.螺旋矩阵II
题目建议: 本题关键还是在转圈的逻辑,在二分搜索中提到的区间定义,在这里又用上了。
/**
* @param {number} n
* @return {number[][]}
*/
var generateMatrix = function(n) {
let nums = new Array(n).fill(0).map(() => new Array(n).fill(0));// 最后的数组
let count = 1; // 遍历时候计数,也是为了给每个对应位置赋值
let startX = 0; // 每一圈的起始位置都要变,每一圈++
let startY = 0; // 每一圈的终止位置都要变,每一圈++
let offset = 1; // 终止位置要减去的数量,,每一圈++,终止位置就是n - offset
let loop = Math.floor(n/2); // 旋转圈数
let mid = Math.floor(n/2); // 中间位置
while (loop--) {
let row = startX, col = startY;
// 上行从左到右(左闭右开)
for (;col < startY + n - offset; col++) {
nums[row][col] = count++;
}
// 右列从上到下(左闭右开)
for (;row < startX + n - offset; row++) {
nums[row][col] = count++;
}
// 下行从右到左(左闭右开)
for (;col > startY; col--) {
nums[row][col] = count++;
}
// 左列做下到上(左闭右开)
for (;row> startX; row--) {
nums[row][col] = count++;
}
startX++;
startY++;
offset += 2;
}
// 如果n是奇数,最中间的数其实也是确定的
if (n % 2 === 1) {
nums[mid][mid] = count;
}
return nums;
};
1、数组特点
2、常见算法题
(1)二分:
常见场景关键词:搜索、查找、位置、有序数组、平方数、平方根、
注意:边界思想(左闭右开、左闭右闭)
(2)双指针:
①从0开始-快慢指针
常见场景关键词:移除、删除、移动、
②起始终止(区间)-滑动窗口
for循环中的j是终止位置
如何移动起始位置?移动时机(一旦符合最终结果的条件),取相应区间
常见场景关键词:有序、平方、子数组
(3)模拟
常见场景关键词:螺旋矩阵