時間複雜度、空間複雜度、排序、異或運算
時間複雜度
- 常數時間操作:
- 算數運算:+ - * /
- 位運算:>>(帶符號右移動)、 >>>(不帶符號右移動) 、 <<、 | 、& 、^
帶符號就是最高位補符號位,不帶符號就是最高位補0
- 賦值操作:比較,自增,自減操作
- 陣列定址等
總之,執行時間固定的操作都是常數時間的操作。反之執行時間不固定的操作,都不是常數時間的操作
- 通過基本動作的常數時間,推導時間複雜度
對於雙層迴圈來說,n(常數)+ (n-1)(常數)+ ... + 2(常數) + 1(常數) => 推匯出
y = an^2 + bn + c
忽略掉低階項,忽略掉常數項,忽略掉高階項的係數,得到時間複雜度為n^2
排序操作
選擇排序
package class01;
import java.util.Arrays;
public class Code01_SelectionSort {
public static void selectionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
// 0 ~ N-1
// 1~n-1
// 2
for (int i = 0; i < arr.length - 1; i++) { // i ~ N-1
// 最小值在哪個位置上 i~n-1
int minIndex = i;
for (int j = i + 1; j < arr.length; j++) { // i ~ N-1 上找最小值的下標
minIndex = arr[j] < arr[minIndex] ? j : minIndex;
}
swap(arr, i, minIndex);
}
}
public static void swap(int[] arr, int i, int j) {
int tmp = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
// for test
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
// for test
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;
}
// for test
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;
}
// for test 對數器
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;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
selectionSort(arr1);
comparator(arr2);
if (!isEqual(arr1, arr2)) {
succeed = false;
printArray(arr1);
printArray(arr2);
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
int[] arr = generateRandomArray(maxSize, maxValue);
printArray(arr);
selectionSort(arr);
printArray(arr);
}
}
氣泡排序
package class01;
import java.util.Arrays;
public class Code02_BubbleSort {
public static void bubbleSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
// 0 ~ N-1
// 0 ~ N-2
// 0 ~ N-3
for (int e = arr.length - 1; e > 0; e--) { // 0 ~ e
for (int i = 0; i < e; i++) {
if (arr[i] > arr[i + 1]) {
swap(arr, i, i + 1);
}
}
}
}
// 交換arr的i和j位置上的值
public static void swap(int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
// for test
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
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;
}
// for test
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;
}
// for test
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;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
bubbleSort(arr1);
comparator(arr2);
if (!isEqual(arr1, arr2)) {
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
int[] arr = generateRandomArray(maxSize, maxValue);
printArray(arr);
bubbleSort(arr);
printArray(arr);
}
}
插入排序
package class01;
import java.util.Arrays;
public class Code03_InsertionSort {
public static void insertionSort(int[] arr) {
if (arr == null || arr.length < 2) {
return;
}
// 0~0 有序的
// 0~i 想有序
for (int i = 1; i < arr.length; i++) { // 0 ~ i 做到有序
for (int j = i - 1; j >= 0 && arr[j] > arr[j + 1]; j--) {
swap(arr, j, j + 1);
}
}
}
// i和j是一個位置的話,會出錯
public static void swap(int[] arr, int i, int j) {
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
// for test
public static void comparator(int[] arr) {
Arrays.sort(arr);
}
// for test
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;
}
// for test
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;
}
// for test
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;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 100; // 隨機陣列的長度0~100
int maxValue = 100;// 值:-100~100
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr1 = generateRandomArray(maxSize, maxValue);
int[] arr2 = copyArray(arr1);
insertionSort(arr1);
comparator(arr2);
if (!isEqual(arr1, arr2)) {
// 列印arr1
// 列印arr2
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
int[] arr = generateRandomArray(maxSize, maxValue);
printArray(arr);
insertionSort(arr);
printArray(arr);
}
}
插入排序和前面兩種排序的不同是在於,插入排序跟陣列初始順序有關,在初始有序的情況下,有可能時間複雜度為O(N),有可能為O(N ^2),但是我們估計時間複雜度要按照最差的情況來估計,所以插入排序的時間複雜度仍然O(N ^2)
空間複雜度
申請有限幾個變數,和樣本量n沒關係,就是空間複雜度O(1),如果要開闢一個空間陣列和樣本量n是一樣大,用來支援我們的演算法流程那麼O(N)。反之使用者就是要實現陣列拷貝,我們開闢一個新的n大小陣列用來支撐使用者的需求,那麼仍然是O(1)
常數項時間複雜度
如果兩個相同時間複雜度的演算法要比較效能,這個時候需要比較單個常數項時間,對能力要求較高,沒有意義,不如樣本量試驗實際測試來比較
演算法最優解
我們認為最優解的考慮順序是,先滿足時間複雜度指標,再去使用較少的空間。一般來說,演算法題,ACM等不會卡常數項時間
常見時間複雜度
依次從好到壞 O(1) -> O(logN) -> O(N) -> O(N*logN) -> O(N^2) -> O(N^3) ... -> O(N!)
演算法和資料結構脈絡
- 知道怎麼算的演算法
- 知道怎麼試的演算法(遞迴)
認識對數器
- 準備你想要測試的方法a
- 實現一個複雜度不好,但是容易實現的方法b
- 實現一個隨機樣本產生器
- 把方法a和方法b跑相同的隨機樣本,看看得到的結果是否一樣
- 如果有一個隨機樣本使得對比結果不一致,列印樣本進行人工干預,改對方法a和方法b
- 當樣本數量很多,測試對比依然正確,可以確定方法a已經正確
認識二分法
- 在一個有序陣列中,找某個數是否存在
二分查詢值,基於有序陣列,演算法複雜度為二分了多少次,O(log2N)可以寫成O(logN)
123579
package class01;
import java.util.Arrays;
public class Code04_BSExist {
public static boolean exist(int[] sortedArr, int num) {
if (sortedArr == null || sortedArr.length == 0) {
return false;
}
int L = 0;
int R = sortedArr.length - 1;
int mid = 0;
// L..R
while (L < R) {
// mid = (L+R) / 2;
// L 10億 R 18億
// mid = L + (R - L) / 2
// N / 2 N >> 1
mid = L + ((R - L) >> 1); // mid = (L + R) / 2
if (sortedArr[mid] == num) {
return true;
} else if (sortedArr[mid] > num) {
R = mid - 1;
} else {
L = mid + 1;
}
}
return sortedArr[L] == num;
}
// for test
public static boolean test(int[] sortedArr, int num) {
for(int cur : sortedArr) {
if(cur == num) {
return true;
}
}
return false;
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
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 main(String[] args) {
int testTime = 500000;
int maxSize = 10;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr = generateRandomArray(maxSize, maxValue);
Arrays.sort(arr);
int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
if (test(arr, value) != exist(arr, value)) {
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
}
}
- 在一個有序陣列中,找>=某個數最左側的位置
122222333578888999999 找大於等於2最左側的位置
package class01;
import java.util.Arrays;
public class Code05_BSNearLeft {
// 在arr上,找滿足>=value的最左位置
public static int nearestIndex(int[] arr, int value) {
int L = 0;
int R = arr.length - 1;
int index = -1; // 記錄最左的對號
while (L <= R) {
int mid = L + ((R - L) >> 1);
if (arr[mid] >= value) {
index = mid;
R = mid - 1;
} else {
L = mid + 1;
}
}
return index;
}
// for test
public static int test(int[] arr, int value) {
for (int i = 0; i < arr.length; i++) {
if (arr[i] >= value) {
return i;
}
}
return -1;
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
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;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 10;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr = generateRandomArray(maxSize, maxValue);
Arrays.sort(arr);
int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
if (test(arr, value) != nearestIndex(arr, value)) {
printArray(arr);
System.out.println(value);
System.out.println(test(arr, value));
System.out.println(nearestIndex(arr, value));
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
}
}
- 在一個有序陣列中,找<=某個數最右側的位置
package class01;
import java.util.Arrays;
public class Code05_BSNearRight {
// 在arr上,找滿足<=value的最右位置
public static int nearestIndex(int[] arr, int value) {
int L = 0;
int R = arr.length - 1;
int index = -1; // 記錄最右的對號
while (L <= R) {
int mid = L + ((R - L) >> 1);
if (arr[mid] <= value) {
index = mid;
L = mid + 1;
} else {
R = mid - 1;
}
}
return index;
}
// for test
public static int test(int[] arr, int value) {
for (int i = arr.length - 1; i >= 0; i--) {
if (arr[i] <= value) {
return i;
}
}
return -1;
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
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;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 10;
int maxValue = 100;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr = generateRandomArray(maxSize, maxValue);
Arrays.sort(arr);
int value = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
if (test(arr, value) != nearestIndex(arr, value)) {
printArray(arr);
System.out.println(value);
System.out.println(test(arr, value));
System.out.println(nearestIndex(arr, value));
succeed = false;
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
}
}
- 區域性最小值問題
無序陣列,任意兩個相鄰的數不相等,返回一個區域性最小值
package class01;
public class Code06_BSAwesome {
public static int getLessIndex(int[] arr) {
if (arr == null || arr.length == 0) {
return -1; // no exist
}
if (arr.length == 1 || arr[0] < arr[1]) {
return 0;
}
if (arr[arr.length - 1] < arr[arr.length - 2]) {
return arr.length - 1;
}
int left = 1;
int right = arr.length - 2;
int mid = 0;
while (left < right) {
mid = (left + right) / 2;
if (arr[mid] > arr[mid - 1]) {
right = mid - 1;
} else if (arr[mid] > arr[mid + 1]) {
left = mid + 1;
} else {
return mid;
}
}
return left;
}
}
認識異或運算
異或運算:相同為0,不同為1
同或運算:相同為1, 不同為0,不掌握
上述特別不容易記住,異或運算就記成無進位相加:比如十進位制6異或7,就理解為110和111按位不進位相加,得到001
- 所以 0^N = N , N^N = 0
- 異或運算滿足交換律和結合律,所以A異或B異或C = A異或(B異或C) = (A異或C)異或B
題目一:如何不用額外變數就交換兩個數
a = x b = y兩個數交換位置
a = a ^ b # 第一步操作,此時 a = x^y , b=y
b = a ^ b # 第二步操作,此時 a = x^y , b = x^y^y => b = x^0 => b = x
a = a ^ b # 第三步操作,此時 a = x^y^x, b = x, a=> x^x^y => a=y
三步操作,實現交換ab的值
package class01;
public class Test {
public static void main(String[] args) {
int a = 6;
int b = 6;
a = a ^ b;
b = a ^ b;
a = a ^ b;
System.out.println(a);
System.out.println(b);
int[] arr = {3,1,100};
System.out.println(arr[0]);
System.out.println(arr[2]);
swap(arr, 0, 0);
System.out.println(arr[0]);
System.out.println(arr[2]);
}
public static void swap (int[] arr, int i, int j) {
// arr[0] = arr[0] ^ arr[0];
arr[i] = arr[i] ^ arr[j];
arr[j] = arr[i] ^ arr[j];
arr[i] = arr[i] ^ arr[j];
}
}
注意,如果a和b指向同一塊記憶體,改方法不可行
題目二:一個陣列中有一種數出現了奇數次,其他數都出現了偶數次,怎麼找到並列印這種數
[2,2,1,3,2,3,2,1,1] 陣列中存在四個2,兩個3,三個1,定義一個常量等於0,分別對該陣列中的數遍歷一遍進行異或,最後,該變數等於多少,那麼奇數的值就是多少。因為異或運算滿足交換和結合律
題目三:怎麼把一個int型別的數,提取出最右側的1來
n與上(n取反加1)即可 => N & ( (~N)+1 )
題目四:一個陣列中有兩種不相等的數出現了奇數次,其他數出現了偶數次,怎麼找到並列印這兩種數
定義一個常量eor = 0,分別對該陣列每個數異或,最終結果為a異或b,其中a和b就是這兩個奇數,由於a!=b所以a異或b不等於0,即eor的值某一位上一定為1(有可能不止一個1隨便選一個例如第八位),用該位做標記對原有陣列的數進行分類,那麼a和b由於第八位不相同一定被分開,再定義常量eor' = 0分別對第八位為0的數異或,那麼得到的值,就是a和b其中一個,由於之前eor = a異或b,那麼在用eor和eor'異或,就是另外一個值。一般來說,隨便找一個1我們就找最右側的那個1,如題目三
package class01;
public class Code07_EvenTimesOddTimes {
// arr中,只有一種數,出現奇數次
public static void printOddTimesNum1(int[] arr) {
int eor = 0;
for (int i = 0; i < arr.length; i++) {
eor ^= arr[i];
}
System.out.println(eor);
}
// arr中,有兩種數,出現奇數次
public static void printOddTimesNum2(int[] arr) {
int eor = 0;
for (int i = 0; i < arr.length; i++) {
eor ^= arr[i];
}
// eor = a ^ b
// eor != 0
// eor必然有一個位置上是1
// 0110010000
// 0000010000
int rightOne = eor & (~eor + 1); // 提取出最右的1
int onlyOne = 0; // eor'
for (int i = 0 ; i < arr.length;i++) {
// arr[i] = 111100011110000
// rightOne= 000000000010000
if ((arr[i] & rightOne) != 0) {
onlyOne ^= arr[i];
}
}
System.out.println(onlyOne + " " + (eor ^ onlyOne));
}
public static int bit1counts(int N) {
int count = 0;
// 011011010000
// 000000010000 1
// 011011000000
//
while(N != 0) {
int rightOne = N & ((~N) + 1);
count++;
N ^= rightOne;
// N -= rightOne
}
return count;
}
public static void main(String[] args) {
int a = 5;
int b = 7;
a = a ^ b;
b = a ^ b;
a = a ^ b;
System.out.println(a);
System.out.println(b);
int[] arr1 = { 3, 3, 2, 3, 1, 1, 1, 3, 1, 1, 1 };
printOddTimesNum1(arr1);
int[] arr2 = { 4, 3, 4, 2, 2, 2, 4, 1, 1, 1, 3, 3, 1, 1, 1, 4, 2, 2 };
printOddTimesNum2(arr2);
}
}