1. 两数之和
题目:给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
思路:最容易想到的方法是枚举数组中的每一个数 x,寻找数组中是否存在 target - x。
当我们使用遍历整个数组的方式寻找[target - x] 时,需要注意到每一个位于 x 之前的元素都已经和 x 匹配过,因此不需要再进行匹配。而每一个元素不能被使用两次,所以我们只需要在 x 后面的元素中寻找 target - x。
解法1:暴力枚举
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
for(let i=0;i<nums.length;i++){
for(let j=0;j<nums.length;j++){
if(nums[i]+nums[j]==target && i!=j){
return [i, j]
}
}
}
};
思路:遍历的同时可以记录一些信息,以省去一层循环,‘以空间换时间’,
需要记录已经遍历过的数值和他所对应的下标,可以借助查找表实现。
解法2:哈希表
var twoSum = function(nums, target) {
let map = new Map()
map.set(nums[0], 0)
for(let i=1;i<nums.length;i++){
if(map.has(target-nums[i])){
return [map.get(target-nums[i]), i]
}else{
map.set(nums[i], i)
}
}
};
2. 26. 删除有序数组中的重复项
题目:给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。
将最终结果插入 nums 的前 k 个位置后返回 k 。
不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
思路:
/**
* @param {number[]} nums
* @return {number}
*/
var removeDuplicates = function(nums) {
if(!nums.length){return 0}
let i=1
for(let j=1;j<nums.length;j++){
if(nums[j]!=nums[i-1]){
nums[i]=nums[j]
i++
}
}
return i
};
3. 27. 移除元素
题目:给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 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) {
if(nums.length==0)return 0
let i=0;
for(let j=0; j<nums.length; j++){
if(nums[j]!=val){
nums[i]=nums[j]
i++
}
}
return i
};
4. 53. 最大子数组和
题目:
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
⚠️ 子数组 是数组中的一个连续部分。
示例:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6
贪心法
/**
* @param {number[]} nums
* @return {number}
*/
var maxSubArray = function(nums) {
let sum = 0 // 当前和
let maxAns = nums[0] // 最大的和
nums.forEach(item => {
// 若当前指针所指元素之前的和 < 0,则丢弃之前的数列
if(sum < 0) {
sum = item
} else {
// 反之,则累加上之前的和
sum += item
}
// 在存的最大和与当前和之前取较为大的值
maxAns = Math.max(maxAns, sum)
})
};
5. 35. 搜索插入位置
题目:给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
⚠️ 请必须使用时间复杂度为 O(log n) 的算法。
示例1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例2:
输入: nums = [1,3,5,6], target = 2
输出: 1
解答:
/**
* @param {number[]} nums
* @param {number} target
* @return {number}
*/
var searchInsert = function(nums, target) {
let len = nums.length
for(let i=0;i<len;i++){
if(nums[i] === target||nums[i]>target){
return i;
}
if(i==len-1 && nums[i]<target){
return len;
}
}
};
6. 88. 合并两个有序数组
题目:给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6],其中斜体加粗标注的为 nums1 中的元素。
思路:
- 直接合并后排序:将数组直接合并,然后用sort排序,两行就可以完成,但这样没有利用到 两个数组 都是有序数组的条件,时间复杂度与空间复杂度都比较高,不建议采用;
- 双指针法:这一方法将两个数组看作队列,每次从两个数组头部取出比较小的数字放到结果中;
复杂度分析
时间复杂度:O(m+n)。
指针移动单调递增,最多移动 m+n 次,因此时间复杂度为 O(m+n)。
空间复杂度:O(m+n)。
需要建立长度为 m+n 的中间数组sorted。 - 逆向双指针:2的方法多使用一个空间,因为sums1的后半部分是0(占位元素),所以可以直接覆盖,不会影响结果,因此指针可以从后向前遍历,取较大值放到最后,也就是以下的解答代码;
复杂度分析
时间复杂度:O(m+n)
空间复杂度:O(1),直接对sums1进行修改,没有使用多余的空间。
解答:
/**
* @param {number[]} nums1
* @param {number} m
* @param {number[]} nums2
* @param {number} n
* @return {void} Do not return anything, modify nums1 in-place instead.
*/
var merge = function (nums1, m, nums2, n) {
let cur = m + n - 1 //从nums1尾部开始
while (cur >= 0) {
if (n === 0) return //num2已经全部放入num1中了
if (m < 1) {//num1指针先走完了
nums1[cur--] = nums2[--n]
continue
}
if (n < 1) {//num2指针先走完了
nums1[cur--] = nums1[--m]
continue
}
//取较大的插入 nums1 的末尾、更新对应的指针
if (nums1[m - 1] > nums2[n - 1]) {
nums1[cur--] = nums1[--m]
} else {
nums1[cur--] = nums2[--n]
}
}
};
7. 118. 杨辉三角
题目:给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
暂时无法在文档外展示此内容
示例:
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
解答:
/**
* @param {number} numRows
* @return {number[][]}
*/
var generate = function (numRows) {
const ret = []
for (let i = 0; i < numRows; i++) {
// 创建当前(二级)数组的首尾值 1
const row = new Array(i + 1).fill(1)
// 算出当前数组的中间的值
for (let j = 1; j < row.length - 1; j++) {
// cur = 左上方的值+右上方的值
row[j] = ret[i - 1][j - 1] + ret[i - 1][j]
}
ret.push(row)
}
return ret;
};
8. 119. 杨辉三角 II *
题目:给定一个非负索引 rowIndex,返回「杨辉三角」的第 rowIndex 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
示例 1:
输入: rowIndex = 3
输出: [1,3,3,1]
解答1:和上题的解法一样,只是最后返回了数组的最后一行
/**
* @param {number} rowIndex
* @return {number[]}
*/
var getRow = function (rowIndex) {
const ret = []
for (let i = 0; i < rowIndex + 1; i++) {
let row = new Array(i + 1).fill(1)
for (let j = 1; j < row.length - 1; j++) {
row[j] = ret[i - 1][j] + ret[i - 1][j - 1]
}
ret.push(row)
}
return ret[rowIndex]
};
分析:
这种做法占用了一整个杨辉三角的数组空间,可以进一步优化,只用到前一个数组的空间;
解答2:
/**
* @param {number} rowIndex
* @return {number[]}
*/
var getRow = function(rowIndex) {
const row = new Array(rowIndex + 1).fill(0);
row[0] = 1;
for (let i = 1; i <= rowIndex; ++i) {
row[i] = row[i - 1] * (rowIndex - i + 1) / i;
}
return row;
};
复杂度分析
- 时间复杂度:O(rowIndex)。
- 空间复杂度:O(1)。不考虑返回值的空间占用。
9. 217. 存在重复元素
题目:给你一个整数数组 nums 。如果任一值在数组中出现 至少两次 ,返回 true ;如果数组中每个元素互不相同,返回 false 。
示例 1:
输入:nums = [1,2,3,1]
输出:true
示例 2:
输入:nums = [1,2,3,4]
输出:false
解答1:
/**
* @param {number[]} nums
* @return {boolean}
*/
var containsDuplicate = function(nums) {
let arr = [...new Set(nums)]
let isExist = false
if(arr.length != nums.length) {
isExist = true
}
return isExist
};
10. 283. 移动零
题目:给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:
输入: nums = [0]
输出: [0]
解答1:
/**
* @param {number[]} nums
* @return {void} Do not return anything, modify nums in-place instead.
*/
var moveZeroes = function (nums) {
let l = nums.length
// 从后向前遍历,当前值为0时将其移动到最后
for (let i = l - 1; i > -1; i--) {
if (nums[i] == 0) {
nums.splice(i, 1);
nums.push(0);
}
}
return nums
};
分析:提交之后耗时308ms,可能是因为使用了splice耗时较高;
解答2:
var moveZeroes = function (nums) {
let j = 0;
for (let i = 0; i < nums.length; i++) {
if (nums[i] !== 0) { // 遇到非0元素,让nums[j] = nums[i],然后j++
nums[j] = nums[i];
j++;
}
}
for (let i = j; i < nums.length; i++) { // 剩下的元素全是0
nums[i] = 0;
}
return nums;
};
分析:这个方法耗时88ms,操作都变成了赋值,缩短了运行时间;
11. 349. 两个数组的交集
题目:给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例 2:
输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的
解答:
/**
* @param {number[]} nums1
* @param {number[]} nums2
* @return {number[]}
*/
var intersection = function (nums1, nums2) {
let arr = []
nums1.forEach(item => {
if (nums2.includes(item)) { // 判断当前值是否存在于nums2中
arr.push(item) // 存在则放到新数组
}
})
return [...new Set(arr)] // 数组去重
};
12. 1002. 查找共用字符
题目:给你一个字符串数组 words ,请你找出所有在 words 的每个字符串中都出现的共用字符( 包括重复字符),并以数组形式返回。你可以按 任意顺序 返回答案。
示例 1:
输入:words = [“bella”,“label”,“roller”]
输出:[“e”,“l”,“l”]
解答:
/**
* @param {string[]} A
* @return {string[]}
*/
const commonChars = (A) => {
A.reduce(([...prev], [...next]) =>
prev.filter(item => next.indexOf(item) > -1 && next.splice(next.indexOf(item), 1).length)
)
}