1 题目
在一个长度为n的数组里的所有数字都在0到n-1的范围内。数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是第一个重复的数字2。
2 尝试解决
小弟不才,只想到暴力解决,通过两次遍历数组找出重复的内容,有就将数字放入duplication[0]并返回true,没有就false,下面是代码,方法上的注释是牛客网给出的,方法内的是我自己写的
public class Solution {
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
// Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
// 这里要特别注意~返回任意重复的一个,赋值duplication[0]
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
public boolean duplicate(int numbers[],int length,int [] duplication) {
//双重循环遍历数组
for(int i=0;i<length;i++){
for(int j=0;j<length;j++){
//相同下标直接继续
if(i==j)
continue;
//不同下标进行比较
if(numbers[i]==numbers[j]){
//相等,即重复
duplication[0]=numbers[i];
return true;
}
//不相等则继续运行,不做处理
}
}
//循环结束后仍未停止运行,说明无重复
return false;
}
}
运行结果
3 更进一步
虽然通过了测试,但暴力算法终究是不可取的,不妨看看他人的思路,学习学习。
双重for循环的复杂度为O(n^2).
3.1 排序后比较
先排序后比较的复杂度为 排序的O(nlogn)和一次遍历O(n),取大的为O(nlogn),下面是代码
public class Solution {
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
// Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
// 这里要特别注意~返回任意重复的一个,赋值duplication[0]
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
public boolean duplicate(int numbers[],int length,int [] duplication) {
//排序后比较
//排序
quickSort(numbers,0,length-1);
//比较 最大为length-1防止越界
for(int i=0;i<length-1;i++){
if(numbers[i]==numbers[i+1]){
duplication[0]=numbers[i];
return true;
}
}
//无重复
return false;
}
public void quickSort(int arr[],int left,int right){
if(right-left<=0)
return;
else{
int pivot=arr[right];
int partition=partitionIt(arr,left,right,pivot);
quickSort(arr,left,partition-1);
quickSort(arr,partition+1,right);
}
}
public int partitionIt(int[]arr,int left,int right,int pivot){
int leftPtr=left-1;
int rightPtr=right;
while(true){
while(arr[++leftPtr]<pivot)
;
while(rightPtr>0 && arr[--rightPtr]>pivot)
;
if(leftPtr>=rightPtr)
break;
else
swap(arr,leftPtr,rightPtr);
}
swap(arr,leftPtr,right);
return leftPtr;
}
public void swap(int arr[],int index1,int index2){
int temp=arr[index1];
arr[index1]=arr[index2];
arr[index2]=temp;
}
}
运行结果
3.2 哈希表
这种方法的想法就是在遍历数组的过程中填充一个哈希表,每扫描到一个数字就查看哈希表中是否已有该数字,有就结束,没有就返回。这种方法以O(n)的空间代价将时间效率提升至O(n)
但是在牛客的网页上不允许导入包,所以下面贴一段eclipse中的代码,以题目中的那个例子做测试。
public static void main(String[] args) {
int [] numbers= {2,3,1,0,2,5,3};
int [] duplication=new int[1];
duplicate(numbers, numbers.length, duplication);
System.out.println(duplication[0]);
}
public static boolean duplicate(int numbers[],int length,int [] duplication) {
//哈希
HashSet<Integer> set=new HashSet<>();
for(int i=0;i<length;i++){
//判断是否已有
if(set.contains(numbers[i])){
//重复
duplication[0]=numbers[i];
return true;
}else{
//还没有
set.add(numbers[i]);
}
}
//遍历结束,无重复
return false;
}
运行结果
3.3 结合题干分析
题目中所有数字都是在0到n-1之间,对数组进行排序,如果没有重复的数字,那么排序后的数组下标和值应当是一一对应的,即 numbers[0]==0,numbers[1]==1,numbers[2]==2,以此类推,那么在对数组进行重排的过程中,假设当前扫描到了第 i 个位置,如果numbers[i]==i ,那么这个数字就处在正确的位置上,如果numbers[i]!=i
,那么此时的number[i]就错位了,假设此时number[i]==m,尝试判断 numbers[i]会不会等于numbers[m],等于就说明重复了,不等于就应该将该数字放到正确的位置上,即把 i 和 m 上的数字进行交换,然后继续向后判断,直到找出重复的数字或结束重排。
public class Solution {
// Parameters:
// numbers: an array of integers
// length: the length of array numbers
// duplication: (Output) the duplicated number in the array number,length of duplication array is 1,so using duplication[0] = ? in implementation;
// Here duplication like pointor in C/C++, duplication[0] equal *duplication in C/C++
// 这里要特别注意~返回任意重复的一个,赋值duplication[0]
// Return value: true if the input is valid, and there are some duplications in the array number
// otherwise false
public boolean duplicate(int numbers[],int length,int [] duplication) {
//重排
for(int i=0;i<length;i++){
//判断下标与当前值是否一一对应
if(numbers[i]==i){
//正确对应了,直接继续
continue;
}else{
//不一一对应
//判断i上的数字是否与numbers[i]上的数字相等
//此处可将numbers[i]赋值给m,更易读一些,而且可以防止numbers[i]被修改造成死循环
int m=numbers[i];
if(numbers[i]==numbers[m]){
//相等,重复了
duplication[0]=numbers[i];
return true;
}else{
//不相等,交换两者位置
int temp=numbers[i];
numbers[i]=numbers[m];
numbers[m]=numbers[i];
}
}
}
//重排结束,未重复
return false;
}
}
3.4 与全解的代码差异
全解的代码
public boolean duplicate(int[] nums, int length, int[] duplication) {
if (nums == null || length <= 0)
return false;
for (int i = 0; i < length; i++) {
while (nums[i] != i) {
if (nums[i] == nums[nums[i]]) {
duplication[0] = nums[i];
return true;
}
swap(nums, i, nums[i]);
}
}
return false;
}
private void swap(int[] nums, int i, int j) {
int t = nums[i];
nums[i] = nums[j];
nums[j] = t;
}
可以看出他在一开始判断了数组为空和长度未负数的情况,这是我所没有考虑到的。
将swap方法抽出来单独写这一点我想到了,但是将swap方法设置成private我没注意。
在for循环中直接判断还是使用while这一点我倒是没有注意,但是在leetcode中提交的时候使用if是无法通过的,只能用while,另外替换方法要注意,没写好会死循环的。
4 结语
一开始看书的时候看到下面的分析内容说要进行排序,但是对排序算法不熟悉,纠结了半天决定先按自己的想法试试再说,没想到居然通过了,再一次验证了一道问题有多种解法的,最重要的还是要思考。
排序和集合类方面仍有不足,剑指offer看累了就看点这方面的内容吧。
5 参考链接
1.快速排序—(面试碰到过好几次)
2.时间复杂度大小比较
3.java各种集合类区别
4.官解
5.剑指 Offer 全解(Java 版)