Java中查找列表中第一个非重复元素的4种方法

在本教程中,我们将探讨查找列表中第一个非重复元素的问题。我们将首先了解问题陈述,然后实施一些方法来实现预期结果。

给定一个元素列表,任务是找到列表中第一个不重复的元素。换句话说,我们需要识别列表中仅出现一次的第一个元素。如果没有非重复元素,我们将返回一个适当的指示,例如null。

1、使用for循环
此方法使用嵌套的for循来迭代列表并检查重复元素。它很简单,但效率较低。
首先,我们迭代输入列表中的每个元素。对于每个元素,我们通过再次迭代列表来检查它是否仅在列表中出现一次。如果发现某个元素重复,我们将标志isRepeating设置为true。如果发现某个元素不重复,则该方法返回该元素。

下面是上述想法的实现:

Integer findFirstNonRepeating(List<Integer> list) {
    for (int i = 0; i < list.size(); i++) {
        int current = list.get(i);
        boolean isRepeating = false;
        for (int j = 0; j < list.size(); j++) {
            if (i != j && current == list.get(j)) {
                isRepeating = true;
                break;
            }
        }
        if (!isRepeating) {
            return current;
        }
    }
    return null
}

让我们看一下示例列表:

[1, 2, 3, 2, 1, 4, 5, 4]
在第一次迭代期间,内部循环扫描整个列表以查找元素1的任何其他出现。它在索引4处找到元素1的另一个出现。由于元素1再次出现在列表中的其他位置,因此被视为重复。对元素2重复该过程。在第三次迭代中,它在列表中没有找到元素3的任何其他出现。因此,它被识别为第一个非重复元素,并且该方法返回3。

复杂性分析
设 n 为输入列表的大小。外循环对列表迭代一次,迭代次数为 O(n)。内循环在每次外循环迭代时也对列表进行一次迭代,因此每次外循环迭代的迭代次数为 O(n)。因此,总体时间复杂度为 O(n^2)。无论输入列表的大小如何,该方法都会使用恒定数量的额外空间。因此,空间复杂度为 O(1)。

这种方法为查找第一个非重复元素提供了一个直接的解决方案。不过,它的时间复杂度为 O(n^2),因此对于大型列表来说效率不高。

2、使用indexOf()和lastIndexOf()
indexOf ()方法检索元素第一次出现的索引,而lastIndexOf()返回最后一次出现的索引。通过比较列表中每个元素的这些索引,我们可以识别只出现一次的元素。

在迭代中,我们检查每个元素的第一次出现索引是否不等于其最后一次出现索引。如果它们不相等,则意味着该元素在列表中出现多次。如果找到具有相同首次出现索引和最后一次出现索引的元素,则该方法返回该元素作为第一个非重复元素:

Integer findFirstNonRepeatedElement(List<Integer> list) {
    for (int i = 0; i < list.size(); i++) {
        if (list.indexOf(list.get(i)) == list.lastIndexOf(list.get(i))) {
            return list.get(i);
        }
    }
    return null;
}

让我们看一下提供的示例列表:

[1, 2, 3, 2, 1, 4, 5, 4]
在初始迭代期间,indexOf(1)和lastIndexOf(1) 都返回0和4。他们不平等。这表明元素1是重复元素。对于后续元素2重复该过程。但是,当检查元素3时,indexOf(3)和lastIndexOf(3)都返回2。这个等式意味着元素3是第一个非重复元素。因此,该方法返回3作为结果。

复杂性分析
设 n 为输入列表的大小。该方法在列表中遍历一次。对于每个元素,它都会调用 indexOf() 和 lastIndexOf(),这两个函数可能会遍历列表以查找索引。因此,总体时间复杂度为 O(n^2)。这种方法会使用一定量的额外空间。因此,空间复杂度为 O(1)。

