半年工作经验前端备战2022上半年跳槽-算法学习日记
新的一周,组里的pm上周跑路了,需求不明确暂时没业务写,摸鱼1天自己学习。
开局先甩一个最近收藏的算法学习好文,记录一下,今天学习的主要内容也出自该文,剩余内容也会认真看完。
两个数组的交集
描述问题内容: 给定两个数组,编写一个函数来计算它们的交集。
示例 1:
输入: nums1 = [1,2,2,1], nums2 = [2,2]
输出: [2,2]
示例 2:
输入: nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出: [4,9]
来源:力扣(LeetCode)
链接:leetcode-cn.com/problems/in…
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
1. 文中给出了一种利用hash表的解法,双函数-时间复杂度:O(n+m)
let intersect = function(nums1, nums2) {
let map1 = makeCountMap(nums1)
let map2 = makeCountMap(nums2)
let res = []
for(let num of map1.keys()) {
const count1 = map1.get(num)
const count2 = map2.get(num)
if(count2) {
// 如果2中确实出现了1中的num那么就取出现次数较少的为计数 然后按照计数push该计数次数的该元素
const pushCount = Math.min(count1, count2)
for(let i = 0; i < pushCount; i++) {
res.push(num)
}
}
}
return res
}
function makeCountMap(nums) {
let map = new Map()
for(let i = 0; i < nums.length; i++) {
let num = nums[i]
let count = map.get(num)
if(count) {
map.set(num, count + 1)
} else {
map.set(num, 1)
}
}
return map
}
这里给出另一种单函数hash表解法-时间复杂度:O(n+m)
//思路:遍历短数组建造hash表计数,然后遍历长数组,做消消乐~
let intersect = function (nums1, nums2) {
// 保持短的数组放在更前面
if (nums1.length > nums2.length) {
return intersect(nums2, nums1)
}
let map = new Map()
// 遍历短数组 建立每个值的出现次数
for(let i = 0; i < nums1.length; i++) {
let count = (map.get(nums1[i]) || 0) + 1
map.set(nums1[i], count)
}
// 开辟栈记录交集
let res = []
//遍历长数组 与 短数组对照
for (let i = 0; i < nums2.length; i++) {
let count = map.get(nums2[i]) || 0
// map结构中记录的对应的数据有次数就压入结果栈 再更新map结构中记录短数组的次数(做减法)
if(count) {
res.push(nums2[i])
map.set(nums2[i], count - 1)
}
}
return res
}
console.log(intersect([1, 2, 3], [1, 2])); // [1, 2]
2. 双指针+排序
说实话我自己之前不知道双指针具体做法是啥(太菜了太菜了TT)
今天在leetCode上看到了,这里用自己的话叙述一遍:
首先要对数组进行一个升序排列
利用 Arr.prototype.sort()
该方法可以传入一个比较函数形如(a, b) => a - b;
1,若a-b<0,a会排在b前
2,若a-b=0,返回0
3,若a-b>0, b会排在a前
对于进行比较的的arr1 = [a1, a2, a3...]和 arr2 = [b1, b2, b3...]
开辟一个记录栈 res = []
设置两个指针 i 和 j 分别记录当前指向arr1和 arr2的元素
1,如果当前比较的两元素相等,那么把该元素压入记录栈,i和j会同时向后指一位
2,如果当前比较的两元素不相等,那么其中较小值对应的指针会向后指一位
依照上述两点执行直到其中一个数组遍历完毕,这时候对于长数组而言的多余元素其实是unique的,不予理会,
此时res就是想要的交集
OK,现在列出解答
let intersect = function (nums1, nums2) {
//排序
nums1.sort((a, b) => a - b)
nums2.sort((a, b) => a - b)
// 构建指针和记录栈
let i = 0, j = 0, res = []
//遍历
while(i < nums1.length && j < nums2.length) {
if(nums1[i] === nums2[j]) {
res.push(nums1[i])
i++
j++
} else nums1[i] < nums2[j] ? i++ : j++
}
return res
}
console.log(intersect([1, 2, 3], [2, 3])); // [2, 3]
然后还看到进阶的双指针+归并排序
let intersect = function(nums1, nums2) {
nums1 = nums1.sort((a,b) => a - b);
nums2 = nums2.sort((a,b) => a - b);
let i = 0;
let j = 0;
let k = 0;
while(i < nums1.length && j < nums2.length){
if(nums1[i] < nums2[j]){
i++;
}else if(nums1[i] > nums2[j]){
j++;
}
else{
nums1[k++] = nums1[i];
i++;
j++;
}
}
return nums1.slice(0,k);
};
这里对比普通排序的不同在于nums1[k++] = nums1[i]; ,假如i=0,j=0的指针指向的两值相同,设为x,会把x直接保存给nums1[0]( 因为此时k是0),然后k会自增, nums1和nums2的指针同时后跳
按照上述的方法继续遍历执行到完毕,最后slice(0, k)返回需要的结果
好处是不需要开辟新的记录栈,减少了空间复杂度
贴一个阮老师的ES6-Map和Set数据结构 es6.ruanyifeng.com/#docs/set-m…
3.暴力循环-时间复杂度O(n^2)
let intersect = function (nums1, nums2) {
let result = []
let minArr = nums1.length > nums2.length ? nums2 : nums1
let maxArr = nums1.length > nums2.length ? nums1 : nums2
for( let i = 0; i < minArr.length; i++ ) {
let maxIndex = maxArr.indexOf(minArr[i])
if(maxIndex != -1) {
result.push(maxArr.splice(maxIndex, 1)[0])
}
}
return result
}
4.贴一个时间复杂度和空间复杂度的解读
读完就差不多懂了 # 算法的时间与空间复杂度(一看就懂)
本文原创发布于微信公众号「 不止思考 」,欢迎关注,交流更多的 互联网认知、工作管理、大数据、Web、区块链技术。
小结
今天学习的内容其实并不难,但是对我这种前端小菜鸡而言确实获益良多。
-
学习了算法的思路,
-
复习巩固了各种数组方法和Set,Map数据结构,
-
还有诸如
if(nums1[i] === nums2[j]) {
res.push(nums1[i])
i++
j++
} else nums1[i] < nums2[j] ? i++ : j++
nums1[k++] = nums1[i];
i++;
j++;
较为优雅的细节写法
尾声
最近玩的有点疯,10.1还要回家赴大学同学的婚宴,
希望自己9月可以不忘初心,努力进步哈哈哈哈哈哈哈
尽管开始会很慢,但是至少我在做,我做了,
不积跬步无以至千里,不积小流无以成江海。