算法(附思维导图 + 全部解法)300题之(15)三数之和
零 标题:算法(leetode,附思维导图 + 全部解法)300题之(15)三数之和
导读:
作者简介
1 作者简历
2 作者标签
1)“伪全栈工程师,主攻前端,偶尔写点后端”。 2)2019年的微信小程序应用开发赛-全国三等奖; 2019CODA比赛- 前 17/211 强 且 荣获“优秀团队”称号 等。 3)“半自媒体人”,在校期间、个人公众号(IT三少。新自媒体(公众号)号:码农三少)在半年内实现了0到5.8K+的粉丝增长等。
一 题目描述
二 解法总览(思维导图)
三 全部解法
1 方案1
1)代码:
// 方案1 “暴力”。3层for循环,超时、通过 315 / 318 。 // 技巧:涉及“数量、重复、唯一性”可优先考虑 hash (JS里的 map数据结构 ) var threeSum = function(nums) { const l = nums.length; // map:判断是否重复 —— 即之前是否存过该答案 // 1)初始化 map 和 resArr let map = new Map(), resArr = []; // 2)3层for循环。i范围[0, l - 3],j范围[i + 1, l - 2],k范围[j + 1, l - 3]。 for (let i = 0; i < l - 2; i++) { for (let j = i + 1; j < l - 1; j++) { for (let k = j + 1; k < l; k++) { // 3)核心处理 const tempSum = nums[i] + nums[j] + nums[k]; if (tempSum === 0) { // 3.1)当 nums[i] + nums[j] + nums[k] === 0 时, // 判断此时3个数所组成的答案之前有没有存过,没有就保存到 resArr 里 const tempStr = [nums[i], nums[j], nums[k]].sort().join('#'); if (!map.has(tempStr)) { // 注:此处的 1 无意义,仅表示 tempStr 的答案存储过了 map.set(tempStr, 1); resArr.push([nums[i], nums[j], nums[k]]); } } } } } return resArr; };
2 方案2
1)代码:
// 方案2 “排序 + 双指针”。 // 技巧:佛说:“有序胜过无序”。 // 通过sort方法(时间复杂度仅为 O(nlogn))将无序的数组变有序是一件很划算的事情。 var threeSum = function(nums) { // 1)排序(升序) nums = nums.sort((a, b) => a-b); const l = nums.length; let resArr = []; for(let i = 0; i< l-2; i++) { // 边界:因为升序排的,若 nums[i] > 0, // 则必有 nums[i] + nums[left] + nums[right] > 0,需终止循环遍历 if (nums[i] > 0) { break; } // 边界:[0, 0, 0, 0]等。本质:"去第1个数的重"。此时需直接进入下一个循环 if (nums[i - 1] === nums[i]) { continue; } let left = i + 1; let right = l - 1; // 2)核心:固定1个数之后, // 就变成了“双指针”(本质就是twoSum,2个数之和为 (-1) * nums[i])问题。 while (left < right) { // 边界:[-1, 0, 0, 1, 1]。本质:"去第2个数的重"。 if (left - 1 !== i && nums[left] === nums[left - 1]) { left++; continue; } const tempSum = nums[i] + nums[left] + nums[right]; if (tempSum === 0) { // 3)找到之后,肯定不会重、直接放入 resArr。 // 处理:left、right同时往中间靠。 resArr.push([nums[i], nums[left], nums[right]]); left++; right--; } else if (tempSum < 0) { left++; } else { right--; } } } // 4)返回结果 resArr 。 return resArr; }
3 方案3
1)代码:
// 方案3 回溯(说白了、说穿了,就是递归。因为一般用递归实现回溯)。 // 本质:其实跟3层for循环差不多,超时。通过 315 / 318。 var threeSum = function(nums) { // 深度优先遍历。 // 技巧:递归 = 递归出口 + 递归主体。 const dfs = (index, l, curArr, resArr) => { const curLength = curArr.length; // 1)递归出口。index过大 || “当前记录数组”长度 > 3 if (index > l || curLength> 3) { return; } // 2)递归主体。 // 2.1)当 “当前记录数组”长度 === 3,需要判断是否为3个数之和为0且未重复存储过。 if (curLength === 3) { const tempSum = curArr.reduce((acc, cur) => { return acc += cur; }, 0); const tempStr = curArr.join('#'); if (tempSum === 0 && !map.has(tempStr)) { // 边界:必须是 curArr.slice() 、存其副本! // 若直接写的 curArr 就是存的引用,会因引用引起问题 resArr.push(curArr.slice()); map.set(tempStr, 1); } } // 2.2)当 “当前记录数组”长度 < 3,需要进行回溯遍历(即 选 与 不选 )。 else if (curLength < 3) { const newIndex = index + 1; // 核心:所谓的“回溯”本质 —— 选 与 不选。 // 2.2.1)选 curArr.push(nums[index]); dfs(newIndex, l, curArr, resArr); // 2.2.2)不选 curArr.pop(); dfs(newIndex, l, curArr, resArr); } }; const l = nums.length; // 1)排序。去重有用, const tempStr = curArr.join('#'); 。 nums = nums.sort((a, b) => a - b); // 2)“状态”初始化 let index = 0, // 作用:记录是否重复 map = new Map(), curArr = [], resArr = []; // 3)调用回溯函数 —— dfs dfs(index, l, curArr, resArr); // 4)返回结果 resArr 。 return resArr; }
四 内推&更多
1 内推
本人是百度的1名工程师,欢迎校招、社招同学向本人投递简历。
本人可内推公司(也可帮忙内推 阿里、腾讯、字节、美团、滴滴、京东等)的所有岗位,欢迎私信
2 更多
以下是个人整理的一些笔记和书籍(永久有效链接: https://pan.baidu.com/s/1SPc3umO6cZlBtoPylSaHzw 密码: eqee ,若失效的话可私信本人以进行最新资料的获取):