虽然这种方法提供了一种简洁的解决方案,但由于其时间复杂度为二次方(O(n^2)),因此效率很低。对于大型列表,尤其是重复调用 indexOf() 和 lastIndexOf() 时,这种方法可能比其他方法慢得多。

3、使用HashMap
另一种方式是,我们可以使用HashMap来统计每个元素的出现次数,然后找到第一个不重复的元素。这种方法比简单的for循环方法更有效。

在此方法中,我们迭代输入列表以计算每个元素的出现次数并将它们存储在 HashMap中。计算出现次数后,我们再次迭代列表并检查每个元素的计数是否等于1。如果任何元素的计数等于1,则返回该元素作为第一个非重复元素。如果迭代整个列表后没有找到非重复元素,则返回-1。

下面是上述想法的实现:

Integer findFirstNonRepeating(List<Integer> list) {
    Map<Integer, Integer> counts = new HashMap<>();
    for (int num : list) {
        counts.put(num, counts.getOrDefault(num, 0) + 1);
    }
    
    for (int num : list) {
        if (counts.get(num) == 1) {
            return num;
        }
    }
    
    return null;
}

让我们看一下提供的示例列表:

[1, 2, 3, 2, 1, 4, 5, 4]
第一次迭代后的计数将为:

{1=2, 2=2, 3=1, 4=2, 5=1}
迭代列表时,1和2的计数大于1,因此它们不是不重复的。元素3的计数为1,因此它是第一个非重复元素。

复杂性分析
假设 n 是输入列表的大小。计算列表中每个元素的出现次数需要 O(n) 次。遍历列表找到第一个不重复的元素也需要 O(n) 次。因此,总体时间复杂度为 O(n)。这种方法使用的额外空间与输入列表中唯一元素的数量成正比。在所有元素都是唯一的最坏情况下,空间复杂度为 O(n)。

这种方法提供了一种高效的解决方案,可以为各种输入数据查找列表中第一个不重复的元素。它利用 HashMap 来跟踪元素的出现,与传统的 for 循环方法相比,大大提高了性能。

4、 使用数组作为频率计数器
该方法使用数组作为频率计数器来计算每个元素的出现次数并找到第一个非重复元素。

首先,我们初始化一个大小为maxElement + 1的数组频率,其中maxElement是列表中的最大元素。我们迭代列表,并为每个元素num,增加Frequency[num]。此步骤确保Frequency存储元素i出现的次数。

接下来,我们再次遍历列表。对于每个元素num,我们检查Frequency[num]是否等于1。如果频率[num]为1,我们返回num,因为它是第一个非重复元素:

Integer findFirstNonRepeating(List<Integer> list) {
    int maxElement = Collections.max(list);
    int[] frequency = new int[maxElement + 1];
    for (int num : list) {
        frequency[num]++;
    }
    
    for (int num : list) {
        if (frequency[num] == 1) {
            return num;
        }
    }
    return null;
}

让我们看一下提供的示例列表:

[1, 2, 3, 2, 1, 4, 5, 4]
我们初始化频率数组,将所有元素设置为零:

[0, 0, 0, 0, 0, 0]
我们迭代列表:

Increment frequency[1] to 2.
Increment frequency[2] to 2.
Increment frequency[3] to 1.
Increment frequency[4] to 2.
Increment frequency[5] to 1.
接下来,我们再次迭代列表,频率[1]和频率[2]的值为2,因此它不是不重复的。对于Frequency[3],该值等于1,因此该方法返回3。

复杂性分析
假设 n 是输入列表的大小。我们对列表进行两次迭代,但每次迭代的时间复杂度为 O(n)。这种方法比 HashMap 方法更节省内存,空间复杂度为 O(maxElement)。

当列表中元素的范围较小时,这种方法尤其有效,因为它避免了散列的开销,并提供了更直接的实现方法。但是,如果输入列表中包含负数或零,频率数组就必须覆盖整个可能的元素范围,包括负数(如果适用)。