
文章前言
- 【一起学算法】专栏持续更新中,会在这里记录算法学习的过程
- 文末获取【一起学算法】
Github仓库手写算法源码
,一起跟着写一遍吧~- 【一起学算法】中所涉及的部分关于
leetcode
中的原题均可在leetcode官网的运行器通关~
时间复杂度
什么是时间复杂度
常数时间的操作:固定时间完成操作
常见算术运算(+、-、*、/、%)等
常见位运算(>>、>>>、<<、|、&、^)等
赋值、比较、自增、自减操作等
数组寻址
非常数时间的操作:LinkedList寻址
时间复杂度估算
等差数列求和公式:aN²+bN+c
当N->无穷,常数项可忽略,只看N的最高阶,比如一个算法时间复杂度为O(N³),另一个算法时间复杂度为O(N²),两个算法干的是同一件事的情况下 O(N²) 优于 O(N³)
额外空间复杂度
- 实现一个算法,需要开辟一些空间来支持算法流程
- 必要的或者和实现目标有关的空间都不算做额外空间
- 作为输入参数的空间
- 作为输出结果的空间
- 只需要开辟有限几个变量:额外空间复杂度为O(1)
常数项
当时间复杂度相同时,通过比常数项来判断算法的优劣
常数项对比
- 放弃理论,生成随机数据进行测试(数据量*测试次数 以最终耗费时间做对比 )
最优解
时间复杂度尽可能的低
尽可能优化空间
常见时间复杂度排名
O(1) O(logN) O(N) O(N*logN)
O(N²) O(N³) ··· O(N^K)
O(2^N) O(3^N) ··· O(k^N)
O(N!)
对数器
import java.util.Arrays;
/**
* @author Rhys.Ni
* @version 1.0
* @date 2021/12/1 3:22 上午
* @Description 数组排序对数器
*/
public class DataTester {
public static void main(String[] args) {
int testCount = 500000;
int maxSize = 100;
int maxValue = 100;
boolean isSuccess = true;
for (int i = 0; i < testCount; i++) {
int[] arr = generateRandomArray(maxSize, maxValue);
int[] arr1 = copyArray(arr);
int[] arr2 = copyArray(arr);
//需要测试的排序方法
SelectionSort.sort(arr1);
//用系统自带排序作为对数器和手写的排序方法做对比
comparator(arr2);
if (!isEqual(arr1, arr2)) {
for (int j = 0; j <arr.length ; j++) {
System.out.print(arr[j]+" ");
}
System.out.println();
isSuccess = false;
break;
}
}
System.out.println(isSuccess?"Nice!":"Fucking fucked!");
}
public static int[] generateRandomArray(int maxSize, int maxValue) {
// Math.random() -> [0,1) 所有的小数,等概率返回一个
// Math.random() * N -> [0,N) 所有小数,等概率返回一个
// (int)(Math.random() * N) -> [0,N-1] 所有的整数,等概率返回一个
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
public static int[] copyArray(int[] arr) {
if (arr == null) {
return null;
}
int[] res = new int[arr.length];
for (int i = 0; i < arr.length; i++) {
res[i] = arr[i];
}
return res;
}
public static boolean isEqual(int[] arr1, int[] arr2) {
if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
return false;
}
if (arr1 == null && arr2 == null) {
return true;
}
if (arr1.length != arr2.length) {
return false;
}
for (int i = 0; i < arr1.length; i++) {
if (arr1[i] != arr2[i]) {
return false;
}
}
return true;
}
}
异或运算
int a=7;
int b=13;
//a ^ b = ?
//相同为0,不同为1
//a的二进制:0111
//b的二进制:1101
a ^ b = 10;
-
异或相加又称无进位相加
-
异或三大性质(技巧)
- 0 ^ N = N
- N ^ N = 0
- 有一批数做异或运算,同时满足交换律和结合律,得到的结果是同一个数
- 偶数个1结果一定是0
- 奇数个1结果一定是1
题记
-
两数值交换
int temp = 0; temp = a; a = b; b = temp; //以上代码可用异或代替 1、a = a ^ b; //第一步:设 a=甲 ,b=乙 得:a = 甲 ^ 乙 //将a = 甲 ^ 乙带入第二步中 2、b = a ^ b; //第二步:b = 甲 ^ 乙 ^ 乙 //由N ^ N = 0 (乙^乙=0)得:b = 甲 ^ 0 //由0 ^ N = N 得 b = 甲 //将a = 甲^乙 ,b = 甲带入第二步中 3、a = a ^ b; //第三步:a = 甲^乙^甲 = 甲^甲^乙 = 0^乙 = 乙 //至此 a = 乙 , b = 甲,完成了两数的互换 //注意:此方法只可以用作两个不同区域的 /** * @author Rhys.Ni * @version 1.0 * @date 2021/12/3 12:47 上午 * @Description 两值互换 */ public class TwoValueExchange { public static void main(String[] args) { twoValueExchange(); twoValueExchangeForArray(); } /** * 不用额外变量交换两个数的值 */ public static void twoValueExchange() { int a = 10; int b = 4; a = a ^ b; b = a ^ b; a = a ^ b; System.out.println("a=" + a); System.out.println("b=" + b); } /** * 不用额外变量交换数组中两个数的值 */ public static void twoValueExchangeForArray() { int[] arr = {1, 5, 3, 6}; printArray(arr); //交换1、3位置的值 arr[1] = arr[1] ^ arr[3]; arr[3] = arr[1] ^ arr[3]; arr[1] = arr[1] ^ arr[3]; printArray(arr); } public static void printArray(int[] arr) { for (int i = 0; i < arr.length; i++) { System.out.print(arr[i] + " "); } System.out.println(); } }
-
怎么把一个int类型的数,提取出二进制中最右侧的1来
// 运用 a & (-a) 技巧 //设:a = 01101110010000 //第一步:对a取反 //得:~a = 10010001101111 //根据 -a = ~a+1 //得:-a = 10010001110000 // a & (-a) = 00000000010000
-
找数
- 一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数?
- 一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这两种数
/** * @author Rhys.Ni * @version 1.0 * @date 2021/12/2 10:10 下午 * @Description */ public class EvenTimesOddTimes { public static void main(String[] args) { findOneKindOddNum(); findTwoKindsOddNum(); } //一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这种数? //利用性质三: // 有一批数做异或运算,同时满足交换律和结合律,得到的结果是同一个数 // 偶数个1结果一定是0 // 奇数个1结果一定是1 public static void findOneKindOddNum() { int[] arr = {7, 4, 5, 7, 6, 7, 4, 5, 6}; int exclusiveOr = 0; for (int i = 0; i < arr.length; i++) { exclusiveOr ^= arr[i]; } // 44 55 66 777 // 4^4=0 5^5=0 6^6=0 7^7=0 最终三个7消掉两个剩一个7直接返回 System.out.println(exclusiveOr); } //一个数组中有两种数出现了奇数次,其他数都出现了偶数次,怎么找到并打印这两种数 public static void findTwoKindsOddNum() { int[] arr = {7, 4, 5, 7, 6, 7, 5, 6}; int exclusiveOr = 0; for (int i = 0; i < arr.length; i++) { exclusiveOr ^= arr[i]; } //利用 -a = ~a+1 提取最右边的1 int oneRight = exclusiveOr & (-exclusiveOr); int exclusiveOr2 = 0; for (int i = 0; i < arr.length; i++) { //4:0100 5:0101 6:0110 7:0111 //只将最右边为1的数参与异或运算,与运算性质是只有两个位上都是1结果才为1,所以两个数与运算结果!=0的时候必然 是两种数中的其中一个 if ((arr[i] & oneRight) != 0) { exclusiveOr2 ^= arr[i]; } } //既然已经得到两种数中的其中一种,那么另外一种就直接用 exclusiveOr ^ exclusiveOr2 即可得出 System.out.println(exclusiveOr2 + " , " + (exclusiveOr ^ exclusiveOr2)); } }
-
一个数组中有一种数出现K次,其他数都出现了M次,已知M > 1,K < M,找到出现了K次的数,要求额外空间复杂度O(1),时间复杂度O(N)
import java.util.HashMap; import java.util.HashSet; /** * @author Rhys.Ni * @version 1.0 * @date 2021/12/3 3:08 上午 * @Description */ public class KM { public static void main(String[] args) { int kinds = 5; //设置需要生成几种数,至少两种数 int maxValue = 100; int testTimes = 100000; for (int i = 0; i < testTimes; i++) { //----------------------------------- //以下代码保证了始终满足k<m条件 int a = (int) (Math.random() * 9 + 1); int b = (int) (Math.random() * 9 + 1); int k = Math.min(a, b); int m = Math.max(a, b); if (k == m) { m++; } //----------------------------------- int numKinds = (int) (Math.random() * kinds) + 2; int[] arr = randomArray(numKinds, maxValue, k, m); int res1 = test(arr, k); int res2 = findKTimesNum(arr, k, m); if (res1 != res2) { System.out.println("出错了! " + res2); } } System.out.println("Nice!"); } /** * 随机生成一个符合题意的数组 * * @param maxValue * @param k * @param m * @return */ public static int[] randomArray(int numKinds, int maxValue, int k, int m) { //首先设置一个数往数组里塞k次 int kNum = randomNum(maxValue); //由k , m ,以及生成几种数决定数组长度 // k+(numKinds-1)*m int[] arr = new int[k + (numKinds - 1) * m]; int i = 0; for (; i < k; i++) { arr[i] = kNum; } //第一种数添加完成后减少一种数 numKinds--; //生成的数要不能重复,所以建立hash表来记录已经存在的数 HashSet<Integer> set = new HashSet<>(); set.add(kNum); while (numKinds != 0) { int currNum = 0; do { //如果hash表已经存在生成的数就重新生成; currNum = randomNum(maxValue); } while (set.contains(currNum)); set.add(currNum); numKinds--; //填充m个 for (int j = 0; j < m; j++) { //接着i的位置往后填充 arr[i++] = currNum; } } return upset(arr); } /** * 将规律的数组打乱 * * @param arr * @return */ public static int[] upset(int[] arr) { for (int i = 0; i < arr.length; i++) { int j = (int) (Math.random() * arr.length); int temp = arr[i]; arr[i] = arr[j]; arr[j] = temp; } return arr; } /** * 随机生成一个 -num ~ num的数 * * @param range * @return */ public static int randomNum(int range) { return (int) (Math.random() * range + 1) - (int) (Math.random() * range + 1); } /** * 寻找出现k次的数 * * @param arr * @param m * @return */ public static int findKTimesNum(int[] arr, int k, int m) { //设k=3;M=5 满足M > 1,K < M int[] initArr = new int[32]; for (int num : arr) { for (int j = 0; j < initArr.length; j++) { //&1的含义:j=0 时,num=0011 ,0011 & 1 = 1 initArr[0]+=1 在initArr[0]这个位置上记录一次 //j = 1 时,num >> 1 = 0001 ,0001 & 1 = 1 initArr[1]+=1 initArr[1]记录一次 //j = 2 时,num >> 2 = 0000 , 0000 & 1 = 0 initArr[1]+=0 相当于initArr[2]位置不记录 //以此类推,相当于从最低位到最高位挨个记录对应位置为1的次数 initArr[j] += (num >> j) & 1; } } //new一个32位int类型变量,遍历记录完各个数出现次数的initArr数组,用每个位上的次数对K和M取模,结果为1的说明这个 数在第i位上为1 //逐位往new的变量里面设置值,最终得到一个数 int result = 0; for (int i = 0; i < initArr.length; i++) { if (initArr[i] % m != 0) { //在第几位上有1就将result的第几位设置成1 // 如:第2位上有1时 1(0001)<<2 = 0100 ;0100 | result(0000) = 0100 // 第3位上有1时 1(0001)<<3 = 1000 ;1000 | result(0100) = 1100 // 得到最终result为1100 (此结果为举例结果,非题解) result |= (1 << i); } } return result; } /** * 对数器遍历计数法 百分百正确的结果 * * @param arr * @param k * @return */ public static int test(int[] arr, int k) { HashMap<Integer, Integer> map = new HashMap<>(); for (int num : arr) { if (map.containsKey(num)) { map.put(num, map.get(num) + 1); } else { map.put(num, 1); } } for (int num : map.keySet()) { if (map.get(num) == k) { return num; } } return -1; } }
-
一个数组中有一种数出现K次,其他数都出现了M次,已知M > 1,K < M,找到出现了K次的数,并且当这个数没有出现K次的时候返回-1要求额外空间复杂度O(1),时间复杂度O(N)
//findKTimesNum方法改写 public static int findKTimesNum(int[] arr, int k, int m) { //设k=3;M=5 满足M > 1,K < M int[] initArr = new int[32]; for (int num : arr) { for (int j = 0; j < initArr.length; j++) { //&1的含义:j=0 时,num=0011 ,0011 & 1 = 1 initArr[0]+=1 在initArr[0]这个位置上记录一次 //j = 1 时,num >> 1 = 0001 ,0001 & 1 = 1 initArr[1]+=1 initArr[1]记录一次 //j = 2 时,num >> 2 = 0000 , 0000 & 1 = 0 initArr[1]+=0 相当于initArr[2]位置不记录 //以此类推,相当于从最低位到最高位挨个记录对应位置为1的次数 initArr[j] += (num >> j) & 1; } } //new一个32位int类型变量,遍历记录完各个数出现次数的initArr数组,用每个位上的次数对K和M取模,结果为1的说明这个数在第i位上为1 //逐位往new的变量里面设置值,最终得到一个数 int result = 0; for (int i = 0; i < initArr.length; i++) { if (initArr[i] % m == 0) { continue; } if (initArr[i] % m == k) { result |= (1 << i); } else { result = -1; } } //解决0没有出现k次的情况 if (result==0){ int count = 0; for (int num:arr) { if (num==0){ count++; } } if (count!=k){ return -1; } } return result; } //randomArray方法改写 public static int[] randomArray(int numKinds, int maxValue, int k, int m) { //首先设置一个数往数组里塞k次 int kNum = randomNum(maxValue); //使生成k次的几率为50%,50%几率随机生成一个比m小的数; int times = Math.random()<0.5?k: (int) ((Math.random() * (m - 1)) + 1); int[] arr = new int[times + (numKinds - 1) * m]; int i = 0; for (; i < times; i++) { arr[i] = kNum; } //第一种数添加完成后减少一种数 numKinds--; //生成的数要不能重复,所以建立hash表来记录已经存在的数 HashSet<Integer> set = new HashSet<>(); set.add(kNum); while (numKinds != 0) { int currNum = 0; do { //如果hash表已经存在生成的数就重新生成; currNum = randomNum(maxValue); } while (set.contains(currNum)); set.add(currNum); numKinds--; //填充m个 for (int j = 0; j < m; j++) { //接着i的位置往后填充 arr[i++] = currNum; } } return upset(arr); } //对数器改写 public static void main(String[] args) { int kinds = 5; //设置需要生成几种数,至少两种数 int maxValue = 100; int testTimes= 100000; for (int i = 0; i < testTimes; i++) { //----------------------------------- //以下代码保证了始终满足k<m条件 int a = (int) (Math.random() * 9 + 1); int b = (int) (Math.random() * 9 + 1); int k = Math.min(a, b); int m = Math.max(a, b); if (k == m) { m++; } //----------------------------------- int numKinds = (int) (Math.random() * kinds) + 2; int[] arr = randomArray(numKinds, maxValue, k, m); int res1 = test(arr, k); int res2 = findKTimesNum(arr, k, m); if (res1 != res2) { System.out.println("出错了! "+res1 +" "+ res2); } } System.out.println("Nice!"); }
源码获取地址
加油小伙伴~ 点击获取算法篇代码~