【leetcode】前端er必会算法-数组高频面试题篇

911 阅读5分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

原文位于 github仓库-正在起步阶段的前端知识库,其中记录了一名前端初学者的学习日记🤔&学习之路点点滴滴的记录(练手demo🧑‍💻,必会知识点🧐)

欢迎大家来贡献更多“前端er必会知识点”🧑‍🎓/分享更多有意义的demo❤️!(请给这个年幼的小仓库更丰富的内容吧🥺🥺🥺)

这篇文章汇总一个超极高频的问题:

找出排序数组中只出现一次的数字&延伸题目

image.png

另外还有一道小变式,蛮有意思

  • 第一题:使用异或找到数组中只出现一次的单身🐕固然可行,但是为了追求效率,应该使用二分查找~

  • 第二题:如果出现多个单身🐕 那么应该使用分组异或

  • 第三题:每个元素都出现三次,寻找单身🐕,位运算贼复杂(不会问一个小前端这个问题吧555),直接暴力哈希了(你要不要吧,你要不要🍉)

之前听学长说面试被问到了,然后追问了更佳的解法,就让我好奇起来了,一查力扣,嗬,剑指Offer,来做做看~

540. 有序数组中的单一元素

剑指 Offer II 070. 排序数组中只出现一次的数字 一样

我的题解——[JavaScript]异或、二分搜索(全体二分查找乱序数组&偶数二分查找有序数组)注释齐全

异或快捷解决

540题中英文版有规定 :Your solution must run in O(log n) time and O(1) space. 所以这个方法仅供了解

主要考察二分法!

 var singleNonDuplicate = function(nums) {
     let res;
     for(let i = 0; i < nums.length; i++){
         res ^= nums[i];
     }
     return res;
 };
再优化,二分法(面试重点)

这里的第一个关键点是先把四种情况列出来!

参考官方题解的图~

例子 1:中间元素的同一元素在右边,且被 mid 分成两半的数组为偶数。

我们将右子数组的第一个元素移除后,则右子数组元素个数变成奇数,我们应将 lo 设置为 mid + 2。

在这里插入图片描述

例子 2:中间元素的同一元素在右边,且被 mid 分成两半的数组为奇数。

我们将右子数组的第一个元素移除后,则右子数组的元素个数变为偶数,我们应将 hi 设置为 mid - 1。

在这里插入图片描述

例子 3:中间元素的同一元素在左边,且被 mid 分成两半的数组为偶数。

我们将左子数组的最后一个元素移除后,则左子数组的元素个数变为奇数,我们应将 hi 设置为 mid - 2。

在这里插入图片描述

例子 4:中间元素的同一元素在左边,且被 mid 分成两半的数组为奇数。

我们将左子数组的最后一个元素移除后,则左子数组的元素个数变为偶数,我们应将 lo 设置为 mid + 1。

在这里插入图片描述

然后就常规二分法做就行了~注意分情况讨论的细则即可!

 var singleNonDuplicate = function(nums) {
     // 定义双指针
     let i = 0, j = nums.length - 1;
     while(i < j){
         // let mid = (i + j) >> 1;
         // 为了防止大数溢出 建议这么写
         let mid = i + (j - i >> 1)
         // 此方法的关键——判断哪边为奇数的变量 要设置好
         let isEven = (j - mid) % 2 == 0;
         // 如果j-mid为偶数 则去除中间两个值相同的元素并跳过它们之后,两指针(包括两指针)之间有奇数个元素,
         // 也就是单个的元素一定在这之间
         if(nums[mid] === nums[mid - 1]){
             if(isEven){
                 // 在左边
                 j = mid - 2;
             }
             else{
                 i = mid + 1;
             }
         }
         else if(nums[mid] === nums[mid + 1]){
             if(isEven){
                 // 在右边
                 i = mid + 2;
             }
             else{
                 console.log("last j",j)
                 j = mid - 1;
             }
         }
         else{
             return nums[mid];
         }
     }
     return nums[i];
 };

怎么说呢,双指针的题,多画图就完事了!

时间复杂度 O(logn),相比于暴力循环(包括异或),每次迭代将搜索空间缩减了一半!

进一步优化,仅对偶数索引进行二分搜索

最佳实践

 var singleNonDuplicate = function(nums) {
     let i = 0, j = nums.length - 1;// 数组长度必为奇数,所以一前一后两个元素下标为偶数
     while(i < j){
         let mid = i + ((j - i) >> 1);
         if(mid % 2 === 1){
             // mid为奇数则-1变为偶数 则mid现在必为“边缘” 不必再分四种情况来讨论
             // 这就是仅对偶数索引进行二分搜索!
             mid--;
         }
         if(nums[mid + 1] === nums[mid]){
             // 去除mid那一对数之后,左侧数必为偶数,右侧数必为奇数,继续去紧挨着那对数的右边1个找
             i = mid + 2;
         }
         else{
             // 去除mid那一对数之后,左侧数为奇数,右侧数必为偶数,继续去紧挨着那对数的左边1个找
             j = mid;// 此时mid已经在原基础上左移一位了 所以j直接放在mid这个位置即可
         }
     }
     return nums[i];
 };
  • 时间复杂度:O(log n/2) = O(logn)。我们仅对元素的一半进行二分搜索。

剑指 Offer 56 - I. 数组中数字出现的次数

位运算-分组异或

这个分组的方法就很灵性。

 var singleNumbers = function(nums) {
     let n = 0;
     // 01 n 为 两个单独数a b的乘积
     // 接下来(02中)使用与运算
         // 与运算特点 二进制中只有6&6 = 6 6&0 = 0&0 =0
     for(let num of nums){
         n ^= num;
     }
     // 02 m可以保证这个数组中单身的两个数a b中的一个可以不被它抵消掉 
     // 也就是 m&a = 0 m&b != 0
     let m = 1;
     while((n & m) === 0){
         // 只要n&m不为0 就一直让m左移,直到m可以抵消掉a与b中的一个
         m <<= 1;
     }
     // 03 接下来使用m把两个单独的数分在两堆 并分组
     let x = 0, y = 0;
     for(let num of nums){
         if((num & m) === 0){
             x ^= num;
         }
         else{
             y ^= num;
         }
     }
     return [x, y];
 };

看不懂我这个解释(或者觉得太大白话) 可以看看 K神的题解

137. 只出现一次的数字 II

剑指 Offer 56 - II. 数组中数字出现的次数 II一样

暴力哈希解
 var singleNumber = function(nums) {
     let map = new Map();
     for(let num of nums){
         if(map.get(num) > 0){
             let count = map.get(num);
             count++;
             map.set(num, count);
         }
         else{
             map.set(num, 1);
         }
     }
     for(let [num,count] of map.entries()){
         if(count === 1){
             return num;
         }
     }
 };

其他方法太恶心了 饶了我吧…我真不想做位运算了😢😢😢😢😢😢,数电都出来了

如上。