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

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

一、问题理解

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(),也要了解不同排序算法的优缺点,以便在面试中深入讨论。

相关推荐
User_芊芊君子12 小时前
CANN010:PyASC Python编程接口—简化AI算子开发的Python框架
开发语言·人工智能·python
团子的二进制世界12 小时前
G1垃圾收集器是如何工作的?
java·jvm·算法
白日做梦Q12 小时前
Anchor-free检测器全解析:CenterNet vs FCOS
python·深度学习·神经网络·目标检测·机器学习
吃杠碰小鸡12 小时前
高中数学-数列-导数证明
前端·数学·算法
故事不长丨12 小时前
C#线程同步:lock、Monitor、Mutex原理+用法+实战全解析
开发语言·算法·c#
long31612 小时前
Aho-Corasick 模式搜索算法
java·数据结构·spring boot·后端·算法·排序算法
近津薪荼12 小时前
dfs专题4——二叉树的深搜(验证二叉搜索树)
c++·学习·算法·深度优先
熊文豪12 小时前
探索CANN ops-nn:高性能哈希算子技术解读
算法·哈希算法·cann
喵手12 小时前
Python爬虫实战:公共自行车站点智能采集系统 - 从零构建生产级爬虫的完整实战(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·采集公共自行车站点·公共自行车站点智能采集系统·采集公共自行车站点导出csv
喵手12 小时前
Python爬虫实战:地图 POI + 行政区反查实战 - 商圈热力数据准备完整方案(附CSV导出 + SQLite持久化存储)!
爬虫·python·爬虫实战·零基础python爬虫教学·地区poi·行政区反查·商圈热力数据采集