一、时间复杂度与空间复杂度
时间复杂度就是统计一个算法中的常数操作
常数操作有 数组寻址操作、循环遍历操作、加减乘除等,与数据量有关都不是常数操作
空间复杂度指程序执行所需要使用的空间大小 常数为O(1)
在不使用额外空间的情况下更换两数的值
无进位相加
通过使用异或的性质 ^ :
- 0 ^ N = N N ^ N = 0
- (a^b)^c = a^(b^c)
- a^b^a = b^(a^a) = b^0 = b
//使a、b交换值 真的妙 a = a ^ b; b = a ^ b; ---> 结果 b = (a^b)^b = a^(b^b) = a^0 = a; a = a ^ b; ---> 结果 a = a^a^b = 0^b = b; //简化 //防止出现两指针位置相同可以排除额外情况进行交换 if(a == b){ return; } a ^= b; b ^= a; a ^= b;
使用这种方式的前提是 a和b值可以相同但一定不会指向同一块地址,如果他们指向同一块地址(位置) 说明a和b完全相同,则异或会为0,使用异或交换的本质是交换地址,基本类型是值交换
二、位运算
按位与( & ): 对应位数都为1 结果为1 否则为0
按位或( | ):对应位数为0 结果为0 否则为1
按位异或( ^ ):对应位数 相同为0 不同为1
面试题一、
一个数组中只有一种数是奇数个,其余为偶数个,求这个奇数个的数
题解:对数组中所有的元素进行异或,根据异或的特性,相同数异或为0,0与任何数异或都是他本身。
public static void num(String[] args){
int[] arr = {1,2,3,2,1};
for(int i=1; i<arr.length;i++){
arr[0] ^= arr[i];
}
System.out.println(arr[0]);
}
面试题二、
一个数组中,有两种数是奇数个a、b,其他都为偶数个,求这两个奇数个的数
题解:对数组元素进行异或,最终得到的结果是两个奇数个数a、b, 异或的结果err,然后获取err中位数为1的位数(因为a与b不相同,所以他们异或的结果肯定至少有一位不为一),那么a和b在此位中一个为0一个为1,找出此位不为一的所有数进行异或,得到的结果就是a或者b,再将这个结果与err进行异或得到另一个值
public static void main(String[] args){
int arr[] = {1,2,3,4,5,3,2,1};
//异或结果
int err = 0;
for(int i=0;i<arr.length;i++){
err ^= arr[i];
}
//a、b不相同 进行异或至少有一位为1
//获取结果err最右侧为1的位数 000010
int onlyOne = err & (~err +1); //原数与其补码与运算得到最右侧的1
//err2为 a或者b
int err2 = 0;
for(int i=1;i < arr.length;i++){
//找出所有onlyOne对应位上不为1的所有数进行异或得到a或b
//例如 100101 & 000010 -> 000000
if((arr[i] & onlyOne) == 0){
err2 ^= arr[i];
}
}
System.out.println("第一个数为" + err2);
System.out.println("第二个数为" + err ^ err2);
}
三、排序算法 O(n^2)
1、插入排序
插入排序就像是打扑克牌时进行排序一样,抽到一张牌然后一次找到合适的位置将他插入,先在0~2范围内进行排序 然后0~3范围内进行排序,相当于将第三张牌在前两个位置找到合适地方插入,如果第三张牌比前面的大就进行交换
public class insertSort {
//插入排序
public static void main(String[] args) {
int[] arr = {1,2,8,9,7,6,5};
//依次获取 0~1 0~2 0~3 0~3 0~i
//不需要判断0~0 所以从1开始
for(int i = 1 ; i < arr.length; i++ ) {
//依次在每个范围内进行判断大小并交换
//取最后一个数进行排序 相当于抽一张扑克牌放到抽好的范围内
for(int j = i - 1 ; j >= 0 && arr[j] > arr[j + 1]; j--) {
//满足条件 j大于等于0 防止数组越界
//并且左边的值比右边大进行交换
swap(arr,j,j+1);
}
}
System.out.println(Arrays.toString(arr));
}
/**
* 必须传入数组,才能交换数据 交换地址引用
*/
public static void swap(int[] arr,int i,int j) {
arr[i] ^= arr[j];
arr[j] ^= arr[i];
arr[i] ^= arr[j];
}
}
2、选择排序
选择排序就是每次将最小值移到最前面,每次定义循环第一位为最小值,如果遍历到比最小值还小的数,就进行交换 (选择排序更优)
public class bubbleSort {
public static void main(String[] args) {
int[] arr = {1,2,8,9,7,6,5};
//定义循环次数
for(int i = 0 ; i < arr.length; i++) {
//将每次的循环第一位作为最小值
int minIndex = i;
//从最小值后一位向后遍历 如果比最小值还小就进行交换
for(int j = i + 1 ; j < arr.length; j++) {
if(arr[j] < arr[minIndex]) {
swap(arr,j,minIndex);
}
}
}
System.out.println(Arrays.toString(arr));
}
public static void swap(int[] arr,int i,int j) {
arr[i] ^= arr[j];
arr[j] ^= arr[i];
arr[i] ^= arr[j];
}
}
3、冒泡排序
冒泡排序区别于选择排序,冒泡排序是每次找出最大值,并且是在两个相邻元素之间进行交换,如果前一个数比当前数小那么就交换两数,最终获取一轮循环的最大值,在进行n-1次循环,依次向后找出每轮的最大值并交换到数组末尾
public class bubbleSort {
public static void main(String[] args) {
int[] arr = {1,5,9,7,6,3,2};
for(int i = arr.length - 1; i > 0 ; i--) {
for(int j = 0; j < i; j++) {
if(arr[j] > arr[j + 1]) {
swap(arr,j,j+1);
}
}
}
System.out.println(Arrays.toString(arr));
}
public static void swap(int[] arr,int i,int j) {
arr[i] ^= arr[j];
arr[j] ^= arr[i];
arr[i] ^= arr[j];
}
}
补充:二分查找
public void twoSelect() {
int[] arr1 = {1,2,3,5,7,9};
int val = 7;
int n = arr1.length;
int left = 0;
int right = n - 1 ;
while(left <= right) {
//int mid = left + (right - left)/2 右移一位比除二更快;
int mid = left + (right - left)>>1;
if(arr1[mid] < val) {
left = mid + 1;
}else {
right = mid - 1 ;
}
}
System.out.println(left);
}
4、递归算法 O(NlogN)
master公式 计算递归算法的时间复杂度
前提:每次递归调用子问题的规模都是等量的
T(N)= a * T(N/b) + O(N^d)
母问题 = 子问题被调用的次数 * 子问题每次调用的规模 + 剩下的时间复杂度
5、归并排序 -- O(NlogN)、空间复杂度O(N)
采用了分治思想:将大的事物通过递归逐步划分成小的具体实现 使用空间换时间
public class mergeSort {
public static void main(String[] args) {
int[] arr = {1,5,9,7,2,3,4,6};
process(arr,0,arr.length-1);
System.out.println(Arrays.toString(arr));
}
public static void process(int[] arr,int L,int R) {
if(L == R) {
return;
}
int mid = L + ((R - L) >> 1);
//分治思想 先递归
process(arr,L,mid);
process(arr,mid +1,R);
//再合并排序
merge(arr,L,mid,R);
}
public static void merge(int[] arr,int L,int M,int R) {
//L~R上一共有多少个数就开多大空间
int[] temp = new int[R-L+1];
//定义一个变量作为temp的指针
int i = 0;
//定义两个指针分别指向两个数组的起始位置
//指向左部分
int p1 = L;
//指向右部分
int p2 = M + 1;
while(p1 <= M && p2 <= R) {
temp[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
}
//经过了循环后查看哪一个指针没有越界
//将没越界的全部复制进去
while(p1 <= M) {
temp[i++] = arr[p1++];
}
while(p2 <= R) {
temp[i++] = arr[p2++];
}
//对临时数组进行复制
for(i = 0 ; i < temp.length; i++) {
arr[i + L] = temp[i];
}
}
}