目录 | java刷题 | 剑指offer_数组_2.3.1_题3.数组中重复的数字 | 不同考虑情况下的解法
背景
刷题+总结+进步!
刷leetcode刷到这个数组部分,刷到这个题了。
主要根据面试官对于时间复杂度和空间复杂度的不同需求,更换自己完成该题目的数据结构和算法内容。
实现
分析
题1:剑指 03
https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/solution/jian-zhi-03-shu-zu-zhong-zhong-fu-de-shu-kg05/
方法1:排序+遍历
时复:O(NlogN)
空间复杂度:根据排序算法不同
1、将输入数组排序,最优的快排时间复杂度O(NlogN);
2、从头到尾扫描排序后的数组,比较当前节点和下一个节点的的值是否相等。当第一个相等的时候,就可以进行返回。
方法2:时间优先:哈希表
时复:O(N)
空间复杂度:O(N)
1、从头到尾比扫描数组每个数字,每扫描到一个数字的时候,用O(1)时间判断还行表中是否存在当前值。
2、若存在则找到重复数字,将当前值返回;若没找到,则加入哈希表。
方法3:指针+原地交换排序数组
根据题目特点数组n长度,数值0~n-1.
时间复杂度:O(N)
空间复杂度:O(1)
如果数组中没有重复的数字,则数组排序后数字i将出现在下标为i的位置上。
由于数组中有重复的数字,排序后,有些位置可能存在多个数字/没有数字。
遍历中,第一次遇到数字 xx 时,将其交换至索引 xx 处;而当第二次遇到数字 xx 时,一定有 nums[x] = xnums[x]=x ,此时即可得到一组重复数字。
1、从头到尾扫描每个数字。
扫描到下标为i的数字时,首先比较这个数字(m)是否=i,
是则扫描下一个;不是则将该数与m位置上的数比较,若和第m个数字相等,则找到一个重复数字(i\m两个位置都出现);
如果不相等则交换第i和第m个数字,将m放到属于自己的位置上。
2、重复比较交换的过程,直到发现一个重复的数字。
进阶题目2-剑指中的题目二:长度为n+1,数值为1~n。
不能修改原数组
方法4:二分法,个数和数值结合二分!!!
不能修改原数组的话,可以创建辅助空间。
时间:O(N) 空间复杂度:O(N)
1、创建长度为n+1的辅助数组,逐个将原数组数字复制到辅助数组:原数组被复制数字为m,则复制到辅助数组下标为m的位置。(方便发现哪个数字是重复的)
空间O(1)且不能修改原数组
方法5:二分+统计数值个数,与数值比较并继续二分数值
时间复杂度:O(NlogN) 空间复杂度:O(1) 以时间换空间
数组长度n+1大于数值范围n,一定存在重复。
将数值1`n从中间值分为两部分[1,m][m+1,n]。若数值为[1,m]的数字个数>m,则数值[1,m]中绝对包含了重复数字。另一半也是这样分析。
继续将包含重复数字的区间二分,直到找到一个重复数字。
类似二分,多了统计区间中数字个数的操作。
统计区间中某数值出现的次数,每次都要遍历区间进行统计。O(N)
根据二分法,总共进行O(logN)次。
但是不能确保找出所有重复数字。因为可能有些数字出现次数和没出现的数字的总个数刚好满足判断条件(【2,2】数值1~2范围内,2出现两次)
步骤
题解
注意:以下代码中注释部分的”方法几“是与分析中一一对应的。
方法1:排序+遍历
//方法1:排序
import java.util.Arrays;
class Solution {
public int findRepeatNumber(int[] nums) {
Arrays.sort(nums);
for(int i = 0; i < nums.length - 1; i++){
if(nums[i] == nums[i + 1]) return nums[i];
}
return 0;
}
}
方法2:时间优先:哈希表/set
//方法2:哈希表、set
class Solution {
public int findRepeatNumber(int[] nums) {
HashMap<Integer,Integer> map = new HashMap<>();
for(int i = 0; i< nums.length ; i++){
if(map.containsKey(nums[i]))
return nums[i];
map.put(nums[i], i);
}
return 0;
}
}
// //set存储
// class Solution {
// public int findRepeatNumber(int[] nums) {
// HashSet<Integer> set = new HashSet<>();
// for(int i = 0; i < nums.length; i++){
// if(set.contains(nums[i]))
// return nums[i];
// set.add(nums[i]);
// }
// return 0;
// }
// }
方法3:指针+原地交换排序数组
//方法3:指针+原地交换排序数组
class Solution {
public int findRepeatNumber(int[] nums) {
for(int i = 0; i < nums.length; i++){
if(nums[i] == i) { //如果相等则直接返回
continue;
}
if(nums[nums[i]] == nums[i]){ //如果nums[i]为索引的对应位置上,存在一个nums[i]的数值,那就已经有重复数字了,直接返回
return nums[i];
}else{ //如果两者不相等,交换两个位置上的数值
swap(nums,i,nums[i]);
}
}
return 0;
}
public void swap(int[] arr, int i, int j){
int temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
方法4、方法5:二分
package Algrithom;
import org.junit.Test; //自己建包写测试类,因leetcode上没有该题
public class lc_findRepeatNumber {
@Test
public void test01(){
int [] arr = {2, 3, 1, 4, 2, 5, 3}; //剑指中规定了数组长度为n+1;数字范围为1~n,所以可以用0来初始化辅助数组
System.out.println(findRepeatNumber(arr));
System.out.println(findRepeatNumber2(arr));
}
//方法4:创辅助数组,复制原数值到对应位置
public int findRepeatNumber(int[] nums) {
int[] arr = new int [nums.length];
for(int i = 0; i< nums.length ; i++){
if(nums[i] == arr[nums[i]]) return nums[i];
arr[nums[i]] = nums[i];
}
return 0;
}
//方法5:二分+统计数值个数,与数值比较并继续二分数值
public int findRepeatNumber2(int[] nums) {
if(nums == null || nums.length <= 0) return -1;
int start = 1, end = nums.length - 1;
while(end >= start){
int middle = ((end - start) >> 1) + start;
int count = countRange(nums, nums.length, start, middle);
if(end == start){ //结束二分的条件为:左右指针相遇
if(count > 1) return start; //相遇后如果存在个数大于数值范围,则返回重复数值
else break; //否则不存在重复,结束while。因为该if是二分到最后才执行的,相当于结束while的条件
}
if(count > (middle - start + 1)) end = middle;
else start = middle + 1;
}
return -1; //需要有一个函数返回值
}
public int countRange(int[] arr, int length, int start, int end){
if(arr == null) return 0;
int count = 0;
for(int i =0; i < length; i++){
if(arr[i] >= start && arr[i] <= end) count++;
}
return count;
}
}
总结
感觉在面试中,如果遇到,会层层深入的询问。
参考
链接: link.
[1]: http://先保留一个格式