剑指offer题解(Java实现)—— 面试题3:数组中重复的数字

前言

本系列的文章为笔者学习《剑指offer第二版》后的笔记整理以及Java实现,解法不保证与书上一致。

另外,我最近在系统整理一些 Java 后台方面的面试题和参考答案,有找工作需求的童鞋,欢迎关注我的 Github 仓库,如果觉得不错可以点个 star 关注 :

题目描述

在一个长度为 n 的数组里的所有数字都在 0~n-1 的范围内。 数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如,如果输入长度为 7 的数组 {2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字 2 或者 3。

解题思路

解决这个问题主要有三种方法:

思路一

先将输入的数组排序,然后从头到尾遍历排序后的数组,如果有重复的数字,那么一定会存在相邻元素相等的情况。从头到尾遍历的过程种,只需相邻元素凉凉比较,有相等的情况就找到了重复数字。很明显,扫描数组需要的时间复杂度为 O(n),而排序数组则需要 O(nlogn) 的时间复杂度,所以该方法需要总的时间复杂度为 O(nlogn)。

	/**
     * 方法一:先排序后遍历查找看是否有相邻元素相等
     * @param nums 待查找的数组
     * @param duplication 用于保存重复数字,第一个被找到的重复数字存放在duplication[0]中
     * @return 数组中的重复数字(如果有的话)
     */
    public boolean duplicate1(int[] nums, int[] duplication) {
        int length = nums.length;
        if (nums == null || length <= 0) {
            return false;
        }

        // 排序数组,时间复杂度为 O(nlogn)
        Arrays.sort(nums);
        for (int i = 0; i < length - 1; i++) {
            if (nums[i] == nums[i + 1]) {
                // 排序后相邻元素相等,说明有重复元素,将其保存到 duplication[0] 中
                duplication[0] = nums[i];
                return true;
            }
        }
        return false;
    }

思路二

通过哈希表来解决。从头到尾按顺序扫描数组中的每个数字,可以通过 O(1) 的时间复杂度来判断哈希表中是否包含某个数字。每扫描到到一个数字时,如果哈希表中不存在该数字,就将其加入哈希表中。反之,表中已存在则说明找到了一个重复的数字。

这个算法的时间复杂度为 O(n),但需要消耗大小为 O(n) 的哈希表的空间,也就是说空间复杂度为 O(n)。

代码暂略...

思路三

根据题干中的信息“长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内”可知,如果数组中没有重复的数字,那么数组经过排序之后数字 i 将出现在下标为 i 的位置,即 nums[i] = i。但现在由于数组中存在重复的数字,有些数字可能有多个,而有些数字可能空缺了。

如果数组中存在重复数字,就肯定会存在某个 j != i时,nums[j] = nums[i]的情况,只要能找到满足这个条件的数字,则找到了重复的数字。现在我们可以来这么做:

  • 1、扫描到下标 i 时,如果 nums[i] == i,则继续接着扫描下一个数;
  • 2、如果 nums[i] != i,则令 nums[i] = j,比较 nums[i]nums[j]的值是否相等,如果相等,说明在与 i 不同的位置 ‘j’ 找到了一个重复的数字。
  • 在第 2 步的基础上,如果 nums[i] != nums[j],则将第 i 个数字和第 ‘j’ 个数字交换。接下来不断重复这个比较、交换的过程,直到找到一个重复的数字(除非不存在重复数字)。
package com.offers.chapter2.question03;

import java.util.Arrays;

/**
 * @author Rotor
 * @since 2019/9/21 0:35
 */
public class FindDuplicate {
    /**
     * 方法三:利用题干信息"长度为n的数组里的所有数字都在0到n-1的范围内"推导
     *
     * @param nums 待查找的数组
     * @param duplication 用于保存重复数字,第一个被找到的重复数字存放在duplication[0]中
     * @return 数组中的重复数字(如果有的话)
     */
    public boolean duplicate3(int[] nums, int[] duplication) {
        int length = nums.length;
        if (nums == null || length <= 0) {
            return false;
        }

        // 数组中包含 0~n-1 之外的无效的数字
        for (int i = 0; i < length; i++) {
            if (nums[i] < 0 || nums[i] > length - 1) {
                return false;
            }
        }

        for (int i = 0; i < length; i++) {
            while (nums[i] != i) {
                /**
                 * nums[i] != i,设numbers[i] = j,所以如果下面的 if 条件成立,即numbers[i] == numbers[j],
                 * 说明第 i 位与第 nums[i] 位数字相等,找到重复数字
                 */
                int j = nums[i];
                if (nums[i] == nums[j]) {
                    duplication[0] = nums[i];
                    return true;
                }
                swap(nums, i, nums[i]);
            }
        }
        return false;
    }

    // 交换 nums[i] 与 nums[nums[i]]
    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

    public static void main(String[] args) {

    }

}

代码中有一个两重循环,但每个数字最多只要交换两次就能找到属于它自己的位置,因此总的时间复杂度为 O(n)。另外,所有的操作步骤都是在输入数组上进行的,不需要额外分配内存。所以空间复杂度为 O(1)。

总结

  • 本体主要考察对一维数组的理解及编程能力。一维数组在内存中占据连续的存储空间,因此可以根据下标定位对应的元素;
  • 遇到类似的问题时,可以结合题干给出的信息进行分析。

后记

如果你同我一样想要努力学好数据结构与算法、想要刷 LeetCode 和剑指 offer,欢迎关注我 GitHub 上的 LeetCode 题解:awesome-java-notes

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章