1.定义
顺序存储的顺序表。数组都属于物理结构。
1. 读取元素
直接下标访问,如arr[0]
2. 更新元素
直接下标更新,如arr[0] = 4
3. 插入元素
- 尾部插入:在数组尾部空闲的位置插入 。
- 中间插入:先插入元素,再把插入位置+后面的元素全部后移。
- 超范围插入:先创建新的数组,然后把老的全部插入,再加上新的元素。
4. 删除元素
判断是否越界,没有则把删除的位置元素删除,并且把后面所有元素往前挪动。
2.刷题
26. 删除有序数组中的重复项
给你一个有序数组 nums ,请你 [原地] 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
说明:
为什么返回数值是整数,但输出的答案是数组呢?
请注意,输入数组是以**「引用」**方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
示例 1:
输入: nums = [1,1,2]
输出: 2, nums = [1,2]
解释: 函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
示例 2:
输入: nums = [0,0,1,1,1,2,2,3,3,4]
输出: 5, nums = [0,1,2,3,4]
解释: 函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
答题
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
if(nums.length === 0) {
return 0
}
let fast = 1 //由于都是从1开始才能与前一格开始比较,所以从1开始循环
let slow = 1
while(fast < nums.length ) {
if(nums[fast] !== nums[fast -1]) {
nums[slow] = nums[fast] //先赋值
slow++ //slow指针再移动索引
}
fast++ //每次都移动一格
}
// nums.length = slow 这里应该把nums.length 改为 slow的长度,不如实际原来的尾部数据还是会被保留
return slow
};
80. 删除有序数组中的重复项 II
给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 最多出现两次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
输入: nums = [1,1,1,2,2,3]
输出: 5, nums = [1,1,2,2,3]
解释: 函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 不需要考虑数组中超出新长度后面的元素。
示例 2:
输入: nums = [0,0,1,1,1,1,2,3,3]
输出: 7, nums = [0,0,1,1,2,3,3]
解释: 函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。 不需要考虑数组中超出新长度后面的元素。
答题
/**
* @param {number[]} nums
* @return {number}
*/
// 输入:nums = [1,1,1,2,2,3]
// 输出:5, nums = [1,1,2,2,3]
//快慢指针
var removeDuplicates = function(nums) {
let len = nums.length
if (len <= 2) {//只有两个数字的话,一样不一样都满足,直接返回
return 2
}
let slow = 2
let fast = 2
while(fast < len) { //
if(nums[slow - 2] != nums[fast]) {//从第0个和第2个开始比较,满足则把第2给第1个,然后 slow往前移动,fast是否满足都正常移动一位。
//不管第0个和第1个是否一样,都是按隔两个数字一直判断下去。
// console.log(`当前[${slow - 2}]:${nums[slow - 2]} 与 [${fast}]:${nums[fast]} 不一样,覆盖`)
nums[slow] = nums[fast] // 把最新的不一样值赋值给 当前的slow索引下标值
slow++ //慢指针往前移动一位
}else {
// console.log(`当前[${slow - 2}]:${nums[slow - 2]} 与 [${fast}]:${nums[fast]} 一样,不覆盖`)
}
// console.log("结果nums",nums)
fast++//快指针正常移动
}
return slow
};
//测试代码
removeDuplicates( [1,1,1,2,2,3])
当前[0]:1 与 [2]:1 一样,不覆盖
结果nums [ 1, 1, 1, 2, 2, 3 ]
当前[0]:1 与 [3]:2 不一样,覆盖
结果nums [ 1, 1, 2, 2, 2, 3 ]
当前[1]:1 与 [4]:2 不一样,覆盖
结果nums [ 1, 1, 2, 2, 2, 3 ]
当前[2]:2 与 [5]:3 不一样,覆盖
结果nums [ 1, 1, 2, 2, 3, 3 ]
最大子数组和
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:
输入: nums = [1]
输出: 1
示例 3:
输入: nums = [5,4,-1,7,8]
输出: 23
答题
//这里特点是每次判断上一个(pre+当前值) 与 当前值 中的 取最大值,
//可以过滤掉有负数的转折情况,因为正数是前面所有的合计,当遇到有负数大于前面正数合值,则从新去下一个整数位起点
//pre已经是处理过最大值,
// maxAns, pre 取最大值,保留在全局,一直记录最大值
var maxSubArray = (nums) => {
if(!nums) {
return 0
}
let pre = 0
let maxVal = nums[0]
for (let i = 0; i < nums.length; i++) {
const num = nums[i];
pre = Math.max(num + pre,num)
maxVal = Math.max(maxVal,pre)
}
return maxVal
}
15. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c , 使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例 1:
输入: nums = [-1,0,1,2,-1,-4]
输出: [[-1,-1,2],[-1,0,1]]
示例 2:
输入: nums = []
输出: []
示例 3:
输入: nums = [0]
输出: []
关键逻辑
- 先将数组进行排序
- 从左侧开始,选定一个值为 定值 ,右侧进行求解,获取与其相加为 00 的两个值
- 类似于快排,定义首和尾
- 首尾与 定值 相加
- 等于 00,记录这三个值
- 小于 00,首部右移
- 大于 00,尾部左移
- 定值右移,重复该步骤
答题
/**
* @param {number[]} nums
* @return {number[][]}
*/
// 方案1 暴力循环3次
//测试用例一般通不过,时间复杂度太高
var threeSum = function(nums) {
let ret = []
nums.sort()
for (let i = 0; i < nums.length; i++) {
for (let j = i+1; j < nums.length; j++) {
nums[j];
for (let k = j+1; k < nums.length; k++) {
nums[k];
if(i!== j && i !== k) {
if(nums[i] + nums[j] + nums[k] == 0) {
let newSub = []
newSub.push(nums[i])
newSub.push(nums[j])
newSub.push(nums[k])
ret.push(newSub)
}
}
}
}
}
let set = new Set(ret.map( (obj)=> {
return obj.join(',')
}))
let last = Array.from(set).map( (o) => o.split(',').map(o=> parseInt(o)) )
console.log(last)
return last
};
// 方案2 排序+ 3个游标
var threeSum = function(nums) {
let ans = [];
const len = nums.length;
if(nums == null || len < 3) return ans;//数组的长度大于3
nums.sort((a, b) => a - b); // 排序,
//这里需要指定排序方法,默认不传会按字符编码进行排序 如 [10,101,19,1001] 排序完为 [10,1001,101,19]
for (let i = 0; i < len ; i++) {
if(nums[i] > 0) break; // 由于数据已经是排好顺序的,所以当前与后的数都是大于0 ,直接不处理。
if(i > 0 && nums[i] == nums[i-1]) continue; // 去重,减少多余处理
let L = i+1;
let R = len-1;
while(L < R){// L 和 R 一直往中间移动,直到相遇
const sum = nums[i] + nums[L] + nums[R];
if(sum == 0){
ans.push([nums[i],nums[L],nums[R]]);
while (L<R && nums[L] == nums[L+1]) L++; // 去重,减少多余处理
while (L<R && nums[R] == nums[R-1]) R--; // 去重,减少多余处理
L++;
R--;
}
else if (sum < 0) L++;//当值小于0 ,证明负数太大,要L 右移动,减少负数值
else if (sum > 0) R--;//当值大于0 ,证明正数太大,要R 左移动,减少正数值
}
}
return ans;
};
11. 盛最多水的容器
给你 n 个非负整数 a1,a2,...,a``n,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明: 你不能倾斜容器。
示例 1:
输入: [1,8,6,2,5,4,8,3,7]
输出: 49
解释: 图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例 2:
输入: height = [1,1]
输出: 1
示例 3:
输入: height = [4,3,2,1,4]
输出: 16
思路
- 通过公式 min(leftHeigt,rightHeight) * x之间的距离 求出最大面积
- 遍历方式:通过左右双指针的方式,移动条件是那一边较小就优先移动
- 每次移动前都记录下来最大面积
- 遍历结束时最大值得出来
答题
//最终转化的效果是 y坐标:[1,8,6,2,5,4,8,3,7],与x坐标 动态长度 之间的关系互相相乘 ,最终结果面积取最大值
// 核心公式 min(leftHeigt,rightHeight) * 距离
// 如:min(1,7)∗8=8 这里的*8 是对应x坐标的每一个格 *8
// min(8,7)∗7=49
// min(8,3)∗6=18
//方案1 暴力双循环
var maxArea = function(height) {
let max = 0
for (let i = 0; i < height.length; i++) {
for (let j = i+1; j < height.length; j++) {
let aera = Math.min(height[i],height[j]) * (j-i)
max = Math.max(max,aera)
}
}
return max
};
//方案 while+双指针
var maxArea = function(height) {
let left = 0 //左边的游标
let right = height.length - 1 //右边的游标
let max = 0 //全局最大值
while (left < right) { //两个游标相碰就结束
let minHeight = Math.min(height[left],height[right]) //取最小高度
let width = right - left //中间间隔多少格
let area = minHeight * width
max = Math.max(max,area)
if(height[left] < height[right]) {
left++
}else {
right--
}
}
return max
};
//方案 while+双指针 简化版
var maxArea = function(height) {
let max = 0;
for (let i = 0, j = height.length - 1; i < j;) {//双指针i,j循环height数组
//i,j较小的那个先向内移动 如果高的指针先移动,那肯定不如当前的面积大
const minHeight = height[i] < height[j] ? height[i++] : height[j--];
const area = (j - i + 1) * minHeight;//计算面积
max = Math.max(max, area);//更新最大面积
}
return max;
};
219. 存在重复元素 II
给你一个整数数组 nums 和一个整数 k ,判断数组中是否存在两个 不同的索引 i 和 j ,满足 nums[i] == nums[j] 且 abs(i - j) <= k 。如果存在,返回 true ;否则,返回 false 。
示例 1:
输入:nums = [1,2,3,1], k = 3
输出:true
示例 2:
输入:nums = [1,0,1,1], k = 1
输出:true
示例 3:
输入:nums = [1,2,3,1,2,3], k = 2
输出:false
答题
注意,这里只要处理最小的,所有当nums = [1,0,1,1], k = 3 , 只需要处理 第index 0 到 2 的上的值都是一就满足 <= k ,不需要处理 =k的情况,所以通过map记录上一个值的下标,判断小于等于K 就可以立刻返回 ,每次都会更新最新的map的下标值
/**
* @param {number[]} nums
* @param {number} k
* @return {boolean}
*/
//双重循环
var containsNearbyDuplicate = function(nums, k) {
for (let i = 0; i < nums.length; i++) {
for (let j = i+1; j < nums.length; j++) {
if(nums[i] == nums[j]) {
if(Math.abs(i-j) <= k) {
return true
}
}
}
}
return false;
};
//map 方式
var containsNearbyDuplicate = function(nums, k) {
let map = new Map()
for (let i = 0; i < nums.length; i++) {
const num = nums[i];
if (map.has(num)) {
if(i - map.get(num) <= k) {
return true
}
}
map.set(num,i) //注意,这里即使重复,每次都覆盖最新的下标
}
return false;
};
//set + 滑动窗口
var containsNearbyDuplicate = function(nums, k) {
const set = new Set();
const length = nums.length;
for (let i = 0; i < length; i++) {
if (i > k) {
set.delete(nums[i - k - 1]);
}
if (set.has(nums[i])) {
return true;
}
set.add(nums[i])
}
return false;
};
349. 两个数组的交集
给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [9,4]
说明:
- 输出结果中的每个元素一定是唯一的。
- 我们可以不考虑输出结果的顺序。
答题
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
*/
var intersection = function(nums1, nums2) {
let set1 = new Set(nums1)
let set2 = new Set(nums2)
let ret = []
//优化set1固定循环的集合,默认按最短的遍历
if(set1.length > set2.length) {
[set1,set2] = [set2,set1]
}
set1.forEach((val) => {
if(set2.has(val)){
ret.push(val)
}
})
return ret
};