前言
本系列的文章为笔者学习《剑指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