【问题标题】:Find one unique element in List with duplicates in Java在 Java 中查找 List 中具有重复项的唯一元素
【发布时间】:2017-11-02 13:02:44
【问题描述】:

如果整数(或其他对象)像这样,我们有列表:

[0, 0, 1, 1, ... , 777777, ... , 999999, 999999]

Java 代码:

List<Integer> ints = new LinkedList<Integer>();
for (int i = 0; i < 999999; i++) {
    ints.add(i); // add first value
    if (i!=777777){
        ints.add(i); // add duplicate value in not '5'
    }
}

我就是这样解决这个问题的:

Integer unique = null;
while (true) {
    unique = ints.get(0);
    ints.remove(0);
    if (ints.contains(unique)) {
        ints.remove(ints.indexOf(unique));
    } else {
        break;
    }
}
System.out.println(unique);// 777777

大约需要 30 毫秒。使用 ArrayList,它的工作时间会更长。 问题是如何以最佳(正确/优雅/最快)的方式从此列表中获取一个唯一值(例如 777777)。

【问题讨论】:

  • 1.对列表进行排序 2. 迭代并检查每个项目与上一个和下一个项目是否是唯一的。
  • 如果您知道元素是成对出现的(唯一的除外),请使用二分搜索来查找这些对出现在偶数索引和奇数索引之间的边界,以及它们进入的位置奇数,然后是偶数索引。与ArrayList 一起工作很快。
  • 相等的元素是否总是连续出现在列表中?
  • 优雅和快速是有区别的。你喜欢哪个?在我耳边,30 毫秒听起来没什么问题。

标签: java arrays unique


【解决方案1】:

您的实现效率非常低,因为您在循环中运行诸如ints.contains(unique)ints.indexOf(unique) 之类的线性时间操作。因此,在最坏的情况下,您的运行时间是 O(n^2)

如果数字总是排序的(就像在您的示例中一样),您可以遍历列表的元素一次,找到不等于 list.get(i-1)list.get(i+1) 的元素 list.get(i)。这需要O(n)

如果数字并非总是排序,您可以在O(nlogn) 中对它们进行排序并像以前一样继续。

或者您可以遍历数字一次,计算每个数字的出现次数(您可以将计数存储在 HashMap 中),然后找到计数为 1 的数字。这将采用 O(n)输入列表是否已排序。

【讨论】:

    【解决方案2】:

    除了其他答案给出的Set 解决方案之外,如果您知道您的重复元素只出现两次(或偶数次)并且只有一个非-重复数字,您可以对所有值进行异或运算以找到唯一的值。

    public int findUnique(int[] nums) { // Or (List<Integer> nums), it works also
        int xor = 0;
        for(int i : nums){
            xor ^= i;
        }
        return xor;
    }
    

    此解决方案比其他解决方案更快、更短。


    你也可以用 Java 8 风格来做,但当然会慢一些:

    public int findUnique(int[] nums) {
        return Arrays.stream(nums)
                .reduce((acc,i) -> acc ^= i)
                .orElseThrow(IllegalArgumentException::new);
    }
    

    【讨论】:

    • 好的,所以这可能不是最易读的解决方案,但它的时间复杂度是 O(n),除了簿记之外没有空间复杂度(本例中为 xor 和 i)。我今天在一次采访中提出了这个问题,我认为这是一个非常糟糕的解决方案。
    【解决方案3】:

    您使用了错误的集合。 List 不是此问题中的正确集合。
    您应该使用 HashSet 解决此问题。

    private void find(List<Integer> ints){
       HashSet<Integer> set = new HashSet<>();
       for(int i=0;i<ints.size();i++){
           if(set.contains(i)){
              set.remove(i);
           }else{
              set.add(i);
           }
       }
       Iterator<Integer> itr = set.iterator();
       if(itr.hasNext()){
          System.out.println("Unique value is :"+itr.next());
       }else{
          System.out.println("There is no duplicated value");
       }
    }
    

    此方法仅适用于 2 和 1 计数列表项。如果您有 3 次相同的数字,这种方法会发现它是重复的。

    排序可能是答案,但排序最多有 O(logn),之后 O(n) 用于查找重复的元素。如果您可以在创建列表时创建哈希集,则此方法将重复元素查找为 O(1)。

    【讨论】:

      【解决方案4】:

      先获取唯一元素,然后使用 collections api 的频率方法获取重复的出现次数

      Set<String> uniqueSet = new HashSet<String>(list);
      
      for (String temp : uniqueSet) {
          if(Collections.frequency(list, temp)==1) {
            System.out.println(temp);
          }
      
      }
      

      这将返回列表中的唯一元素。

      【讨论】:

      • 好主意,但时间太长了。
      【解决方案5】:

      试试 jdk8 的流式 API 怎么样:

      import java.util.Arrays;
      import java.util.Collection;
      import java.util.Collections;
      import java.util.List;
      import java.util.function.Function;
      import java.util.stream.Collectors;
      
      public class FiindUnique {
      
          public static void main(String[] args) {
              System.out.println("Hello World");
              Collection<String> list = Arrays.asList("A", "B", "C", "D", "A", "B", "C");
      
              //solution 1
              List<String> distinctElements = list.stream().filter(name -> Collections.frequency(list, name) == 1).collect(Collectors.toList());
              System.out.println(distinctElements);
      
              //Solution 2
      
              List<String> distinctElements2 = list.stream()
                  .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
                  .entrySet()
                  .stream()
                  .filter(e -> e.getValue() == 1)
                  .map(e -> e.getKey())
                  .collect(Collectors.toList());
               System.out.println(distinctElements2);
          }
      }
      

      【讨论】:

      • 解决方案 1 看起来非常好,但需要太多时间。第二个工作更快。
      【解决方案6】:

      通过二分查找找到反转点,即唯一编号之前的偶数和奇数索引将具有相同的编号,并且在唯一编号之后,奇数和偶数将具有相同的编号,因此我们可以进行二分查找要找到反演点,时间复杂度将为 log(N)

      【讨论】:

        【解决方案7】:

        您是否假设数组已排序? 这应该会更快。

        HashSet<Integer> s = new HashSet<Integer>();
        
        for ( Integer i : ints){
            if ( s.contains(i)){
                s.remove(i); // remove dups
            }
            else{
                s.add(i); //add uniques
            }
        }
        
        for ( Integer unique: s){
        // should contain one value
            System.out.println(unique);// 777777
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-06-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-03-28
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多