叫“梦开始的地方”,或许是因为这是大多数人LeetCode的第一站,也可能是最后一站。
有人相爱,有人夜里开车看海,有人leetcode第一题都做不出来。
2sum:两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那两个整数,
并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
第一次的想法很简单也很直白,双for循环直接遍历求解。
var twoSum = function (nums, target) {
/* 暴力解:先在数组中固定一个一个数字,再依次判断数组中其余的数字与它的和是不是等于k,
这种暴力解法的时间复杂度为O(n^2)
*/
let res = [];
for (let i = 0; i < nums.length; i++) {
if (nums[i] > target) continue;
let mid = target - nums[i];
for (let j = 1; j < nums.length; j++) {
if (nums[j] === mid && i !== j) {
res = [i, j].sort((a, b) => a - b);
}
}
}
return res;
};
第二次考虑到优化解法的方法使用了双指针,解法。双指针适用前提是一个有序目标集合。只要选定好双端指针和指针移动边界规则就能解出双指针的解法。如图
// 双指针解法 O(n)
var twoSum = function (nums, target) {
let left = 0,
right = nums.length - 1;
let sum = nums[left] + nums[right]; // 初始化
while (left < right && sum != target) {
if (sum < target) {
// 指针移动
left++;
} else {
right--;
}
sum = nums[left] + nums[right];
}
return [left, right];
};
第三种也是目前相对快的解法,通过hashmap空间换时间的方式进行快速解。相对来说,hashmap解法通常需要O(n)的空间复杂度。
var twoSum = function (nums, target) {
const map = new Map(); // 数组中同一元素不重复出现
for (let i = 0, len = nums.length; i < len; i++) {
if (map.has(target - nums[i])) {
let res = map.get(target - nums[i]);
return [i, res];
}
map.set(nums[i], i);
}
};
3sum:三数之和
一题二写,三数之和,题解四瞅五瞄六瞧,水平还七上八下九流,十分辣鸡。
十推九敲,八种思路,用光七情六欲五感,在这里四覆三翻二挠,一拳爆屏。
给你一个包含 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]
输出: []
三数和的解法要考虑的问题相对两数之和就会复杂一些,不然怎么会有开头的对联...
实际上三数和的解法首先从对 nums 的排序开始,通过双指针解法如图, 固定一个小于0的k值,然后求出一个nums[k] + nums[l] + nums[r] = 0等式,同时避免相同三元组即可求解。
/**
* @param {number[]} nums
* @return {number[][]}
*/
var threeSum = function(nums) {
if(nums.length < 2) {
return []
}
// nums 排序
nums.sort((a, b) => a - b);
let res = [];
for(let k = 0 ; k < nums.length - 2; k++) { // 限定 k 的移动,同时为 l r 指针留空位
if(nums[k] > 0) break; // nums[k] 如果大于零,那么 r > l > k 的前提下,不可能出现三数和为零的解
if(k > 0 && nums[k] === nums[k - 1]) continue;// 跳过同值的 nums[k]
let l = k + 1;
let r = nums.length - 1;
while(l < r) { // 双指针循环
let midSum = nums[k] + nums[l] + nums[r];
if(midSum < 0) {
l ++;//左指针右移
while(l < r && nums[l] === nums[l - 1]){
l ++
}
} else if( midSum > 0) {
r --;// 右指针左移
while(l < r && nums[r] === nums[r + 1]) {
r --;
}
} else {
// 这里是 midSum == 0 但是要去重 避免出现重复三元组
res.push([nums[k], nums[l], nums[r]]);
l ++;
r --;
// left 指针右移后与上一位还是一样就再移一步
while(l < r && nums[l] === nums[l - 1] ){
l ++;
}
// right 同 left
while(l < r && nums[r] === nums[r + 1]) {
r --;
}
}
}
}
return res;
};