【问题标题】:Searching for first duplicate element in huge array of numbers在大量数字中搜索第一个重复元素
【发布时间】:2018-08-11 23:58:14
【问题描述】:

试图解决这个问题 - 给定一个数组 a,它只包含从 1 到 a.length 范围内的数字,找到第二次出现具有最小索引的第一个重复数字。

这是我的解决方案 -

function firstDuplicate(a) {
  for (let i = 0; i < a.length; i++) {
    if (a.indexOf(a[i]) !== i) {
      return a[i];
    }
  }

  return -1;
}

问题 - 接受标准之一是,算法应该在 4 秒内找到第一个重复值,当输入数组很大时我无法实现。我使用包含 100k 个项目的输入数组进行了测试,我的算法花费了 5 秒以上。有人可以帮我调整我的代码,让它在 4 秒内完成吗?

非常感谢!

【问题讨论】:

  • 喜欢这个? let dupe = arr.find((k,i) =&gt; arr.indexOf(k) !==i);
  • 使用集合来添加独特的项目。这工作得更快function firstDuplicate(a) { var uniques = new Set(); for(let item of a) { if(uniques.has(item)) { return item; } else { uniques.add(item); } } return -1; }
  • 使用地图并存储计数。如果地图包含一个计数大于 1 的键,则返回它。

标签: javascript arrays algorithm


【解决方案1】:

您必须遍历该数组并将元素收集到临时对象,该对象将数字(元素)作为键,将一些布尔值作为索引。

在每次迭代时检查临时对象是否具有该键。

const bigArray = [];


for(let i = 0; i<1000000; i++) {
  bigArray.push(i);
}


for(let i = 0; i<1000000; i++) {
  bigArray.push(parseInt(Math.random()*1000000));
}


const firstDuplicateInArray = array => {
  const temp = {};
  for (let i = 0; i < array.length; i++) {
    if (temp[array[i]] === true) {
      return array[i];
    }
    temp[array[i]] = true;
  }
  return -1;
};

const start = new Date().getTime();
console.log('Time start:', start);

console.log('Found 1st duplicate:', firstDuplicateInArray(bigArray));

const end = new Date().getTime();
console.log('Time end:', end);

console.log('Time taken:', end - start, 'microseconds');

附: Set 慢 2 倍以上(取决于数组的大小):

const bigArray = [];


for(let i = 0; i<1000000; i++) {
  bigArray.push(i);
}


for(let i = 0; i<1000000; i++) {
  bigArray.push(parseInt(Math.random()*1000000));
}


function firstDuplicate(a) {
  const r = new Set();
  for (let e of a) {
    if (r.has(e)) return e;
    else r.add(e);
  }
  return -1;
}

const start = new Date().getTime();
console.log('Time start:', start);

console.log('Found 1st duplicate:', firstDuplicate(bigArray));

const end = new Date().getTime();
console.log('Time end:', end);

console.log('Time taken:', end - start, 'microseconds');

【讨论】:

  • 否定是聪明的。其他人应注意:当问题将整数值限制为问题大小时,这几乎总是意味着您应该使用该大小的数组作为集合或以这些值为键的映射。
  • @num8er 您在两个脚本中使用了不同的随机测试数据,这并没有给我们一对一的比较。在我的测试中,它与 Set 版本一样快,因为您也有效地使用了当前列出的散列结构(对象)。试试const temp = new Array(array.length); ...
  • @spinkus 我知道。我这样做的目标是制作足够大的数组,其中重复元素可能离开始很远。使用阵列解决方案测试性能。
【解决方案2】:

使用Set 会导致密钥冲突。由于您知道您的值是有界范围内的整数,因此最快的方法是使用直接索引,这需要O(1) 查找时间而不是O(lg n)。虽然,直接实施将需要2*n 存储。如果您能够改变输入数组,您可以将其用作您的工作空间:

// No extra memory version.
// Negate value at index of seen number to store seen-ness.
// Assumes only numbers in the range from 1 to a.length allowed in array `a`.    
function firstDuplicateNew(a) {
  for (let i = 0; i < a.length; i++) {
    v = Math.abs(a[i])
    if (a[v-1] < 0) {
      return a[i];
    }
    a[v-1] = -1*a[v-1];
  }
  return -1;
}

// OP's Proposed faster version using Set.
function firstDuplicateSet(a) {
  r = new Set();
  for (e of a) {
    if (r.has(e)) return e;
    else r.add(e);
  }
  return -1;
}

// Another posted version.
const firstDuplicateInArray = array => {
  const temp = {};
  for (let i = 0; i < array.length; i++) {
    if (temp[array[i]] === true) {
      return array[i];
    }
    temp[array[i]] = true;
  }
  return -1;
};

a = []
l = 5e6
// for(i = 0; i<l;i++){ a.push(Math.floor(Math.random()*l)); }
for(i = 0; i<l;i++){ a[i] = i+1; }
a[l-1] = 7

for(f of [firstDuplicateSet, firstDuplicateInArray, firstDuplicateNew])      {
  then = Date.now()
  i = f(a)
  now = Date.now()
  console.log(f.name ? f.name : '-')
  console.log('Len:', a.length)
  console.log('Value:'+i)
  console.log('Time:', now-then+'ms')
}

似乎比其他版本运行得更快。

【讨论】:

    【解决方案3】:

    如果快速处理时间是必须的,我认为在算法中花费一些内存是值得的:

    只需创建一个反向映射:一个与存储数字范围一样大的数组。然后,浏览输入数组并将每个数字对应的索引存储在反向映射中。当你发现这个号码已经被索引时,你得到了你的重复号码。

    【讨论】:

      【解决方案4】:

      function firstDuplicate(a) {
        r = new Set();
        for (e of a) {
          if (r.has(e)) return e;
          else r.add(e);
        }
        return -1;
      }

      我是这样解决的。

      【讨论】:

      • 我也解决了,做了比较。检查我的答案,这可能很有趣(:
      • 我添加了另一个选项,根据 JSBench 似乎性能更高。
      【解决方案5】:

      使用字典来存储键/值对,而不是使用 O(n) 运行时的 indexOf,键是数字,值是索引。它可以在 O(1) 时间内访问,您只需要通过数组一次。如果你的键有一个未定义的值,你知道你还没有看到它,否则,你找到的第一个具有实际值的键必须是第一个副本,并且该值是最小索引。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2013-06-05
        • 2019-11-08
        • 2014-03-30
        • 2021-09-03
        • 2020-03-15
        • 2022-10-21
        • 2020-11-28
        • 2015-04-18
        相关资源
        最近更新 更多