算法(附思维导图 + 全部解法)300题之(18)四数之和
零 标题:算法(leetode,附思维导图 + 全部解法)300题之(18)四数之和
导读:
作者简介
1 作者简历
2 作者标签
1)“伪全栈工程师,主攻前端,偶尔写点后端”。 2)2019年的微信小程序应用开发赛 - 全国三等奖; 2019CODA比赛 - 前 17/211 强 且 荣获“优秀团队”称号 等。 3)“半自媒体人”,在校期间、个人公众号(IT三少。新自媒体(公众号)号: 码农三少 )在半年内实现了0到5.8K+的粉丝增长等。
一 题目描述
二 解法总览(思维导图)
三 全部解法
1 方案1
1)代码:
// 方案1 “暴力”。 // 先排序,再4层for循环、穷举所有的可能。 // 超时,通过 282 / 286 。 var fourSum = function(nums, target) { const l = nums.length; // 1)排序。 nums = nums.sort((a, b) => a - b); // 2)状态初始化。 let map = new Map(), resArr = []; // 3)核心:4层for循环、穷举所有的可能。 for (let i = 0; i < l-3; i++) { for (let j = i + 1; j < l-2; j++) { for (let k = j + 1; k < l-1; k++) { for (let z = k + 1; z < l; z++) { const tempSum = nums[i] + nums[j] + nums[k] + nums[z], tempStr = [nums[i], nums[j], nums[k], nums[z]].join('#'); // 3.1)判断“当前4个数组合的和” 是否等于 target 且 之前没有存储过。 if (tempSum === target && !map.has(tempStr)) { resArr.push([nums[i], nums[j], nums[k], nums[z]]); map.set(tempStr, 1); } } } } } // 4)返回结果 resArr 。 return resArr; };
2 方案2
1)代码:
// 方案2 “方案1的优化版”。 // 原先的4层for循环 --> 2层for循环 + 双指针 。 // 技巧:“有序胜过无序”。 // 通过sort方法(时间复杂度仅为 O(nlogn))将无序的数组变有序是一件很划算的事情。 // 要想使用双指针等,必须先排序、使其变有序。 var fourSum = function(nums, target) { const l = nums.length; // 1)排序。 nums = nums.sort((a, b) => a - b); // 2)状态初始化。 let map = new Map(), resArr = []; // 3)核心:2层for循环 + 双指针 。 for (let i = 0; i < l-3; i++) { for (let j = i + 1; j < l-2; j++) { let left = j + 1, right = l-1; while (left < right) { const tempSum = nums[i] + nums[j] + nums[left] + nums[right], tempStr = [nums[i], nums[j], nums[left], nums[right]].join('#'); // 3.1)判断“当前4个数组合的和” 是否等于 target 且 之前没有存储过。 if (tempSum === target) { if (!map.has(tempStr)) { resArr.push([nums[i], nums[j], nums[left], nums[right]]); map.set(tempStr, 1); } // 3.2)核心:若 tempSum === target , // 则下一次的可能结果必须得让 left、right 各往中间至少走一步,不然组合必重复! left++; right--; } else if (tempSum < target){ left++; } else { right--; } } } } // 4)返回结果 resArr 。 return resArr; };
3 方案3
1)代码:
// 方案3 回溯(说白了、说穿了,就是递归。因为一般用递归实现回溯)。 // 本质:跟4层for循环差不多,可能的区别会体现在代码的可读性上。 // 超时,通过 268 / 286 。 var fourSum = function(nums, target) { // 技巧:永远记住,递归 = 递归出口(为了不陷入无线递归的死循环) + 递归主体(一般会变更一些参数后,在调用函数本身)。 // 一般 递归出口 放前面, 递归主体 放后面。 // 1)递归 const dfs = (index, l, curArr, resArr, nums, target) => { const curLength = curArr.length; // 1.1)递归出口。 // 边界:是 index > l ,而不是 index >= l(这样会遗漏答案,case:[1,0,-1,0,-2,2] 0 )。 if (index > l || curLength > 4) { return; } // 1.2)递归主体 if (curLength === 4) { const tempSum = curArr.reduce((acc, cur) => { acc += cur; return acc; }, 0), tempStr = curArr.join('#'); // 判断“当前4个数组合的和” 是否等于 target 且 之前没有存储过。 if (tempSum === target) { if (!map.has(tempStr)) { resArr.push(curArr.slice()); map.set(tempStr, 1); } } } else if (curLength < 4) { // 技巧:回溯的核心 = 选 + 不选 。 // 1.2.1)选 curArr.push(nums[index]); dfs(index + 1, l, curArr, resArr, nums, target); // 1.2.1)不选 curArr.pop(); dfs(index + 1, l, curArr, resArr, nums, target); } }; // 2)排序。 nums = nums.sort((a, b) => a - b); // 3)状态初始化。 const l = nums.length; let index = 0, map = new Map(), curArr = [], resArr = []; // 4)调用 回溯函数 —— dfs递归。 dfs(index, l, curArr, resArr, nums, target); // 5)返回结果 resArr 。 return resArr; }
4 方案4
1)代码:
// 方案4 “方案3(回溯)的优化版” —— 剪枝。 // TODO:暂时想不出,跳过。 // case:[-477,-476,-471,-462,-440,-400,-398,-394,-394,-393,-389,-386,-350,-346,-338,-315,-273,-249,-182,-172,-166,-161,-149,-116,-112,-109,-100,-73,-33,-26,-22,-11,6,8,13,19,56,78,101,102,111,140,155,158,181,205,211,225,232,242,254,265,281,308,310,320,320,364,366,381,385,387,443,496,496] // 1236 var fourSum = function(nums, target) { // TODO:暂时想不出,跳过。 }#面经##学习路径#