Leetcode: Remove Duplicates from Sorted Array II
发布日期:2025-04-05 03:41:32 浏览次数:9 分类:精选文章

本文共 7232 字,大约阅读时间需要 24 分钟。

测试交集数组的处理问题:超时代码解析与优化

###问题描述大家可能都知道,常见的数组去重问题是这样的:给定一个数组,去除重复元素,保留唯一值。比如说给定数组是[1,1,1,2,2,3],去重后的数组就是[1,2,3]。现在,假设我们需要满足一个更灵活的需求:允许每个元素最多保留两次,也就是说在去重时每个元素可以出现两次。那么,如何处理这样的情况呢?

简单来说,给定一个已经排序好的数组A = [1,1,1,2,2,3],我的任务是让最终的数组长度变成5,比如变成[1,1,2,2,3]。这种情况下的解决方案需要充分考虑元素的多次保留需求。

###问题分析这个问题跟传统的去重问题有点不同,因为允许某些元素保留两次。所以,我的初步想法是:如何在遍历数组的过程中,记录每个元素的出现次数,并在达到两次的时候停止记录这个元素的出现?

首先,我需要了解数组的元素及其出现的频率。例如,在数组[1,1,1,2,2,3]中:

  • 1出现3次
  • 2出现2次
  • 3出现1次

我的目标是让这些元素在Output数组中最多出现两次。那么,1这个元素应该只保留2次,2保留2次,3保留1次。最终,Output数组应该是[1,1,2,2,3]。

接下来的任务是,如何根据这些信息来写出一个高效的解决方案?

###我的解决方案我觉得有两种思路可以实现这个目标:一种是基于频率统计的方式,另一种是基于滑动窗口的方式。

具体来说,基于频率统计的方式可以通过先统计数组中每个元素的出现次数,然后遍历数组,记录那些不超过两次的元素。这种方法时间复杂度是O(n),因为只需要遍历数组两次:一次统计,一次输出。这也是非常高效的一种方法。

另一种方式是基于滑动窗口的技术。这种方法的灵感来自于两次滑动窗口,可以更灵活地控制元素的数量。这种方法的时间复杂度同样是O(n),也是一种不错的选择。

