一道简单的算法题(一)

求数组中为一个不同的数字

先看看这个题的内容:

有给定一个数组有一些数字。只有一个数字和其他的数字不一样,需要把它找出来
数组至少包含3个元素。

例子:
findUniq([ 1, 1, 1, 2, 1, 1 ]) === 2
findUniq([ 0, 0, 0.55, 0, 0 ]) === 0.55

原题: https://www.codewars.com/kata/585d7d5adb20cf33cb000235

分析:

先看看有哪些条件:
1. 一个数组
2. 每一个元素的数据类型是 number
3. 只有一个唯一的一个元素与其他的元素不一样
4. 本题的关键就是如何表示数组唯一元素的唯一性

解答

当时写了两个解决方案:

第一种方法:求最大值和最小值

不管这个数组中有多少个元素,其实只有两种数字:唯一的一个元素与其他相同的元素

let arr = [1, 1, 1, 3, 1, 1];

arr 只有两种数字,1 和 3。

在深入一步:可以得到一个数组中的最大值和最小值,并且那个唯一的数字就隐藏在这两者之中,

如何得到它呢,就要使用一个数组的查找元素的方法,indexOflastIndexOf。我们将会使用到这两个方法的共同作用之一:返回查到到的第一个元素的索引。

good!答案就有;上代码:

  function findUniq(arr) {

    let max = Math.max(...arr);
    let min = Math.min(...arr);
    return arr.indexOf(max) === 
           arr.lastIndexOf(max)? max: min;
  }
  1. 首先是获取数组中的最大值和最小值
  2. 拿到最大值在数组的索引,并且是从左右两方向获取,之后判断这两个索引是否相等
  3. 前面已经分析了在最大和最小值之中会有一个唯一的数组,所以只要获取到的索引相同则就是该最大值,否则就是最小值;

这个方法比较简单,但第一种方法并不是我一开始想到的,一开始我想到的方法是这个:
第二种方法:

  function findUniq(arr) {

    let max = Math.max(...arr);
    let min = Math.min(...arr);
    return filterUnip(arr, max).length <= 1? max: min;
  }

  function filterUnip(arr,nums) {
    return arr.filter((item) => {
      return nums === item;
    });
  };

还是求最大值和最小值,再利用唯一的数字的数量只用一个的特性,并找出该数字。
只不过处理的手法不一样,我使用来数组中的 filter() 方法求得每一种数字组成的数组,用长度来验证元素的唯一性

但是事件还没有完,上面的两种方法其实只能处理数组长度比较小的,一旦数组的长度超过一定的长度,代码的执行效率就很低。大家可以去把代码去原题测试一下,测试可以过,正真的数据过来就不行,因为有部分数组的元素的基数特别大,以及数组的长度是指数级别。

从性能的角度出发:函数的调用性能比一般的代码要弱。
并且遍历的次数过多也会降低代码的执行效率

Math.max , Math.min, indexOf() , lastIndexOf() ,filter() 其实就是在遍历数组中的每一个元素并且进行对比判断。前面的两种方法 其实都遍历 3 次数组。若一个数组是指数级别的,测试的时间自然很长。所以如何减少遍历数组的次数成为本题的关键


第三种方法:
把数组转化为字典

为什么要选择字典呢?
字典:就是键值对形式的数据结构。这种数据结构有一个很大的特点:键是唯一的,一个字典中的键是不会出现相同的,只会后者的值覆盖前者的值。所以为了得到唯一的数字,最多遍历一遍的情况下是可以做到。 只需要把每一种数字的变成键,用值来计算对应键的数量就可以得到想要的结果。

第三种方法:

  function findUniq(arr){

    let obj={};
    for(let i = 0; i < arr.length; i++){

      if(!obj[arr[i]]){
        obj[arr[i]] = 1;

        //当数组中唯一一个不一样的数字出现在数组的最后一位的时候
        if(i === (arr.length - 1)){
          return arr[i];
        }
      }else{
        obj[arr[i]] += 1;

        //出现多次的数字,次数大于2的时候
        if(obj[arr[i]] >= 2){

          for(let key in obj){

            if(obj[key] === 1){
              return key;
            }
          }
        }
      }
    }
  }

代码有点长,不过结构还是比较清晰,假设唯一的值为 a ,其相同的值为 b.

  1. 首先函数内所有的代码都是在一个 for 循环之内,我们的目的是只在遍历数组一次的情况下找出唯一的数值。

  2. 然后在 for 循环之内 只包含了一个 if 语句, 把数组的元素转化为对象中的键,对象其实就是一种字典,所以每一个键在一个对象中都是唯一存在,由此可知该对象中只有两个键,a 和 b ,这个 if 做的事情就是把数组中的元素抽离出来形成对象中唯一的键,然后利用值来计数。

    • 当该键不再对象中存在的时候,会新键一个键,并且值初始化为 1。
    • 当该键存在的时候,其实是已经执行了上一步,所以就是累加,还是利用了题目给的一个条件:唯一的元素。他的计数只用一种情况: 1。 这个方法的优势就在于,可能只用找出第 3 个元素就可以得到想要的结果

      • 当 b 的值大于等于 2 的时候就要枚举对象中的键,并判断每一个键对应的值的计数是否为 1
    • 还有特殊的情况存在的,当唯一的值的位置出现在数组最后一个的时候,就要单独判断,就在给键初始化的时候判断是不是在数组的最后一位。


其他的解法:

这一道题的解法其实有很多:
比如:异或的方法(这个方法我也试过,没有成功)

如果你有新的解法可以在评论区贴出来,一起交流 !

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值