这个系列主要是记录作者本人在学习算法过程中的一些总结
首先呢,我们通过一道题来引出今天所讲到的技巧:
看到这道题,大家会考虑怎么做,我第一次想出的答案就是直接求和,然后减去真正的1~1000数字的和,最后得到的那个数字就是所求,直接暴力求解,哈哈,但是具有局限性,所以就来给大家分享一个很巧妙的方法。
在此之前,大家先学习一下这些位运算的操作符
这道题呢,我们主要是运用 ^ 运算符去求解,也就是左右两个数相同时结果为 0 ,不同时结果为 1,从而达到去重的效果,但是这道题要求的是求出重复的数字,不是正好反着来了吗?所以,就用这数组中1001个元素去与1~1000的数字去 ^ 那么1001中1 ~ 1000的数字就都出现了两次,通过亦或运算符消除掉了,而原来数组中重复的元素加起来出现了三次,两个被去重了,剩下那个就被留下来了,这一点希望大家好好理解一下
我们先假设重复的是3
前面每个元素和后面的进行亦或,都被消掉了
用代码来表示就是:
public class FindRepeat {
public static void main(String[] args) {
int N = 11;
int[] arr = new int[N];
for (int i = 0; i < arr.length; i++) {
arr[i] = i + 1;
}
//生成1~10的随机数,作为重复的那个元素,暂时放在数组末位
arr[arr.length - 1] = new Random().nextInt(N - 1) + 1;
//生成一个随机下标
int index = new Random().nextInt(N);
//将重复的元素与随机出来的下标所在元素互换
swap(arr, index, arr.length - 1);
System.out.println(Arrays.toString(arr));
int x1 = 0;
//1~10之间亦或
for (int i = 1; i <= N - 1; i++) {
x1 = x1 ^ i;
}
//接着找出重复元素
for (int i = 0; i < N; i++) {
x1 = x1 ^ arr[i];
}
System.out.println(x1);
}
//交换元素
public static void swap(int arr[], int index1, int index2) {
int temp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = temp;
}
}
结果就是这个样子的,这里数组里边是11个元素,为了方便大家查看,解题时大小改为1001就可以了
代码中通过生成的随机数来作为重复的元素,先把这个元素放在数组末位,再与随机的一个数组下标所指元素交换,从而打乱重复元素所在位置,之后先把1~10之间元素亦或,再将结果与数组中所有元素亦或,也就是之前我们分析的那样
除了这个方法,还有一个开辟辅助空间的方法,当然,题中要求的是不能开辟辅助空间,这里只是分享一下一个暴力破解的方法,如果上面讲的方法想不到,还可以多一种方法
//开辟辅助空间的方法
int[] helper = new int[N];
//在遍历arr数组的同时,利用下标,给helper数组赋值,两次++的为重复元素
for (int i = 0; i < N; i++) {
helper[arr[i]]++;
}
for (int i = 0; i < N; i++) {
if (helper[i] == 2) {
System.out.println(i);
break;
}
}
这个方法呢,巧妙的利用了下标的关系来进行求解,因为是1~1000的数字加上一个重复的数字,那么 arr[ i ] 中所记录的元素作为数组helper 的下标 ,也不用担心数组越界的问题,最终helper数组中最后一个位置并没有被赋值,为0,其他位置都被赋值为了1,而因为有一个重复的元素,所以helper
数组中有一个元素被赋值了两次,变为了2,遍历找出该元素,那个 i 就是重复的元素
运行之后也是没有问题的,结果是两个方法的运行结果,大家区分一下
本次分享的两个方法掌握之后,再遇到类似的题也可以求解了
例如这个题,比我们提到的例题还简单一点,并没有过多的要求,大家可以尝试一下。
后续还会持续更新,有什么问题可以在评论区留言哦,有报名了蓝桥杯的小伙伴也可以一起督促学习。