(#数组/链表操作)合并两个有重复元素的无序数组,返回无重复的有序结果

合并、去重、排序三合一算法详解

一、问题理解

1. 任务要求

  1. 合并两个数组
  2. 去除重复元素
  3. 排序(不使用内置的 sort() 函数)
  4. 处理边界情况(空数组等)

2. 示例

python 复制代码
输入: nums1 = [3, 1, 2], nums2 = [2, 4, 3]
合并: [3, 1, 2, 2, 4, 3]
去重: [3, 1, 2, 4]  # 去掉了重复的2和3
排序: [1, 2, 3, 4]

二、完整代码实现

python 复制代码
def merge_and_unique(nums1, nums2):
    """
    合并两个数组,去重,然后排序(不使用sort函数)
    时间复杂度:O(n²)(冒泡排序部分)
    空间复杂度:O(n)
    """
    # 1. 合并两个数组
    merged = nums1 + nums2
    
    # 2. 去重(使用集合)
    unique = []        # 存储去重后的元素
    seen = set()       # 记录已经见过的元素
    
    for num in merged:
        if num not in seen:
            seen.add(num)
            unique.append(num)
    
    # 3. 排序(使用冒泡排序,不使用sort())
    n = len(unique)
    
    # 冒泡排序:外层循环控制排序轮数
    for i in range(n):
        # 内层循环:相邻元素比较
        # 每轮把最大的元素"冒泡"到最后
        for j in range(n - i - 1):
            if unique[j] > unique[j + 1]:
                # 交换两个元素
                unique[j], unique[j + 1] = unique[j + 1], unique[j]
    
    return unique

三、代码逐行详解

第一部分:合并数组

python 复制代码
# 方法1:直接相加(最简单)
merged = nums1 + nums2

# 方法2:使用extend(原地扩展)
merged = nums1.copy()  # 先复制,避免修改原数组
merged.extend(nums2)

# 方法3:使用列表推导式
merged = [x for x in nums1] + [x for x in nums2]

# 方法4:使用星号解包(Python 3.5+)
merged = [*nums1, *nums2]

第二部分:去重

python 复制代码
# 方法1:使用集合(推荐,O(1)查找)
unique = []
seen = set()  # 创建空集合

for num in merged:
    if num not in seen:    # 如果没见过这个数字
        seen.add(num)      # 记录到集合中
        unique.append(num) # 添加到结果列表

# 方法2:使用字典(类似集合)
unique = []
seen = {}  # 空字典

for num in merged:
    if num not in seen:
        seen[num] = True   # 值可以是任意值,我们只关心键
        unique.append(num)

# 方法3:直接使用set去重(但会失去顺序)
unique = list(set(merged))
# 注意:集合是无序的,所以顺序可能改变

为什么用集合而不用列表检查重复?

python 复制代码
# ❌ 用列表(慢!O(n)查找)
seen_list = []
for num in merged:
    if num not in seen_list:  # 每次都要遍历整个列表
        seen_list.append(num)

# ✅ 用集合(快!O(1)查找)
seen_set = set()
for num in merged:
    if num not in seen_set:   # 哈希查找,速度极快
        seen_set.add(num)

第三部分:冒泡排序详解

python 复制代码
# 冒泡排序思想:像水泡一样,大的元素慢慢"浮"到顶部
n = len(unique)

# 外层循环:控制排序轮数
# 每轮把一个最大的元素放到正确位置
for i in range(n):
    # 内层循环:比较相邻元素
    # n-i-1: 因为最后i个元素已经排好序了
    for j in range(n - i - 1):
        # 如果前一个元素比后一个大,交换它们
        if unique[j] > unique[j + 1]:
            # Python交换两个变量的简洁写法
            unique[j], unique[j + 1] = unique[j + 1], unique[j]
冒泡排序可视化
复制代码
初始数组: [5, 3, 8, 1]

第一轮 (i=0):
  比较 5和3: 5>3 → 交换 → [3, 5, 8, 1]
  比较 5和8: 5<8 → 不交换 → [3, 5, 8, 1]
  比较 8和1: 8>1 → 交换 → [3, 5, 1, 8]
  第一轮结束,8在正确位置

第二轮 (i=1):
  比较 3和5: 3<5 → 不交换 → [3, 5, 1, 8]
  比较 5和1: 5>1 → 交换 → [3, 1, 5, 8]
  第二轮结束,5在正确位置

第三轮 (i=2):
  比较 3和1: 3>1 → 交换 → [1, 3, 5, 8]
  第三轮结束,3在正确位置

最终: [1, 3, 5, 8]
为什么是 range(n - i - 1)
python 复制代码
# i=0 时: range(n-1)        # 比较0到n-2,共n-1次比较
# i=1 时: range(n-2)        # 比较0到n-3,共n-2次比较
# i=2 时: range(n-3)        # 比较0到n-4,共n-3次比较
# ...
# 因为最后i个元素已经排好序了,不需要再比较
元素交换的三种方法
python 复制代码
# 方法1:Python特有(推荐)
a, b = b, a

# 方法2:使用临时变量
temp = a
a = b
b = temp

# 方法3:使用加减法(仅限数字)
a = a + b
b = a - b
a = a - b

四、优化版本

1. 优化冒泡排序(提前结束)

python 复制代码
def bubble_sort_optimized(arr):
    """优化版冒泡排序:如果某轮没有交换,说明已经有序,提前结束"""
    n = len(arr)
    
    for i in range(n):
        swapped = False  # 标记本轮是否发生交换
        
        for j in range(n - i - 1):
            if arr[j] > arr[j + 1]:
                arr[j], arr[j + 1] = arr[j + 1], arr[j]
                swapped = True
        
        # 如果没有发生交换,说明已经有序,提前结束
        if not swapped:
            break
    
    return arr

2. 完整优化版函数

python 复制代码
def merge_and_unique_optimized(nums1, nums2):
    """
    合并两个数组,去重,优化版冒泡排序
    """
    # 1. 合并
    merged = nums1 + nums2
    
    # 2. 去重
    unique = []
    seen = set()
    
    for num in merged:
        if num not in seen:
            seen.add(num)
            unique.append(num)
    
    # 3. 优化版冒泡排序
    n = len(unique)
    
    for i in range(n):
        swapped = False
        
        for j in range(n - i - 1):
            if unique[j] > unique[j + 1]:
                unique[j], unique[j + 1] = unique[j + 1], unique[j]
                swapped = True
        
        # 如果没有交换,提前结束
        if not swapped:
            break
    
    return unique

五、使用快速排序(更高效)

1. 快速排序实现

python 复制代码
def quick_sort(arr):
    """快速排序实现,时间复杂度 O(n log n)"""
    if len(arr) <= 1:
        return arr
    
    # 选择基准值(这里选中间元素)
    pivot = arr[len(arr) // 2]
    
    # 分割数组
    left = [x for x in arr if x < pivot]
    middle = [x for x in arr if x == pivot]
    right = [x for x in arr if x > pivot]
    
    # 递归排序并合并
    return quick_sort(left) + middle + quick_sort(right)

def merge_and_unique_quick(nums1, nums2):
    """
    使用快速排序的版本(更高效)
    """
    # 1. 合并
    merged = nums1 + nums2
    
    # 2. 去重
    unique = []
    seen = set()
    
    for num in merged:
        if num not in seen:
            seen.add(num)
            unique.append(num)
    
    # 3. 快速排序
    return quick_sort(unique)

2. 快速排序原理

复制代码
快速排序步骤:
1. 选择一个"基准"元素(pivot)
2. 将数组分成三部分:小于基准、等于基准、大于基准
3. 递归地对小于和大于的部分进行排序
4. 合并结果

示例:对 [3, 6, 8, 1, 2] 排序
选择基准 6:
  小于6: [3, 1, 2]
  等于6: [6]
  大于6: [8]
递归排序 [3, 1, 2] 和 [8]
最终合并: [1, 2, 3] + [6] + [8] = [1, 2, 3, 6, 8]

六、边界情况处理

1. 空数组情况

python 复制代码
def merge_and_unique_safe(nums1, nums2):
    # 处理空数组
    if not nums1 and not nums2:
        return []
    
    # 合并(如果为空数组,+操作也能正常工作)
    merged = nums1 + nums2
    
    # 去重
    if not merged:
        return []
    
    unique = []
    seen = set()
    
    for num in merged:
        if num not in seen:
            seen.add(num)
            unique.append(num)
    
    # 排序
    if len(unique) <= 1:
        return unique  # 0或1个元素已经有序
    
    # 冒泡排序
    n = len(unique)
    for i in range(n):
        for j in range(n - i - 1):
            if unique[j] > unique[j + 1]:
                unique[j], unique[j + 1] = unique[j + 1], unique[j]
    
    return unique

2. 测试各种边界情况

python 复制代码
def test_edge_cases():
    test_cases = [
        ([], []),                    # 两个空数组
        ([1, 2, 3], []),             # 一个空数组
        ([], [4, 5, 6]),             # 另一个空数组
        ([1, 1, 1], [1, 1, 1]),      # 所有元素相同
        ([3, 2, 1], [1, 2, 3]),      # 反向重复
        ([9, 8, 7], [1, 2, 3]),      # 无重复
        ([], [1, None, 2]),          # 包含None(可能报错)
    ]
    
    for nums1, nums2 in test_cases:
        try:
            result = merge_and_unique_safe(nums1, nums2)
            print(f"{nums1} + {nums2} → {result}")
        except Exception as e:
            print(f"{nums1} + {nums2} → 错误: {e}")

七、常见错误与修正

错误1:忘记初始化集合

python 复制代码
# ❌ 错误:seen = () 创建的是元组,不是集合
seen = ()  # 这是空元组,不能add,不能检查成员

# ✅ 正确:
seen = set()  # 空集合

错误2:冒泡排序范围错误

python 复制代码
# ❌ 错误:范围太大,会导致索引越界
for j in range(n):  # j最大为n-1,但j+1会到n,越界
    if unique[j] > unique[j+1]:
        ...

# ✅ 正确:
for j in range(n - i - 1):  # j最大为n-i-2,j+1为n-i-1
    if unique[j] > unique[j+1]:
        ...

错误3:去重逻辑错误

python 复制代码
# ❌ 错误:先添加再检查
for num in merged:
    unique.append(num)  # 先添加
    if num not in seen: # 再检查(但已经添加了)
        seen.add(num)

# 结果:重复元素没有被去除!

# ✅ 正确:先检查再添加
for num in merged:
    if num not in seen:
        seen.add(num)
        unique.append(num)

八、复杂度分析

时间复杂度

  1. 合并:O(m+n),m和n是两个数组的长度
  2. 去重:O(m+n),每个元素检查一次
  3. 冒泡排序 :O(k²),k是去重后的元素数量
    • 最好情况:O(k)(优化版,数组已有序)
    • 最坏情况:O(k²)(数组完全逆序)
  4. 总时间复杂度:O(m+n + k²)

空间复杂度

  1. 合并:O(m+n),需要存储合并后的数组
  2. 去重:O(k),集合和结果列表
  3. 排序:O(1)(原地排序)或 O(k)(快速排序递归栈)
  4. 总空间复杂度:O(m+n)

九、替代方案比较

方法 优点 缺点 时间复杂度
冒泡排序 简单易懂,代码短 效率低(O(n²)) O(n²)
快速排序 效率高(O(n log n)) 递归可能栈溢出 O(n log n)
使用内置sort 最简单,效率高 不符合题目要求 O(n log n)
插入排序 简单,对小数组高效 效率低(O(n²)) O(n²)
选择排序 简单,交换次数少 效率低(O(n²)) O(n²)

十、完整测试用例

python 复制代码
def test_merge_and_unique():
    print("测试开始...")
    print("-" * 50)
    
    # 测试用例
    test_cases = [
        # (nums1, nums2, 期望结果)
        ([1, 2, 3], [2, 3, 4], [1, 2, 3, 4]),
        ([], [1, 2, 3], [1, 2, 3]),
        ([4, 5, 6], [], [4, 5, 6]),
        ([], [], []),
        ([1, 1, 1], [1, 1, 1], [1]),
        ([5, 3, 1], [2, 4, 6], [1, 2, 3, 4, 5, 6]),
        ([9, 8, 7], [6, 5, 4], [4, 5, 6, 7, 8, 9]),
        ([1, 3, 5], [2, 4, 6, 5, 3], [1, 2, 3, 4, 5, 6]),
    ]
    
    all_passed = True
    
    for nums1, nums2, expected in test_cases:
        result = merge_and_unique(nums1, nums2)
        
        if result == expected:
            print(f"✓ {nums1} + {nums2} → {result}")
        else:
            print(f"✗ {nums1} + {nums2} → {result} (期望: {expected})")
            all_passed = False
    
    print("-" * 50)
    if all_passed:
        print("所有测试通过!")
    else:
        print("有测试失败!")

# 运行测试
test_merge_and_unique()

十一、扩展:支持多种数据类型

python 复制代码
def merge_and_unique_general(nums1, nums2):
    """
    支持多种数据类型的通用版本
    """
    # 合并
    merged = nums1 + nums2
    
    # 去重(使用字典存储类型和值的组合)
    unique = []
    seen = set()
    
    for item in merged:
        # 为每个元素创建唯一的标识(处理不可哈希类型)
        try:
            # 如果元素可哈希,直接使用
            if item not in seen:
                seen.add(item)
                unique.append(item)
        except TypeError:
            # 如果元素不可哈希(如列表),转换为元组
            item_tuple = tuple(item) if isinstance(item, list) else item
            if item_tuple not in seen:
                seen.add(item_tuple)
                unique.append(item)
    
    # 排序:需要检查元素是否可比较
    try:
        # 尝试排序
        return sorted(unique)  # 使用sorted简化,实际面试中可能需要自己实现
    except TypeError:
        # 如果元素类型不一致,无法排序
        print("警告:元素类型不一致,无法排序")
        return unique

十二、面试常见问题

Q1:为什么冒泡排序效率低?

A:冒泡排序需要多次遍历数组,每次只移动一个元素到正确位置。对于n个元素,最多需要n(n-1)/2次比较,时间复杂度为O(n²)。

Q2:如何优化去重过程?

A:

  1. 使用集合而不是列表检查重复
  2. 如果数组已排序,可以使用双指针法去重(O(n)时间)

Q3:如果数组很大(比如100万个元素),应该用什么排序?

A:应该用O(n log n)的排序算法,如快速排序、归并排序、堆排序。冒泡排序在大量数据下太慢。

Q4:是否可以在合并时就排序?

A:可以,如果两个数组已经有序,可以使用归并排序中的合并步骤,在合并时保持有序,然后再去重。

十三、练习题

1. 力扣 88. 合并两个有序数组 ⭐⭐

  • 链接https://leetcode.cn/problems/merge-sorted-array/
  • 难度:简单
  • 题目:将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组
  • 与我们的区别
    • 我们的题目:两个无序数组,需要去重+排序
    • 这道题:两个有序数组,不需要去重不需要额外空间(原地合并)
  • 核心技巧:从后向前合并,避免覆盖
python 复制代码
class Solution:
    def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
        """
        原地合并两个有序数组
        nums1有足够的空间(长度为m+n)
        """
        p1, p2, p = m-1, n-1, m+n-1
        
        while p1 >= 0 and p2 >= 0:
            if nums1[p1] > nums2[p2]:
                nums1[p] = nums1[p1]
                p1 -= 1
            else:
                nums1[p] = nums2[p2]
                p2 -= 1
            p -= 1
        
        # 如果nums2还有剩余
        while p2 >= 0:
            nums1[p] = nums2[p2]
            p2 -= 1
            p -= 1

2. 力扣 349. 两个数组的交集

  • 链接https://leetcode.cn/problems/intersection-of-two-arrays/
  • 难度:简单
  • 题目:给定两个数组,返回它们的交集
  • 与我们的区别
    • 我们的题目:合并两个数组(取并集)
    • 这道题:求两个数组的交集(共同元素)
  • 核心技巧:使用集合求交集
python 复制代码
class Solution:
    def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
        # 方法1:使用集合交集
        return list(set(nums1) & set(nums2))
        
        # 方法2:使用哈希表
        seen = set(nums1)
        result = []
        for num in nums2:
            if num in seen:
                result.append(num)
                seen.remove(num)  # 避免重复
        return result

3. 力扣 350. 两个数组的交集 II ⭐⭐

python 复制代码
from collections import Counter

class Solution:
    def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:
        # 方法1:使用Counter
        c1 = Counter(nums1)
        c2 = Counter(nums2)
        result = []
        
        for num in c1:
            if num in c2:
                result.extend([num] * min(c1[num], c2[num]))
        return result
        
        # 方法2:排序+双指针
        nums1.sort()
        nums2.sort()
        i = j = 0
        result = []
        
        while i < len(nums1) and j < len(nums2):
            if nums1[i] < nums2[j]:
                i += 1
            elif nums1[i] > nums2[j]:
                j += 1
            else:
                result.append(nums1[i])
                i += 1
                j += 1
        return result

4. 力扣 912. 排序数组 ⭐⭐⭐

  • 链接https://leetcode.cn/problems/sort-an-array/
  • 难度:中等
  • 题目:给你一个整数数组 nums,请你将该数组升序排列
  • 与我们的区别
    • 我们的题目:需要先合并、去重,再排序
    • 这道题:只需要排序(但要求高效)
  • 核心技巧:需要实现高效的排序算法(不能直接用sort)
python 复制代码
class Solution:
    def sortArray(self, nums: List[int]) -> List[int]:
        """快速排序实现"""
        def quick_sort(arr):
            if len(arr) <= 1:
                return arr
            
            pivot = arr[len(arr)//2]
            left = [x for x in arr if x < pivot]
            middle = [x for x in arr if x == pivot]
            right = [x for x in arr if x > pivot]
            
            return quick_sort(left) + middle + quick_sort(right)
        
        return quick_sort(nums)
  1. 数组合并(力扣88、21)
  2. 去重技巧(力扣26、27、349)
  3. 排序算法(力扣75、148、912)
  4. 综合应用(力扣56、977)

十四、总结

这个题目考察了三个核心技能:

  1. 数组操作:合并两个数组
  2. 去重算法:使用集合高效去重
  3. 排序算法:手动实现排序(如冒泡排序)

关键点

  • 使用 set() 进行高效去重
  • 理解冒泡排序的双层循环结构
  • 掌握 Python 的元素交换语法
  • 注意边界情况(空数组、单个元素等)

面试建议

  1. 先完成基本功能,再考虑优化
  2. 解释算法的时间复杂度和空间复杂度
  3. 讨论可能的优化方案
  4. 写出完整的测试用例

记住:即使题目要求不使用 sort(),也要了解不同排序算法的优缺点,以便在面试中深入讨论。

相关推荐
普贤莲花2 小时前
取舍~2026年第4周小结---写于20260125
程序人生·算法·leetcode
人工智能AI技术2 小时前
【Agent从入门到实践】33 集成多工具,实现Agent的工具选择与执行
人工智能·python
curry____3032 小时前
menset的使用方法
算法
AIFQuant2 小时前
如何通过股票数据 API 计算 RSI、MACD 与移动平均线MA
大数据·后端·python·金融·restful
70asunflower2 小时前
Python with 语句与上下文管理完全教程
linux·服务器·python
苦藤新鸡2 小时前
39.二叉树的直径
算法·leetcode·深度优先
deephub2 小时前
为什么标准化要用均值0和方差1?
人工智能·python·机器学习·标准化
hnxaoli2 小时前
win10程序(十五)归档文件的xlsx目录自动分卷
python
TracyCoder1232 小时前
LeetCode Hot100(6/100)——15. 三数之和
算法·leetcode