112.Remove Duplicates from Sorted List-删除排序链表中的重复元素(容易题)

本文介绍了一种算法,用于删除已排序链表中的重复元素,确保每个元素仅出现一次。通过提供示例和代码实现,展示了如何高效地解决这一问题。

删除排序链表中的重复元素

  1. 题目

    给定一个排序链表,删除所有重复的元素每个元素只留下一个。

  2. 样例

    给出 1->1->2->null,返回 1->2->null

    给出 1->1->2->3->3->null,返回 1->2->3->null

  3. 题解

/**
 * Definition for ListNode
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public static ListNode deleteDuplicates(ListNode head) { 
        ListNode dummy = new ListNode(0);
        dummy.next = head;

        while (dummy.next != null)
        {
            int nextValue = dummy.next.val;
            int curValue = dummy.val;
            if (curValue == nextValue)
            {
                dummy.next = dummy.next.next;
            }
            else
            {
                dummy = dummy.next;
            }
        }

        return head;
    }  
}

Last Update 2016.9.6

<think>我们正在处理一个关于Python删除排序重复元素的问。用户希望了解如何删除排序后列表中的重复元素。 根据引用,我们可以总结几种方法: 1. 使用排序后相邻比较的方法(引用1和引用2) 2. 使用集合去重但不保持顺序(引用3提到需要保持顺序时使用另一种方法) 3. 使用双指针(快慢指针)方法,特别适用于已排序的数组(引用2) 但是注意:用户要求的是“删除排序重复元素”,这可以理解为: a.排序,然后删除重复元素(这样重复元素会相邻) b. 删除重复元素后,列表是有序的 由于引用2中提到了一个重要的方法:使用快慢指针删除排序数组中的重复项(原地删除,且要求数组已排序),这可能是用户需要的。 另外,引用3中提到了如何删除重复元素并保持相对顺序(不一定是排序顺序,而是原始顺序),但用户要求的是排序删除重复元素,所以排序后原始顺序已经改变,我们只需要保证删除重复后仍然有序。 因此,我们可以提供两种思路: 1.排序,然后使用双指针方法(快慢指针)原地删除重复元素(适用于列表可变,且希望原地修改)。 2. 利用集合去重,然后重新排序(但这样会丢失原有顺序,不过我们排序重复元素相邻,所以去重后排序可能和原排序后去重结果一样,但注意集合去重后顺序是任意的,所以需要重新排序)。 但是,注意:如果先排序,然后使用集合去重,再排序?这样多此一举。因为排序重复元素相邻,我们可以直接使用双指针或者使用groupby等方法来去重。 另外,引用5提到使用`sorted(list(set(nums)))`,但这会改变原列表的顺序(因为集合无序,所以需要重新排序),并且它不是原地修改。 根据用户需求,我们可能更关注在排序后的列表上删除重复元素,并保持有序(也就是删除重复元素后仍然有序)。 方法1(双指针,原地修改): 步骤: a. 先对列表排序(如果未排序) b. 使用两个指针,慢指针指向当前不重复部分的最后一个位置,快指针遍历整个列表。 c. 比较快指针和慢指针指向的元素,如果不同,则慢指针前移,并将快指针的值赋给慢指针的位置。 方法2(使用itertools.groupby): 使用groupby将连续相同的元素分组,然后取每个组的第一个元素。 方法3(使用set,但会打乱顺序,所以需要重新排序): 先转成set去重,然后排序。但这样无法保持原排序后的相邻顺序(因为set无序),所以需要再排序。 注意:方法1和方法2都要求先排序,然后它们都可以保持排序后的顺序(即删除重复后仍然有序)。 我们分别给出示例代码: 方法1:双指针(原地修改,适用于可变列表,且希望节省空间) 注意:这个方法要求列表已经排序。如果未排序,先排序。 方法2:使用groupby(生成新列表) 代码更简洁,但不是原地修改。 根据引用2,我们有一个现成的双指针实现(用于已排序数组): class Solution: def removeDuplicates(self, nums: List[int]) -> int: if not nums: return 0 slow = 0 for fast in range(1, len(nums)): if nums[slow] != nums[fast]: slow += 1 nums[slow] = nums[fast] return slow + 1 然后,我们可以通过切片返回去重后的部分:nums[:slow+1] 但是,如果用户希望得到一个函数,输入一个列表(可能未排序),输出排序后并去重的列表,我们可以: 步骤1:排序 步骤2:去重(使用双指针或groupby) 另外,引用3中提到了保持相对顺序(原始顺序)的去重,但这里我们不需要,因为排序后原始顺序已经改变。 因此,我们提供以下函数: 函数1:使用双指针(原地修改,但排序也会改变原列表) 函数2:使用groupby(返回新列表) 我们分别实现: 方法1(双指针): 注意:这个函数会修改原列表,使其变为排序去重后的列表(前部分),并返回去重后的长度,但通常我们可能想要整个去重后的列表。所以我们可以返回去重后的切片。 但是,如果我们希望返回整个新列表(不改变原列表),我们可以先排序,然后使用双指针生成一个新列表?或者直接使用groupby。 更通用的做法是:返回一个新的排序去重后的列表,不改变原列表。 我们可以: def remove_duplicates_sorted(lst): if not lst: return [] lst_sorted = sorted(lst) # 先排序,生成一个新列表 # 使用双指针去重(在新列表上操作,但双指针通常用于原地,我们可以模拟) # 或者,我们也可以直接使用groupby # 这里我们用双指针方法生成一个新列表 result = [lst_sorted[0]] for i in range(1, len(lst_sorted)): if lst_sorted[i] != lst_sorted[i-1]: result.append(lst_sorted[i]) return result 或者更高效的双指针(避免频繁append,但这样写已经可以)? 但是,注意:双指针去重通常用于原地,我们这里生成新列表,所以直接用一个新列表存储即可。 另一种写法(类似快慢指针,但不需要移动指针,只需要遍历): 实际上,我们只需要比较当前元素和前一个元素是否相同。 方法2(使用groupby): from itertools import groupby def remove_duplicates_sorted2(lst): lst_sorted = sorted(lst) return [k for k, g in groupby(lst_sorted)] 方法3(使用set去重再排序): def remove_duplicates_sorted3(lst): return sorted(set(lst)) 注意:方法3是最简洁的,而且效率也不低(对于小列表,set操作很快,排序也是O(n log n))。但是,方法3会打乱原始元素在排序后出现的顺序吗?不会,因为set是无序的,我们重新排序了。所以结果也是排序去重的。 那么,这三种方法有什么区别? 方法1和方法2会保持排序后的连续相同元素的第一个出现,并且方法1和方法2的结果都是按照排序后的顺序,逐个保留第一次出现的元素。方法3也是排序后的顺序。 但是,方法3的效率:set去重O(n)排序O(n log n)。而方法1和方法2:先排序O(n log n),然后去重O(n)。所以总时间复杂度相同。 方法3的代码最简洁,通常推荐使用,除非有特殊要求(比如要求原地修改,或者要求不使用额外空间)。 然而,注意引用5中提到:使用sorted(set(nums))不会改变原始列表,而且需要额外的空间。 所以,如果用户希望原地修改列表,我们可以使用双指针原地去重(但前提是列表已经排序,如果没有排序,需要先原地排序,然后再原地去重)。 因此,我们提供两个版本的函数: 版本A:不修改原列表,返回新列表(推荐): def remove_duplicates_sorted(lst): return sorted(set(lst)) 版本B:原地修改列表(先排序,然后使用双指针去重,并删除多余元素): def remove_duplicates_in_place(lst): if not lst: return lst.sort() # 原地排序 slow = 0 for fast in range(1, len(lst)): if lst[fast] != lst[slow]: slow += 1 lst[slow] = lst[fast] # 现在,去重后的元素在 lst[0:slow+1] # 删除多余元素 del lst[slow+1:] 但是,注意:原地修改会改变原列表,并且效率上,排序是O(n log n),去重是O(n)删除多余元素是O(n)(因为需要移动元素?实际上,删除列表末尾元素是很快的,因为不需要移动后面的元素,但是这里我们删除的是从slow+1到末尾,所以需要将后面的元素都删除,这个操作是O(1)?实际上,列表的删除操作需要调整列表大小,但具体时间取决于删除的数量,不过这里我们只做一次删除,所以可以接受)。 根据用户需求,用户可能只是想要一个函数,所以我们可以提供这两种选择。 另外,引用1中提到了使用排序后从末尾比较相邻元素并删除的方法,但那种方法效率较低(因为每次删除元素都会导致列表调整,时间复杂度为O(n^2)),所以我们不推荐。 因此,我们推荐: 如果不需要原地修改,使用`sorted(set(lst))`是最简单的方法。 如果需要原地修改,则使用先排序再用双指针最后删除多余元素的方法。 下面我们给出代码示例:</think>在Python中,删除排序后列表中的重复元素,主要有以下几种高效方法(均保持排序后顺序): ### 方法1:使用双指针(原地修改 - 推荐) 适用于已排序列表,空间复杂度$O(1)$,时间复杂度$O(n)$ ```python def remove_duplicates(nums): if not nums: return 0 slow = 0 for fast in range(1, len(nums)): if nums[fast] != nums[slow]: slow += 1 nums[slow] = nums[fast] return nums[:slow+1] # 返回去重后的部分 # 示例 sorted_list = [1, 2, 2, 3, 4, 4, 5] result = remove_duplicates(sorted_list) print(result) # 输出: [1, 2, 3, 4, 5] ``` ### 方法2:使用集合与排序(生成新列表) 适用于未排序列表,时间复杂度$O(n \log n)$ ```python def remove_duplicates_sort(lst): return sorted(set(lst)) # 示例 unsorted_list = [3, 2, 2, 1, 4, 3] result = remove_duplicates_sort(unsorted_list) print(result) # 输出: [1, 2, 3, 4] ``` ### 方法3:使用itertools.groupby(保持首次出现顺序) ```python from itertools import groupby def remove_duplicates_groupby(lst): lst.sort() # 先排序 return [k for k, _ in groupby(lst)] # 示例 sorted_list = [1, 1, 2, 3, 3, 3, 4] result = remove_duplicates_groupby(sorted_list) print(result) # 输出: [1, 2, 3, 4] ``` ### 关键区别说明 | 方法 | 输入要求 | 是否原地修改 | 时间复杂度 | 最佳场景 | |--------------|--------------|--------------|-------------|-----------------------| | 双指针 | 已排序列表 | ✓ | $O(n)$ | 已排序的大数据列表 | | 集合+排序 | 任意列表 | ✗ | $O(n \log n)$ | 未排序的小列表 | | groupby | 任意列表 | ✗ | $O(n \log n)$ | 需保持元素首次出现顺序| > **推荐选择**: > 1. 当列表**已排序**时:使用双指针法(方法1) > 2. 当列表**未排序**时:使用集合+排序(方法2) > 3. 需要保持**原始首次出现顺序**时:使用groupby(方法3)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值