算法题 存在重复元素

217. 存在重复元素

问题描述

给定一个整数数组 nums,判断其中是否存在重复元素。如果存在重复元素,返回 true;否则返回 false

示例

示例 1:

输入:nums = [1,2,3,1]

输出:true

解释:

元素 1 在下标 0 和 3 出现。

示例 2:

输入:nums = [1,2,3,4]

输出:false

解释:

所有元素都不同。

示例 3:

输入:nums = [1,1,1,3,3,4,3,2,4,2]

输出:true

算法思路

方法一:哈希集合(Set)

  1. 使用一个 HashSet 来存储已经遍历过的元素
  2. 遍历数组,对每个元素进行判断:
    • 如果该元素已存在于集合中 → 存在重复,返回 true
    • 否则将其加入集合继续遍历
  3. 遍历结束后未发现重复 → 返回 false

优点:时间效率高,逻辑清晰
适用场景:通用解法,适合大多数情况

方法二:排序后比较相邻元素

  1. 先对数组进行排序
  2. 遍历排序后的数组,比较相邻两个元素是否相等
  3. 若发现相等的相邻元素 → 存在重复,返回 true
  4. 遍历结束未发现重复 → 返回 false

优点:空间复杂度更低(原地操作)
缺点:修改了原数组结构,不适用于不允许修改输入的场景

代码实现

方法一:使用 HashSet 判断重复(推荐解法)

import java.util.HashSet;
import java.util.Set;

class Solution {
    /**
     * 判断数组中是否存在重复元素(使用HashSet)
     * 
     * 核心思想:利用HashSet的唯一性特性,
     * 在添加元素时自动检测是否已存在相同元素。
     * 
     * @param nums 整数数组
     * @return 存在重复返回true,否则返回false
     */
    public boolean containsDuplicate(int[] nums) {
        // 创建HashSet用于存储已遍历的元素
        // HashSet特点:不允许重复元素,查找和插入均为O(1)
        Set<Integer> seen = new HashSet<>();
        
        // 遍历数组中的每一个元素
        for (int num : nums) {
            // 尝试将当前元素加入HashSet
            // add()方法返回boolean值:
            // - 如果元素不存在则添加成功,返回true
            // - 如果元素已存在则添加失败,返回false
            if (!seen.add(num)) {
                // 添加失败说明该元素已经存在 → 发现重复
                return true;
            }
            // 如果添加成功,继续处理下一个元素
        }
        
        // 所有元素都成功添加,说明没有重复元素
        return false;
    }
}

方法二:排序后检查相邻元素(空间优化解法)

import java.util.Arrays;

class Solution {
    /**
     * 判断数组中是否存在重复元素(排序法)
     * 
     * 核心思想:排序后重复元素必定相邻,
     * 只需一次遍历检查相邻元素即可。
     * 
     * 注意:此方法会改变原数组顺序
     * 
     * @param nums 整数数组
     * @return 存在重复返回true,否则返回false
     */
    public boolean containsDuplicate(int[] nums) {
        // 对数组进行排序
        // 排序后相同的元素会聚集在一起
        Arrays.sort(nums);
        
        // 遍历排序后的数组,检查相邻元素
        // 从索引1开始,比较当前元素与前一个元素
        for (int i = 1; i < nums.length; i++) {
            // 如果当前元素等于前一个元素 → 存在重复
            if (nums[i] == nums[i - 1]) {
                return true;
            }
        }
        
        // 遍历完成未发现相邻重复元素 → 无重复
        return false;
    }
}

算法分析

  • 时间复杂度

    • 方法一(HashSet):O(n)
      • 遍历数组一次:O(n)
      • HashSet的add操作平均O(1)
    • 方法二(排序):O(n log n)
      • 排序时间复杂度:O(n log n)
      • 遍历检查相邻元素:O(n)
      • 总体由排序主导
  • 空间复杂度

    • 方法一:O(n)
      • HashSet最多存储n个元素
    • 方法二:O(1) 或 O(log n)
      • 不考虑排序的递归栈空间时为O(1)
      • 快速排序/归并排序的递归调用栈为O(log n)
  • 方法对比

    方法时间复杂度空间复杂度是否修改原数组适用场景
    HashSetO(n)O(n)通用推荐,性能最优
    排序法O(n log n)O(1)/O(log n)内存受限且允许修改输入

