时间复杂度
- 只要高阶项,不要低阶项
局部最小
问题
无序数组,相邻不等,返回一个局部最小的位置,怎么整?
原理
- 【0】<【1】,0是局部最小
- 【N-1】<【N-2】,N-1是局部最小
- 【i-1】<【i】<【i+1】,i是局部最小
方法
- 二分法,在0-N一定存在局部最小
异或计算(无进位相加)
- 0异或N=N N异或N=0
- 满足交换律和结合律
完成数据的交换
- 引入第三方变量
int a = 1;
int b = 2;
int c = a;
a = b;
b = c;
- 单独自身计算,不引入第三方变量
int a = 1;
int b = 2;
a = a + b; //a = 3
b = a - b; //b = 3 - 2 = 1
a = a - b; //a = 3 - 1 = 2
- 使用异或(前提,值是一样的,但是a和b所处的内存区域是不同的,如果相同会出错)
int a = 1;
int b = 2;
a = a 异或 b; //a = 1 异或 2,b = 2
b = a 异或 b; //a = 1 异或 2, b = 1
a = a 异或 b; //a = 1 异或 2 异或 1 = 2, b = 1
- 错误使用
int[] arr = {4,5,3}
int i = 0;
int j = 0;
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
for(int c = 0; c < arr.length; c++){
System.out.println(arr[c]);
}
问题
1,一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这一个数
代码
public static void printOddTimesNum1(int[] arr){
int eor = 0;
for (int cur : arr){
eor ^= cur;
}
System.out.println(eor);
}
原理
- 只有一个是奇数项,将所有元素都异或,相同元素就会消失,只剩下奇数的元素,利用到了异或的交换律和结合律
2,一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到这两个数
例子
【1011,0110,0110,1011,1000,0001】
1,让所有元素异或运算,得到eor变量,这个利用相同位置上1的个数,如果是奇数个,则为1,同理,偶数个1为0
2,第一步得到的eor=1001,因为eor的末尾元素为1,则将所有末尾为1的元素进行第二次异或,得到eor’,具体是【1011,1011,0001】,则eor'=0001,eor'也为第一个奇数个元素
3,将eor和eor'进行异或,eor=1001,eor'=0001,则二者计算得到1000为第二个奇数个的元素
引出新的问题
- 上面例子中的eor=1001,我们提取最后面位数->1,是如何实现的呢?
- 例子(方法1)
01101000如何操作变成00001000呢?提取到指定位数的1,其余位数全部清零
假设 a = 01101000
1,计算 a-1,目的是打散最末尾的数字1,a-1 = 01100111
2,计算 a同或a-1 = 01100000,目的是清楚原始最右侧的1
3,计算 a异或(a同或a-1)= 00001000
-
例子(方法2)
a & (~a +1)
a = 01101000, a取反得到10010111,再 +1 得到10011111
a & (~a +1) = 01101000 & 10011111 = 00001000
算法
public static void printOddTimesNum2(int[] arr){
int eor = 0;
for (int i=0;i<arr.length;i++){
eor ^= arr[i];
//eor = a ^ b
//err != 0
//err必然有一个位置上是1
int rightOne = eor & (~eor + 1);//提取出最右侧的1
int onlyOne = 0;//eor'
for (int cur : arr){
if((cur & rightOne) == 0){
onlyOne ^= cur;
}
}
}
System.out.println(onlyOne + " " + (eor ^ onlyOne));
}
对数器
- 最简单的方法和优化的方法,使用大量的结果分别测试,如果结果是一致的,说明优化的方法是正确的。