题目链接: https://leetcode.cn/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/
1. 题目介绍(03. 数组中重复的数字)
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字
【测试用例】:
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
【条件约束】:
2 <= n <= 100000
2. 题解
2.1 给输入的数组排序 – 差:O(nlogn)
class Solution {
public int findRepeatNumber(int[] nums) {
// 1. 给nums数组排序
Arrays.sort(nums);
// 2. 遍历打印nums,检查是否排序成功
// for (int j = 0; j < nums.length; j++) {
// System.out.print(nums[j] + "\t");
// }
// 3. 冒泡判重
for (int j = 0; j < nums.length-1; j++) {
if(nums[j] == nums[j+1]){
return nums[j];
}
}
return -1;
}
}
2.2 哈希表判重 – 中:O(n)
class Solution {
/**
* 时间复杂度:O(n)
* 空间复杂度:O(n)
*/
public int findRepeatNumber(int[] nums) {
// 1. 创建一个哈希表
HashMap<Integer,Integer> map = new HashMap<>();
// 2. 遍历nums数组,并进行相同key判断
for (int i = 0; i < nums.length; i++){
// 3. 如果重复,则返回重复值,并对当前重复key中的value+1
if (map.containsKey(nums[i])){
map.replace(nums[i],map.get(nums[i])+1);
return nums[i];
}else{
// 4. 如果不重复,就将其记录在map中
map.put(nums[i],1);
}
}
// 5. 循环结束,无重复结果,返回-1
return -1;
}
}
2.3 下标顺序排列交换 – 优:O(n)
class Solution {
/**
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
public int findRepeatNumber(int[] nums) {
// 1. 判空判断
if (nums.length <= 0) return -1;
// 2. 错误值判断
for (int i = 0; i < nums.length; i++){
if (nums[i] < 0 || nums[i] > nums.length-1){
return -1;
}
}
// 3. 数组元素与下标位置排列交换
for (int i = 0; i < nums.length; i++){
while (nums[i] != i){
// 4. 如果当前数组元素不等于当前下标,那么先判断当前元素是否属于重复,
// 即已经有一个正确的值处于了正确的位置
if (nums[i] == nums[nums[i]]){
return nums[i];
}
// 5. 交换nums[i]与nums[nums[i]]
int temp = nums[i];
nums[i] = nums[temp];
nums[temp] = temp;
}
}
// 4. 循环结束,说明数组中无重复元素
return -1;
}
}
3. 思考
按理说这里的哈希表判重方法应该会比给输入的数组排序的耗时短才对,但是这里的哈希表判重耗时却达到了8ms,当然不排除测试用例的偶然情况,回头可以再测试测试。
4.扩展题 – 不修改数组找出重复的数字
4.1 辅助数组+对应下标位置排列
/**
* 时间复杂度为O(n)
* 空间复杂度为O(n)
*/
public class Duplicate2 {
/**
* @param intArray 输入数组
* @param duplicaiton 将首次找到的重复数字利用duplicaiton[0] = ?存入数组
* @return 如果输入数组无效返回false,duplicaiton[0]=-1
*/
public static boolean findDuplicate(int[] intArray, int[] duplicaiton) {
// 杜绝数组为空
if (intArray.length == 0) {
duplicaiton[0] = -1;
return false;
}
// 杜绝数组有非法数字
for (int i = 0; i < intArray.length; i++) {
if (intArray[i] < 1 || intArray[i] > intArray.length - 1) {
duplicaiton[0] = -1;
return false;
}
}
int start = 1;
int end = intArray.length - 1;
while (end >= start) {
// >> 右移一位相当于除以2
int middle = ((end + start) >> 1);
int count = countRange(intArray, start, middle);
// 终止条件
if (start == end) {
if (count > 1) {
duplicaiton[0] = middle;
return true;
} else {
break;
}
}
if (count > (middle - start) + 1) {
end = middle;
} else {
start = middle + 1;
}
}
duplicaiton[0] = -1;
return false;
}
/**
* @param intArray 输入数组
* @param start 区间起始数字
* @param end 区间结束数字
* @return 个数
* @Description 统计数组在区间里数字的个数
*/
public static int countRange(int[] intArray, int start, int end) {
if (intArray.length == 0) {
return 0;
}
int count = 0;
for (int i : intArray) {
if (i >= start && i <= end) {
count++;
}
}
return count;
}
4.2 二分思想 + 限制区间
/**
* 时间复杂度为O(nlogn)
* 空间复杂度为O(1)
*/
public class BinarySearch {
/**
* 使用递归的二分查找
*
* @param arr 有序数组
* @param key 待查找关键字
* @return 找到的位置
*/
public static int recursionBinarySearch(int[] arr, int key, int low, int high) {
if (arr.length == 0) {
return -1;
}
if (key < arr[low] || key > arr[high] || low > high) {
return -1;
}
int middle = (high - low) >> 2;
if (arr[middle] > key) {
//比关键字大则关键字在左区域
return recursionBinarySearch(arr, key, low, middle - 1);
} else if (arr[middle] < key) {
//比关键字小则关键字在右区域
return recursionBinarySearch(arr, key, middle + 1, high);
} else {
return middle;
}
}
/**
* 不使用递归的二分查找
* @param arr
* @param key
* @return 关键字位置
*/
public static int commonBinarySearch(int[] arr, int key) {
int low = 0;
int high = arr.length - 1;
if (key < arr[low] || key > arr[high] || low > high) {
return -1;
}
while (low <= high) {
int middle = (low + high) >> 2;
if (arr[middle] > key) {
//比关键字大则关键字在左区域
high = middle - 1;
} else if (arr[middle] < key) {
//比关键字小则关键字在右区域
low = middle + 1;
} else {
return middle;
}
}
return -1;
}
}
5. 参考资料
[1] 剑指Offer系列(java版,详细解析) 03. 数组中重复的数字 (扩展题代码来源)