算法过程

输入:nums = [1,2,3,1]

方法一:HashSet 过程

  1. 初始化空HashSet:seen = {}
  2. num=1seen.add(1) → 成功 → seen={1}
  3. num=2seen.add(2) → 成功 → seen={1,2}
  4. num=3seen.add(3) → 成功 → seen={1,2,3}
  5. num=1seen.add(1) → 失败(1已存在)→ 返回 true

方法二:排序法过程

  1. 排序前:[1,2,3,1]
  2. 排序后:[1,1,2,3]
  3. 遍历检查相邻元素:
    • i=1nums[1]=1, nums[0]=1 → 相等 → 返回 true

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1:存在重复(示例1)
    int[] nums1 = {1,2,3,1};
    System.out.println("Test 1: " + solution.containsDuplicate(nums1)); // true
    
    // 测试用例2:无重复(示例2)
    int[] nums2 = {1,2,3,4};
    System.out.println("Test 2: " + solution.containsDuplicate(nums2)); // false
    
    // 测试用例3:两个相同元素
    int[] nums3 = {1,1};
    System.out.println("Test 3: " + solution.containsDuplicate(nums3)); // true
    
    // 测试用例4:单元素
    int[] nums4 = {1};
    System.out.println("Test 4: " + solution.containsDuplicate(nums4)); // false
    
    // 测试用例5:空数组
    int[] nums5 = {};
    System.out.println("Test 5: " + solution.containsDuplicate(nums5)); // false
    
    // 测试用例6:多个重复
    int[] nums6 = {1,2,3,4,2};
    System.out.println("Test 6: " + solution.containsDuplicate(nums6)); // true
    
    // 测试用例7:负数重复
    int[] nums7 = {-1,-2,-1};
    System.out.println("Test 7: " + solution.containsDuplicate(nums7)); // true
    
    // 测试用例8:大数组无重复
    int[] nums8 = new int[1000];
    for (int i = 0; i < 1000; i++) {
        nums8[i] = i;
    }
    System.out.println("Test 8: " + solution.containsDuplicate(nums8)); // false
}

关键点

  1. HashSet 的使用

    • add() 方法的返回值直接反映了元素是否已存在
    • 避免了先调用 contains() 再调用 add() 的两次操作
  2. 早期终止

    • 一旦发现重复立即返回,无需遍历完整个数组
    • 最好情况下时间复杂度可达到 O(1)
  3. 排序法的本质

    • 将"全局重复检测"问题转化为"局部相邻比较"问题
    • 利用排序使重复元素聚集,简化判断逻辑
  4. 边界情况处理

    • 空数组或单元素数组:不可能有重复
    • 负数和零:不影响算法逻辑

常见问题

  1. 为什么 HashSet 方法比排序方法快?

    • HashSet 方法是线性时间 O(n),而排序方法是 O(n log n)
    • 对于大规模数据,log n 的差异会很明显
  2. 能否用其他数据结构?

    • 可以使用 HashMap,但没必要,因为不需要计数
    • 可以使用 boolean[] 标记,但仅适用于元素值范围小的情况
  3. 如何处理超大数值?

    • HashSet 方法不受数值大小影响,只与元素个数有关
    • 排序法同样不受影响
  4. 如果要找出所有重复元素怎么办?

    • 可以改用 HashMap<Integer, Integer> 统计频次
    • 或使用两个 HashSet:一个记录已见元素,一个记录重复元素
  5. 哪种方法更节省内存?

    • 如果数组很大但允许修改原数组,排序法更省空间
    • 否则 HashSet 法虽然占用额外空间,但时间和代码复杂度更优
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值