有一个任意类型元素的列表L,如何高效地删除其中所有重复的元素?顺序必须保留
仅需要算法,不允许导入任何外部库。
有一个任意类型元素的列表L,如何高效地删除其中所有重复的元素?顺序必须保留
仅需要算法,不允许导入任何外部库。
假设顺序很重要:
在Python中:
>>> L = [2, 1, 4, 3, 5, 1, 2, 1, 1, 6, 5]
>>> S = set()
>>> M = []
>>> for e in L:
... if e in S:
... continue
... S.add(e)
... M.append(e)
...
>>> M
[2, 1, 4, 3, 5, 6]
如果顺序不重要:
M = list(set(L))
首先,我们需要确定一些假设,即equals和hash函数之间存在关系。什么意思呢?我指的是对于源对象S的集合,给定任意两个元素x1和x2,存在一个(哈希)函数F使得:
if (x1.equals(x2)) then F(x1) == F(x2)
Java中有这样的关系,它允许您将查重作为接近 O(1) 的操作进行检查,从而将算法简化为简单的 O(n) 问题。如果顺序不重要,那么它只需要一行代码:
List result = new ArrayList(new HashSet(inputList));
如果顺序很重要:
List outputList = new ArrayList();
Set set = new HashSet();
for (Object item : inputList) {
if (!set.contains(item)) {
outputList.add(item);
set.add(item);
}
}
你会注意到我说的是“近似O(1)”。这是因为像Java HashMap或HashSet这样的数据结构依赖于一种方法,在其中使用哈希码的一部分来查找存储后备中的元素(通常称为bucket)。桶的数量是2的幂。这样可以轻松地计算出列表中的索引。hashCode()返回一个int。如果你有16个buckets,你可以通过将hashCode与15进行AND运算来找到要使用哪一个,得到0到15之间的数字。public class A {
private final int a;
A(int a) { this.a == a; }
public boolean equals(Object ob) {
if (ob.getClass() != getClass()) return false;
A other = (A)ob;
return other.a == a;
}
public int hashCode() { return 7; }
}
这段代码是完全合法的,它满足等式哈希码契约。
假设您的集合仅包含A实例,那么插入/搜索操作将变成O(n)操作,将整个插入操作转化为O(n2)。
显然,这是一个极端的例子,但指出这样的机制也依赖于在地图或集使用的值空间内散列的相对良好分布是有用的。
最后,必须说这是一种特殊情况。如果您正在使用没有此类“哈希快捷方式”的语言,则情况不同。
如果列表中不存在排序函数,则只能使用O(n2)暴力比较每个对象与其他对象。所以在Java中:
List result = new ArrayList();
for (Object item : inputList) {
boolean duplicate = false;
for (Object ob : result) {
if (ob.equals(item)) {
duplicate = true;
break;
}
}
if (!duplicate) {
result.add(item);
}
}
如果存在排序函数(例如,对于整数或字符串列表),则可以对列表进行排序(O(n log n)),然后将列表中的每个元素与下一个元素进行比较(O(n))。因此,总的算法复杂度为O(n log n)。Java代码如下:
Collections.sort(inputList);
List result = new ArrayList();
Object prev = null;
for (Object item : inputList) {
if (!item.equals(prev)) {
result.add(item);
}
prev = item;
}
注意:上述示例假设列表中没有空值。
e in S
、S.add
和 M.append
都是 O(1)。 - John La Rooyprev = item
可以提升到if
套件中。 - jfs>>> array = [1, 2, 2, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 5, 6, 6, 6, 6, 6, 6]
>>> unique = set(array)
>>> list(unique)
[1, 2, 3, 4, 5, 6]
在 Haskell 中,可以使用 nub
和 nubBy
函数来实现此功能。
nub :: Eq a => [a] -> [a]
nub [] = []
nub (x:xs) = x : nub (filter (/= x) xs)
nubBy :: (a -> a -> Bool) -> [a] -> [a]
nubBy f [] = []
nubBy f (x:xs) = x : nub (filter (not.f x) xs)
nubBy
函数减少了对 Eq
类型类的依赖,允许你定义自己的相等函数来过滤重复项。这些函数适用于一组一致的任意类型(例如在 Haskell 中不允许使用 [1,2,"three"]
),并且它们都是有序的。为了使其更加高效,可以使用 Data.Map(或实现平衡树)将数据收集到一个集合中(键为元素,值为原始列表中的索引,以便能够恢复原始顺序),然后将结果收集回列表并按索引排序。我稍后会尝试实现这个方法。import qualified Data.Map as Map
undup x = go x Map.empty
where
go [] _ = []
go (x:xs) m case Map.lookup x m of
Just _ -> go xs m
Nothing -> go xs (Map.insert x True m)
data Tree a = Empty
| Node a (Tree a) (Tree a)
deriving (Eq, Show, Read)
insert x Empty = Node x Empty Empty
insert x (Node a left right)
| x < a = Node a (insert x left) right
| otherwise = Node a left (insert x right)
lookup x Empty = Nothing --returning maybe type to maintain compatibility with Data.Map
lookup x (Node a left right)
| x == a = Just x
| x < a = lookup x left
| otherwise = lookup x right
nub [] = []
nub (x:xs) | elem x xs = nub (filter (/=x) xs)
| otherwise = x : nub xs
有趣的是,在第二次递归时,您不需要过滤重复项,因为elem已经检测到没有重复项。
nub
函数来移除两个元素呢?比如 [1,2,2,3] -> [1,3]
。 - Jonno_FTW>>> L = [2, 1, 4, 3, 5, 1, 2, 1, 1, 6, 5]
>>> a=[]
>>> for i in L:
... if not i in a:
... a.append(i)
...
>>> print a
[2, 1, 4, 3, 5, 6]
>>>
Set set = new LinkedHashSet(list);
该函数将返回一个去重后的集合。
对于Java,可以使用以下代码:
private static <T> void removeDuplicates(final List<T> list)
{
final LinkedHashSet<T> set;
set = new LinkedHashSet<T>(list);
list.clear();
list.addAll(set);
}
也就是说,我们不能使用set
(dict
)或sort
。
from itertools import islice
def del_dups2(lst):
"""O(n**2) algorithm, O(1) in memory"""
pos = 0
for item in lst:
if all(item != e for e in islice(lst, pos)):
# we haven't seen `item` yet
lst[pos] = item
pos += 1
del lst[pos:]
解决方案来自这里:
def del_dups(seq):
"""O(n) algorithm, O(log(n)) in memory (in theory)."""
seen = {}
pos = 0
for item in seq:
if item not in seen:
seen[item] = True
seq[pos] = item
pos += 1
del seq[pos:]
也就是说,我们可以使用sort
。这种解决方案不会保留原始顺序。
def del_dups3(lst):
"""O(n*log(n)) algorithm, O(1) memory"""
lst.sort()
it = iter(lst)
for prev in it: # get the first element
break
pos = 1 # start from the second element
for item in it:
if item != prev: # we haven't seen `item` yet
lst[pos] = prev = item
pos += 1
del lst[pos:]
我已经为字符串编写了一个算法。实际上,您拥有的类型并不重要。
static string removeDuplicates(string str)
{
if (String.IsNullOrEmpty(str) || str.Length < 2) {
return str;
}
char[] arr = str.ToCharArray();
int len = arr.Length;
int pos = 1;
for (int i = 1; i < len; ++i) {
int j;
for (j = 0; j < pos; ++j) {
if (arr[i] == arr[j]) {
break;
}
}
if (j == pos) {
arr[pos] = arr[i];
++pos;
}
}
string finalStr = String.Empty;
foreach (char c in arr.Take(pos)) {
finalStr += c.ToString();
}
return finalStr;
}
这取决于你对“高效”的理解。朴素算法的时间复杂度是O(n^2),我猜你实际上想要的是比这更低阶的算法。
正如Maxim100所说,你可以通过将列表与一系列数字配对来保留顺序,使用任何你喜欢的算法,然后将剩余部分重新排序回它们原来的顺序。在Haskell中,它看起来像这样:
superNub :: (Ord a) => [a] -> [a]
superNub xs = map snd
. sortBy (comparing fst)
. map head . groupBy ((==) `on` snd)
. sortBy (comparing snd)
. zip [1..] $ xs
Data.List.sort
也只有20行Haskell代码。请参见http://www.haskell.org/ghc/docs/latest/html/libraries/base-4.4.0.0/src/Data-List.html#sort - u0b34a0f6ae
Data.List.nub
不够高效。 - dfeuer