集合,列表和数组
一、集合:一个或多个确定的元素构成的整体
特点:
1、集合里的元素类型不一定相同
2、集合里的元素没有顺序
二、列表:一种数据项构成的有序数列
特点:
1、列表里的元素类型不一定相同
2、列表里的元素具有一定的顺序,元素在内存中可能相连也可能不相连
包括:
1、栈
2、队列
实现方式:
1、数组
2、链表
三、数组:列表的实现方式之一
特点:
1、数组里的元素类型必须相同
2、数组里的元素就有一定的顺序,元素在内存中都是连续存储的
与列表的最大不同点:数组有索引,列表没有索引
集合,列表和数组的不同点
1、元素的类型:集合和列表的数组类型不同,数组的元素类型相同
2、元素顺序:集合元素没有顺序,列表和数组有顺序,数组是连续存储的,列表不是。数组有索引,列表没有索引
数组的操作
一、读取元素
访问元素:内存地址+索引
时间复杂度:O(1)
二、查找元素
查找方式:从索引0顺序查找
时间复杂度:O(N),N N为数组长度
三、插入元素
时间复杂度:O(N),N N为数组长度
四、删除元素
时间复杂度:O(N),N N为数组长度
示例
一、寻找数组的中心索引
给你一个整数数组 nums ,请计算数组的 中心下标 。
数组 中心下标 是数组的一个下标,其左侧所有元素相加的和等于右侧所有元素相加的和。
如果中心下标位于数组最左端,那么左侧数之和视为 0 ,因为在下标的左侧不存在元素。这一点对于中心下标位于数组最右端同样适用。
如果数组有多个中心下标,应该返回 最靠近左边 的那一个。如果数组不存在中心下标,返回 -1 。
示例 1:
输入:nums = [1, 7, 3, 6, 5, 6]
输出:3
解释:
中心下标是 3 。
左侧数之和 sum = nums[0] + nums[1] + nums[2] = 1 + 7 + 3 = 11 ,
右侧数之和 sum = nums[4] + nums[5] = 5 + 6 = 11 ,二者相等。
思路:
1、先求数组中所有数组元素之和sum;
2、遍历数组,取当前下标左侧所有元素之和leftSum,并用sum减去已经遍历的元素,比较leftSum和sum是否相等,相等就返回下标
3、遍历结束,代表没有中心索引,返回-1;
代码:
/**
* @param {number[]} nums
* @return {number}
*/
var pivotIndex = function (nums) {
let sum = 0
for (let i = 0; i <= nums.length - 1; i++) {
sum += nums[i]
}
let leftSum = 0
for (let i = 0; i <= nums.length - 1; i++) {
sum -= nums[i]
if (leftSum == sum) {
return i
}
leftSum += nums[i]
}
return -1
console.log("numsSum", numsSum)
};
遍历数组,取当前下标左边的元素之和leftSum,同时sum减去已遍历元素,比较二者是否相等,相等则返回当前下标;
二、搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。请必须使用时间复杂度为 O(log n) 的算法。
示例 1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例 2:
输入: nums = [1,3,5,6], target = 2
输出: 1
示例 3:
输入: nums = [1,3,5,6], target = 7
输出: 4
思路:
1、遍历数组,如果遍历元素>=目标元素,就返回
2、全部遍历完证明目标元素为最大元素,直接插入末尾,返回数组长度
代码:
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function (nums, target) {
for (let i = 0; i < nums.length; i++) {
if (nums[i] >= target) return i
}
return nums.length
};
三、合并区间
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
代码:
* @param {number[][]} intervals
* @return {number[][]}
*/
var merge = function (intervals) {
intervals.sort((a, b) => a[0] - b[0])
return intervals.reduce((pre, cur, index) => {
let peek = pre[pre.length - 1]
if (peek && peek[1] >= cur[0]) {
let left = peek[0]
let right = peek[1] > cur[1] ? peek[1] : cur[1]
pre[pre.length - 1] = [left,right]
}else{
pre.push(cur)
}
return pre
}, [])
};
二维数组简介
常用来处理矩阵问题:矩阵旋转,对角线遍历,对子矩阵的操作
一、旋转矩阵
给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。
不占用额外内存空间能否做到?
示例 1:
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],
原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]
示例 2:
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],
原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]
思路:
1、新建一个与matrix同维数的新数组
2、遍历数组,找旋转后的规律:matrix[i][j] = matrix[matrix.length-j-1][i]
代码:
/**
* @param {number[][]} matrix
* @return {void} Do not return anything, modify matrix in-place instead.
*/
var rotate = function (matrix) {
let newArr = Array.from({length:matrix.length},() =>[])
for (let i = 0; i < matrix.length; i++) {
for (let j = 0; j < matrix[i].length; j++) {
newArr[i][j] = matrix[matrix.length-j-1][i]
}
}
return newArr
};
二、零矩阵
编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。
示例 1:
输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
示例 2:
输入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
输出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]
思路:
1、两次遍历二维数组,找到数组中为0的rows,和cols
2、利用 Object.keys得到rows、cols的数组rowList、colList
3、遍历rowList,利用new Array(n).fill(0)将行置为0
4、遍历colList,再遍历matrix将元素列置为0
代码:
/**
* @param {number[][]} matrix
* @return {void} Do not return anything, modify matrix in-place instead.
*/
var setZeroes = function (matrix) {
if (matrix.length === 0) return []
const m = matrix.length
const n = matrix[0].length
const rows = {}
const cols = {}
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (matrix[i][j] === 0) {
rows[i] = true
cols[j] = true
}
}
}
const rowList = Object.keys(rows)
const colList = Object.keys(cols)
rowList.forEach(item => {
matrix[item] = new Array(n).fill(0)
})
colList.forEach(item => {
matrix.forEach(arrList => {
arrList[item] = 0
})
})
return matrix
};
双指针技巧
使用双指针技巧,其思想是分别将两个指针分别指向数组的开头及末尾,然后将其指向的元素进行交换,再将指针向中间移动一步,继续交换,直到这两个指针相遇。
典型场景:
情景一:从两端向中间迭代数组。技巧:一个指针从头部开始,而另一个指针从尾部开始。(排序数组)
情景二:同时有一个慢指针和一个快指针。技巧:确定两个指针的移动策略。要在使用双指针技巧之前对数组进行排序,也可能需要运用贪心法则来决定你的运动策略。
双指针技巧——情景一
一、反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
示例 1:
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:
输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
方法:
1、利用[left,right] = [right,left]
2、利用reverse
代码:
方法1:
/**
* @param {character[]} s
* @return {void} Do not return anything, modify s in-place instead.
*/
var reverseString = function (s) {
let a = 0
let b = s.length - 1
while (a < b) {
[s[a], s[b]] = [s[b], s[a]]
b--;
a++
}
};
方法2:
/**
* @param {character[]} s
* @return {void} Do not return anything, modify s in-place instead.
*/
var reverseString = function (s) {
for (let left = 0, right = s.length - 1; left < right; left++, right--) {
[s[left], s[right]] = [s[right], s[left]]
}
};
方法3:
/**
* @param {character[]} s
* @return {void} Do not return anything, modify s in-place instead.
*/
var reverseString = function (s) {
s.reverse()//不建议
};
一、数组拆分 I
给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n对, 例如 (a1, b1), (a2,b2), ..., (an, bn) ,使得从 1 到 n 的 min(ai, bi) 总和最大。返回该 最大总和。
示例 1:
输入:nums = [1,4,3,2]
输出:4
解释:所有可能的分法(忽略元素顺序)为:
1. (1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3
2. (1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3
3. (1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4
所以最大总和为 4
示例 2:
输入:nums = [6,2,6,5,1,2]
输出:9
解释:最优的分法为 (2, 1), (2, 5), (6, 6). min(2, 1) + min(2, 5) + min(6, 6) = 1 + 2 + 6 = 9
思路:排序,下标为偶数时和相加即为所求
代码:
方法1:
/**
* @param {number[]} nums
* @return {number}
*/
var arrayPairSum = function (nums) {
nums.sort((a, b) => a - b)
let sum = 0
for (let i = 0; i < nums.length; i++) {
if (i % 2 == 0) sum += nums[i]
}
return sum
};
方法2:
/**
* @param {number[]} nums
* @return {number}
*/
var arrayPairSum = function (nums) {
// nums.sort((a,b) => {return a-b});
// let i = 0;
// let res = 0;
// while(i < nums.length){
// res += nums[i];
// i += 2;
// }
// return res;
};
二、两数之和 II - 输入有序数组
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。
你所设计的解决方案必须只使用常量级的额外空间。
示例 1:
输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
示例 2:
输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。
示例 3:
输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
思路:由于数组从小到大排列, 双指针别指向首部和尾部;
1、首部尾部相加等于目标值,返回结果集
2、首部尾部相加小于目标值,首部后移变大
3、首部尾部相加大于目标值,尾部前移变小
代码:
/**
* @param {number[]} numbers
* @param {number} target
* @return {number[]}
*/
var twoSum = function (numbers, target) {
let left = 0
let right = numbers.length - 1
let newArr = []
while (left < right) {
if (numbers[left] + numbers[right] == target) {
newArr = [(left + 1), (right + 1)]
return newArr
} else if (numbers[left] + numbers[right] < target) {
left++
} else {
right--
}
}
};
三、移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
示例 2:
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。
思路:创建一个变量当慢指针,复制一遍数组,当遇到需剔除数据时不执行复制当前值操作跳过当前循环,循环结束返回慢指针值即为新数组长度
代码:
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function (nums, val) {
let left = 0
for (let right = 0; right < nums.length; right++) {
if (nums[right] !== val) {
nums[left] = nums[right]
left++
}
}
return left
};