我在互联网上看到了这个问题。获取列表中仅存在一次的唯一数字,而列表中存在两次其他数字。数据很大并且包含大约一百万个未分类的数字,并且可能包含随机顺序的负数,其中所有数字都出现两次,除了一个只出现一次的数字。
my @array = (1,1,2,3,3,4,4)
输出:
2
列表中只有两个不重复。我尝试了我的解决方案。
my $unique;
$unique ^= $_ for(@array);
say $unique;
它不会对负数工作但速度很快。
我尝试了一个哈希,其中key是数字,value是它在列表中出现的次数。反转哈希值,然后打印值为1作为键,因为所有其他数字的值都是2,因为它们出现两次。哈希解决方案很慢,输入数百万的大数字,但适用于负数。
我尝试了将整个列表与制表符合并然后使用
的正则表达方式my $combined = join " ", @array;
$combined !~ (\d+).*$1;
say $1;
但我只得到列表的最后一个数字
有快速的方法吗?有没有使用正则表达式的想法?
编辑:重新标记标题以获得更好的答案
答案 0 :(得分:4)
处理此问题的标准方法是将其全部放入哈希值。
use v5.10;
use strict;
use warnings;
my @nums = (2..500_000, 500_002..1_000_000, 0..1_000_001);
my %count;
for (@nums) {
$count{$_}++
}
for (keys %count) {
say $_ if $count{$_} == 1;
}
但是,它很慢。
然后我想也许我可以避免不得不遍历哈希来找到单身......
my @nums = (2..500_000, 500_002..1_000_000, 0..1_000_001);
my %uniqs;
my %dups;
for (@nums) {
if( $uniqs{$_} ) {
delete $uniqs{$_};
$dups{$_} = 1;
}
elsif( !$dups{$_} ) {
$uniqs{$_} = 1;
}
}
print join ", ", keys %uniqs;
但那甚至更慢。
这是我提出的最快的事情,大约需要一半的时间。
use v5.10;
use strict;
use warnings;
my @nums = (2..500_000, 500_002..1_000_000, 0..1_000_001);
@nums = sort @nums;
say $nums[0] if $nums[0] != $nums[1];
for (1..$#nums-1) {
my($prev, $this, $next) = @nums[$_-1, $_, $_+1];
say $this if $prev != $this && $next != $this;
}
say $nums[-1] if $nums[-1] != $nums[-2];
通过对列表进行排序,您可以遍历它并检查给定条目的邻居是否重复。必须要小心第一个和最后一个元素。我把他们的检查放在循环之外,以避免每次迭代都运行一个特殊情况。
因为sort
是O(nlogn),随着数字列表变大,这个解决方案最终会慢于基于散列的解决方案,但在此之前你可能会耗尽内存。
最后,如果此列表很大,您应该考虑将其存储在数据库的磁盘上。然后,您可以避免使用内存,让数据库有效地完成工作。
答案 1 :(得分:4)
这似乎很快:
use v5.10; use strict; use warnings;
sub there_can_be_only_one {
my @counts;
$counts[ $_>=0 ? 2*$_ : (-2*$_)-1 ]++ for @{$_[0]};
$counts[ $_>=0 ? 2*$_ : (-2*$_)-1 ]==1 and return $_ for @{$_[0]};
return;
}
my @array = (1,1,-4,-4,2,3,-1,3,4,-1,4);
say there_can_be_only_one(\@array);
它基本上是散列技术的变体,但使用数组而不是散列。因为我们需要处理负数,所以我们不能在@counts
数组中不加修改地使用它们。当然,负索引在Perl中起作用,但它们会覆盖我们的正数索引数据。失败。
所以我们使用类似于两个补码的东西。我们将数组中的正数存储为2*$_
,将负数存储为(-2*$_)-1
。那就是:
Integer: ... -3 -2 -1 0 1 2 3 ...
Stored as: ... 5 3 1 0 2 4 6 ...
因为这个解决方案不依赖于对列表进行排序,而只是对它进行两次传递(好吧,平均来说,一次半通过),它在 O(n)处执行与Schwern的 O(n log n)解决方案形成鲜明对比。因此,对于较大的列表(几百万个整数)应该明显更快。这是我(相当低功耗)上网本的快速比较:
use v5.10; use strict; use warnings;
use Benchmark qw(timethese);
use Time::Limit '60';
sub tobyink {
my @counts;
$counts[ $_>=0 ? 2*$_ : (-2*$_)-1 ]++ for @{$_[0]};
$counts[ $_>=0 ? 2*$_ : (-2*$_)-1 ]==1 and return $_ for @{$_[0]};
return;
}
sub schwern {
my @nums = sort @{$_[0]};
return $nums[0] if $nums[0] != $nums[1];
for (1..$#nums-1) {
my($prev, $this, $next) = @nums[$_-1, $_, $_+1];
return $this if $prev != $this && $next != $this;
}
return $nums[-1] if $nums[-1] != $nums[-2];
}
my @input = (
1..2_000_000, # 1_000_001 only appears once
1..1_000_000, 1_000_002..2_000_000,
);
timethese(1, {
tobyink => sub { tobyink(\@input) },
schwern => sub { schwern(\@input) },
});
__END__
Benchmark: timing 1 iterations of schwern, tobyink...
schwern: 11 wallclock secs ( 8.72 usr + 0.92 sys = 9.64 CPU) @ 0.10/s (n=1)
(warning: too few iterations for a reliable count)
tobyink: 5 wallclock secs ( 5.01 usr + 0.08 sys = 5.09 CPU) @ 0.20/s (n=1)
(warning: too few iterations for a reliable count)
更新:在我的初步回答中,我错过了没有数字会出现两次以上的详细信息。我假设一些数字有可能出现三次或更多次。使用这个额外的细节,我们可以更快:
sub there_can_be_only_one {
my $tmp;
$tmp ^= $_>=0 ? 2*$_ : (-2*$_)-1 for @{$_[0]};
$tmp%2 ? ($tmp+1)/-2 : $tmp/2;
}
say there_can_be_only_one(\@array);
这比我最初的答案快了大约30%。
答案 2 :(得分:2)
它不适用于负数但速度很快。
实际上,如果你想让xor处理负数,你只需要对它们进行字符串化:
my @array = (-10..-7,-5..10,-10..10);
my $unique;
$unique ^= "$_" for @array;
say $unique;
输出
-6
做一些快速基准测试:
Benchmark: timing 100 iterations of schwern, there_can_be_only_one, tobyink, xor_string...
schwern: 323 wallclock secs (312.42 usr + 7.08 sys = 319.51 CPU) @ 0.31/s (n=100)
there_can_be_only_one: 114 wallclock secs (113.49 usr + 0.02 sys = 113.51 CPU) @ 0.88/s (n=100)
tobyink: 177 wallclock secs (176.76 usr + 0.14 sys = 176.90 CPU) @ 0.57/s (n=100)
xor_string: 98 wallclock secs (97.05 usr + 0.00 sys = 97.05 CPU) @ 1.03/s (n=100)
显示xor-ing字符串的速度比将数学平移与正数相差15%。
Schwern的解决方案带来了一个有趣的推论。他对列表进行了排序,然后搜索了所有独特的元素。
如果我们使用额外的信息,即在一对双重按钮中只有1个单例,我们可以通过进行成对比较来快速简化搜索,从而将我们的比较减少到4倍。
但是,我们可以通过二进制搜索做得更好。如果我们在已知匹配对之间的屏障上分隔列表,那么剩下的两个列表中的任何一个都包含我们的单例。我做了一些这个解决方案的基准测试,它比其他任何事情都快了几个数量级(当然):
use strict;
use warnings;
use Benchmark qw(timethese);
sub binary_search {
my $nums = $_[0];
my $min = 0;
my $max = $#$nums;
while ($min < $max) {
my $half = ($max - $min) / 2; # should always be an integer
my ($prev, $this, $next) = ($min+$half-1) .. ($min+$half+1);
if ($nums->[$prev] == $nums->[$this]) {
if ($half % 2) { # 0 0 1 1 2 2 3 ( half = 3 )
$min = $next;
} else { # 0 1 1 2 2 ( half = 2 )
$max = $prev - 1;
}
} elsif ($nums->[$this] == $nums->[$next]) {
if ($half % 2) { # 0 1 1 2 2 3 3 ( half = 3 )
$max = $prev;
} else { # 0 0 1 1 2 ( half = 2 )
$min = $next + 1;
}
} else {
$max = $min = $this;
}
}
return $nums->[$min];
}
sub xor_string {
my $tmp;
$tmp ^= "$_" for @{$_[0]};
}
sub brute {
my $nums = $_[0];
return $nums->[0] if $nums->[0] != $nums->[1];
for (1..$#$nums-1) {
my($prev, $this, $next) = @$nums[$_-1, $_, $_+1];
return $this if $prev != $this && $next != $this;
}
return $nums->[-1] if $nums->[-1] != $nums->[-2];
}
sub pairwise_search {
my $nums = $_[0];
for (my $i = 0; $i <= $#$nums; $i += 2) {
if ($nums->[$i] != $nums->[$i+1]) {
return $nums->[$i];
}
}
}
# Note: this test data is very specific and is intended to take near the maximum
# number of steps for a binary search while shortcutting halfway for brute force
# and pairwise
my @input = sort {$a <=> $b} (0..500_003, 500_005..1_000_000, 0..1_000_000);
#my @input = sort {$a <=> $b} (0..499_996, 499_998..1_000_000, 0..1_000_000);
timethese(1000, {
brute => sub { brute(\@input) },
pairwise => sub { pairwise_search(\@input) },
xor_string => sub { xor_string(\@input) },
binary => sub { binary_search(\@input) },
});
结果:
Benchmark: timing 1000 iterations of binary, brute, pairwise, xor_string...
binary: 0 wallclock secs ( 0.02 usr + 0.00 sys = 0.02 CPU) @ 62500.00/s (n=1000)
(warning: too few iterations for a reliable count)
brute: 472 wallclock secs (469.92 usr + 0.05 sys = 469.97 CPU) @ 2.13/s (n=1000)
pairwise: 216 wallclock secs (214.74 usr + 0.00 sys = 214.74 CPU) @ 4.66/s (n=1000)
xor_string: 223 wallclock secs (221.74 usr + 0.06 sys = 221.80 CPU) @ 4.51/s (n=1000)