这里,我们可以先尝试基于频率统计的方式来编写代码。步骤大致如下:

  • 创建一个频率数组或哈希表,记录每个元素的出现次数。
  • 确定哪些元素的出现次数超过两次,并记录这些元素的位置。
  • 然后,遍历原数组,保留那些符合频率要求的元素。
  • 最后,调整频率超过两次的元素,只保留两次。
  • ###具体实现代码编写代码之前,我们需要明确一下具体的实现步骤。让我尝试写出代码的大致框架:

    public class Solution {    public int removeDuplicates(int[] A) {        if (A == null || A.length == 0) return 0;        int len = 1;        int count = 1;        for (int i = 1; i < A.length; i++) {            if (A[i] == A[i-1] && count < 2) {                continue;            } else {                len++;                count = 1;            }        }        return len;    }}

    不过,这段代码好像哪里不太对。让我仔细检查一下。

    哦,发现了问题,这段代码只计算了连续重复元素的情况,并没有处理元素的所有出现次数。比如,1出现了3次的情况下,中间的1可能会被多次跳过,从而导致最终数组长度不正确。

    看来,我需要更仔细地考虑整个逻辑。

    让我们从头开始stitial步骤:

  • 初始化一个变量来记录当前元素的位置,以及前一个相同的元素的位置。
  • 对于当前元素,如果是重复元素,则根据允许的次数来决定是否记录它。
  • 如果当前元素已经出现了一次,且允许两次,那么记录当前元素,然后继续下一个元素。
  • 如果超过两次,停止记录该元素。
  • 看起来,这样的逻辑是错误的,因为它没有考虑所有的情况。

    那我应该怎么处理多个重复元素的情况呢?

    也许可以借助一个辅助数组,记录每个元素的出现次数。例如,我们可以创建一个叫做cnt的数组,或者使用一个哈希表,这样可能会更简单一些。

    假设我选择使用哈希表来记录每个元素的出现次数:

    步骤如下:

  • 遍历一遍数组,统计每个元素的出现次数。
  • 再次遍历数组,记录那些出现次数不超过两次的元素。
  • 最后,返回结果数组的长度。
  • 这种方法的写法更为直接,但并不一定比两次遍历数组的方法更有效率。

    总的来说,基于频率统计的方式和基于滑动窗口的方式都可以实现。在这种情况下,我倾向于基于频率统计的方式,因为这可以更直观地处理重复元素的计数问题。

    让我尝试写出基于频率统计的具体代码:

    public class Solution {    public int removeDuplicates(int[] A) {        if (A == null || A.length == 0) return 0;        int prev = A[0];        int count = 1;        int len = 1;        for (int i = 1; i < A.length; i++) {            if (A[i] == prev) {                count++;            } else {                // 如果是新元素,可以继续记录                if (count > 2) {                    // 如果前面元素已经记录了两次,那么就不得不跳过                    // 但是这需要结合后面的元素进行处理                    // 这是比较复杂的部分,可能需要更多的记录                    // 或者采用其他方式                }                len += 1;                prev = A[i];                count = 1;            }        }        return len;    }}

    不过这段代码还是有问题的,特别是当遇到多个重复元素时,处理方式可能不够明确。

    或许我应该重新考虑另一种方法:即在数组中找到每个元素的实际上限出现次数,并记录这些值。

    也就是说,我需要一个方法来记录:每个元素最多只能出现在两次的位置。

    这样,我就可以在遍历数组时,跳过那些超出次数限制的元素。

    这样,实现起来可能会更加简单。

    举个例子,假设数组是[1,1,1,2,2,3],那么我需要知道每个元素的位置,并且保证每个元素最多被取出两次。

    我想到了这样的逻辑:

  • 一、先遍历数组,统计每个元素的出现次数。
  • 二、记录这些元素的值和它们的位置。
  • 三、然后,根据每个元素的次数,设置允许的位置范围。
  • 四、最后,再遍历数组,保留符合次数限制的元素。
  • 这可能是一种比较系统的方法,可以确保元素的数量符合要求。

    例如,在原数组中,1出现3次,2出现2次,3出现1次。

    那么,在保留次数不超过两次的前提下:

    • 1可以被保留两次。
    • 2刚好可以保留两次。
    • 3保留一次。

    这样,最终数组长度为5。

    那我的代码该如何写呢?我来尝试一下:

    Step 1:记录元素的出现次数

    int[] count = new int[100]; // 假设数组中元素的范围是有限的for (int num : A) {count[num]++;}

    不过,如果数组中元素很大,比如有超过1000个不同的数,那么这样的数组大小可能不够。所以,更好的方式是使用 HashMap来记录元素及其出现次数:

    Map<Integer, Integer> frequency = new HashMap<>();for (int num : A) {frequency.put(num, frequency.getOrDefault(num, 0) + 1);}

    Step 2:确定每个元素的保留次数不超过两次的情况

    然后,我们需要确定哪些元素需要被保留两次,哪些可以被保留一次,或者更少。

    这可能比较复杂,特别是当数组中有多个重复元素时。

    也许,可以遍历一遍数组,记录每个元素的开始位置和结束位置。

    另一种思路是,直接在遍历过程中,记录那些符合次数要求的元素。

    也就是说,每当遇到一个新的元素时,都确定它是否可以被保留。不超过两次的情况下,可以保留它两次。

    但这个逻辑可能比较复杂。

    也许,采用辅助数组的方式更有助于实现。

    例如,可以创建一个结果数组result,来记录最终的输出数组。

    初始化result为大小为A.length的数组。

    然后,初始化count为0,记录每个元素已经出现的次数。

    然而,这样的方式可能需要多次遍历。

    也许,最有效率的方法是采用两次遍历数组的方式:

    第一遍:统计每个元素的出现次数;第二遍:在第二个遍历中,针对每个元素,如果它已经可以保留了,那么把它加到结果数组中。

    不过,对于允许最多两次保留的情况,这可能不会正确地控制元素的数量。

    或者,另一种思路是,记录已经保留了多少次每个元素。

    这可能更为直观。

    让我试着详细地描述一下:

    • 首先,遍历数组,计算出每个元素的出现次数。
    • 然后,再次遍历数组,每当碰到一个元素时,如果它已经被记录了两次,那么往后就不记录了。
    • 否则,就记录一次,并将计数器加一。

    这样,最终的输出数组中的每个元素最多保留两次。

    那这个过程具体该如何实现呢?

    举个例子,原数组是[1,1,1,2,2,3]。

    第一遍遍历:计算每个元素的出现次数:1: 3次2: 2次3: 1次

    第二遍遍历:初始化一个字典,将每个元素的次数记录下来。

    当再次遇到这些元素时,比如当又一次遇到1时,则可以记录两次。之后,第三次遇到1时,就不记录了。

    这样,处理后的数组就是[1,1,2,2,3]。

    那么,代码的大致逻辑就是:

    • 创建一个字典记录元素的出现次数,统计每个元素出现了多少次。
    • 然后,遍历数组,遇到每个元素时:
      • 如果字典中该元素的值已经达到了2次,就跳过。
      • 否则,将元素加入结果数组,并在字典中将该元素的计数加一。

    这将确保每个元素在结果数组中最多出现两次。

    那么,编写出对应的代码:

    public class Solution {public int removeDuplicates(int[] A) {if (A == null || A.length == 0) return 0;

    Map
    frequency = new HashMap<>(); List
    result = new ArrayList<>(); for (int num : A) { frequency.put(num, frequency.getOrDefault(num, 0) + 1); } for (int num : A) { if (frequency.get(num) > 2) { continue; } result.add(num); frequency.put(num, frequency.get(num) + 1); } return result.size();}

    }

    这样,代码就完成了任务,保留了最多两次各元素的情况。

    当然,这也存在不足之处。比如说,如果同一个数值在数组中是分散出现的,比如[1,2,1,3,1,4,1],那么这种方法也能正确地处理,把每个数值最多保留两次。

    但是,我发现这段代码有一些问题,比如假设数组中有重复的元素,那么它们的顺序会无法得到保证。这可能需要进一步优化。

    或者,我可以采用另一种策略:在处理过程中,记录已经添加了多少次每个数值,这样可以避免错误。

    比如,在第二遍遍历时,先查看该数值的总出现次数。如果已经达到了两次,就跳过;否则,把它加入结果数组,并将计数器加一。

    这样,既能保证每个元素在结果数组中不超过两次,也能保证元素的位置与原数组保持一致,最多只能保留两次。

    好的,现在我意识到自己的前面的代码逻辑存在一个问题:它可能会在第二遍遍历时,不断地添加同一个数,直到计数器达到两次。

    但如果数组中是这样的情况:一个数在数组中分散出现,比如说,1, 2, 1, 3, 1,4,1,那么,在两个遍历过程中,第二遍时它会被加入结果数组,直到计数器达到2次。这样处理后的结果数组将是 [1,2,1,3,1,4]。这样其实有问题,因为我们允许最多两次,但每个数中允许保留两次,不管它们的位置。

    另一种更好的处理方式是,在第二遍遍历时,防止重复添加同一个数值超过两次的同时,在遍历时记录元素是时间上的连续性,这可能需要更复杂的逻辑。

    或许,用双重遍历的方法并不是最优的手段。或者说,这个方法在某些情况下会丢失元素的位置信息,导致最终结果不符合预期。

    为了更好地处理连续出现重复元素的情况,我可能需要更复杂的逻辑。

    让我们来试着调整代码:

    在第二遍遍历中,我们需要保持一个计数器来记录每个数值在结果数组中已经被添加的次数。同时,我们也需要一个变量来记录当前正在处理的元素的状态。

    比如,遍历到一个数时,如果该数值的总次数不超过两次,则将它加入到结果数组,并将计数器加一。

    这样,就能保证每个数值在结果数组中只出现两次。

    比如,在原数组中是[1,1,1,2,2,3],那么第一次遍历后,frequency字典中记录1出现3次,2出现2次,3出现1次。然后,第二遍遍历时:

    • 第一个1,检查频率:3次,超过两次,不加入结果数组。
    • 第二个1,同样频率是3,不加入。
    • 第三个1,同样,不加入。
    • 第一个2,频率是2次,将结果数组加入2,频率加一,变成3次?哦,这里出现一个错误:频率字典的计数器会在这之后继续增加,但结果数组的计数器只能增加两次。不好意思,哪里出错了。

    在代码逻辑中,我们应该在遍历的时候,不仅检查该数值的总次数是否超过两次,还要检查它在结果数组中的出现次数是否已经达到了2次。如果出现次数未达2次,则加一,并且在可能的情况下,不超过两次。

    因此,正确的逻辑应该是:

    遍历数组时,对于每个元素num,

  • 检查它是否已经在结果数组中被添加了两次(为了实现这一点,可以使用一个额外的数组counters来记录每个数已经被加入到结果数组中的次数。
  • 如果counters[num] < 2,那么添加它,并将counters[num]加一。
  • 否则,跳过。
  • 这样,次数就会被正确地控制在两次以内。

    比如,修改后的代码:

    public class Solution {public int removeDuplicates(int[] A) {if (A == null || A.length == 0) return 0;

    Map
    frequency = new HashMap<>(); // 统计每个数的总出现次数 Map
    counters = new HashMap<>(); // 统计已经被加入到结果数组中的次数 List
    result = new ArrayList<>(); // 第一遍统计频率 for (int num : A) { frequency.put(num, frequency.getOrDefault(num, 0) + 1); } // 第二遍构建结果数组,并控制添加次数 for (int num : A) { if (counters.getOrDefault(num, 0) >= 2) { continue; } result.add(num); counters.put(num, counters.getOrDefault(num, 0) + 1); } return result.size();}

    }

    这样,我们就可以确保每个数在结果数组中最多出现两次。

    让我们用这个例子来测试一下:A = [1,1,1,2,2,3]

    frequency字典将是 {1: 3, 2: 2, 3: 1}

    然后,第二遍遍历:

    • num = 1: counters[1]=0 < 2,加入result,counters[1]=1
    • num=1: counters[1]=1 < 2,加入result,counters[1]=2
    • num=1: counters[1]=2,不加入
    • num=2: counters[2]=0 < 2,加入result,counters[2]=1
    • num=2: counters[2]=1 < 2,加入result,counters[2]=2
    • num=3: counters[3]=0 < 2,加入result,counters[3]=1

    结果数组将是 [1,1,2,2,3],长度为5,与预期一致。

    这样,这段代码是正确的,而且也能够处理其他测试案例。

    最终,我认为这样的逻辑是正确的,也能够满足题目中的双重要求。因此,这就是解决方案的关键点。希望这篇文章能帮助你更好地理解如何处理这样的数组去重问题!

    上一篇:Leetcode: Spiral Matrix II
    下一篇:Leetcode: Group Anagrams

    发表评论

    最新留言

    网站不错 人气很旺了 加油
    [***.192.178.218]2025年05月12日 10时39分46秒