合併兩個有序數組,根據題目的條件不一樣,細節處理也不一樣。我想先單純的討論下合併兩個有序數組,不作時間及空間複雜度的限制。算法
這裏,只合並兩個有序數組,不是徹底在LeetCode88題的條件上解題,我只是想挖掘多一點的思路。在這裏我只合併兩個有序數組爲一個有序數組,沒有其餘條件限制,後面將討論LeetCode88題的解法。 高手可略過。
有兩種思路,一種是先合併數組,而無論它是否有序,而後將合併以後的數組排序。另外一種思路是,在合併數組的過程當中,對元素進行排序,合併結束,排序也結束,這種思路借鑑了歸併排序的歸併過程,即它用到了數組有序這個條件。1、先將兩個數組合並,而後再排序。數組
開闢一個新數組來存儲這兩個數組中的元素,須要O(m+n)的空間複雜度。將兩個數組合並,此處不考慮這兩個數組有序,所以合併數組時間花費爲O(m+n),而後排序數組(各類排序均可),這裏考慮想要較好的時間複雜度,所以用快速排序,時間複雜度爲O((m+n)lg(m+n)),綜合起來,最終的時間複雜度爲O((m+n)lg(m+n)),空間複雜度O(m+n)。函數
#include <stdio.h> #include <stdlib.h> void exchange(int* x, int* y) { int temp = *x; *x = *y; *y = temp; } void mergeArr(int *arr, int len, int *arr1, int len1, int *arr2, int len2) { for (int i = 0; i < len; i++) { if (i < len1) { arr[i] = arr1[i]; } else { arr[i] = arr2[i - len1]; } } } // 快速排序是一種原址排序,不須要分配新的數組空間 // 只須要分配常量空間來存儲主元以及用於數組元素交換 // start和end爲子數組下標的起始位置與終止位置 // 快速排序的分割數組步驟,這是快速排序的核心 int partition(int *arr, int start, int end) { // 獲取主元 int key = arr[end]; // left不存在,故將i取值爲start-1 int i = start - 1; for (int j = start; j < end; j++) { // 若是arr[j]小於主元,則將i遞增,那麼左邊小於主元的數組的 // 長度增長了1,同時將arr[i]與arr[j]交換。 // 由於在i遞增以後,指向的是一個大於主元的值 // 而arr[j]則是一個小於主元的值,所以交換他們的值,交換完成 // 則i及i以前的元素都是小於主元的值,i+1到j之間則是大於主元的值 // j到end-1之間是不肯定大小的值,end即主元 if (arr[j] <= key) { i = i + 1; exchange(arr + i, arr + j); } } exchange(arr + i + 1, arr + end); return i + 1; } // 快排也用到了分治的思想,即將大問題分割爲性質相同的小問題, // 小問題繼續分割爲更小的問題,一直到不能再分割, // 而後將小問題的解合併起來,就得到最終的結果 // 所以,快排是先分割,而後在子數組上遞歸調用本身 void quickSort(int *arr, int start, int end) { if (start < end) { int mid = partition(arr, start, end); quickSort(arr, start, mid - 1); quickSort(arr, mid + 1, end); } } void mergeArrSorted(int *arr, int len, int *arr1, int len1, int *arr2, int len2) { mergeArr(arr, len, arr1, len1, arr2, len2); printf("merge Array: %d\n", len); quickSort(arr, 0, len - 1); } int main (void) { int arr1[] = {1, 9, 3, 4, 79, 27}; int arr2[] = {2, 56, 35, 7, 19}; int len1 = sizeof(arr1) / sizeof(*arr1); int len2 = sizeof(arr2) / sizeof(*arr2); int len = len1 + len2; printf("length %d length\n", len); int* arr = (int*)malloc(len * sizeof(int)); mergeArrSorted(arr, len, arr1, len1, arr2, len2); for (int k = 0; k < len; k++) { printf("%d ", arr[k]); } printf("\n"); }
在把數組傳遞爲函數的實參時,也要顯式的傳遞函數長度。由於函數sizeof在編譯時求值,傳遞給函數的實參所指向的數組長度肯定,可是函數的形參獲取到的數組的長度在編譯時是不肯定的,所以在函數內部使用sizeof獲取數組長度會出現不肯定的值。
所以,在C語言中,傳遞數組的同時,也要顯式傳遞數組的長度。
2、開闢一個新的數組arr,用來存儲這兩個有序數組中的元素。這裏用到歸併排序的merge方法,即將兩個指針指向兩個數組頭部,而後比較指針所指的元素,將較小的元素保存到arr數組,並將其指針後移。重複上面的步驟,直到兩個數組遍歷徹底。這種方法,須要循環m+n次,所以時間複雜度爲O(m+n),可是由於須要開闢額外的空間來存儲這兩個數組的元素,所以空間複雜度爲O(m+n)。
void mergeArrSorted(int* arr, int len, int *arr1, int len1, int *arr2, int len2) { int index1 = 0; int index2 = 0; for (int k = 0; k < len; k++) { // 若是是其餘語言,能夠在兩個數組末尾各放一個Infiniti值, // 這樣判斷比較到末尾時,恰好循環len次,下面的代碼也可精簡爲 // if (arr1[index1] <= arr2[index2]) // { // arr[k] = arr1[index1]; // index1++; // } // else // { // arr[k] = arr2[index2]; // index2++; // } // 由於這樣就少了數組越界的判斷 // 下面的代碼的三個外層判斷,就是爲了防止數組訪問越界 if (index1 < len1 && index1 < len2) { if (arr1[index1] <= arr2[index2]) { arr[k] = arr1[index1]; index1++; } else { arr[k] = arr2[index2]; index2++; } } else if (index1 < len1 && index1 >= len2) { arr[k] = arr1[index1]; index1++; } else if (index1 >= len1 && index1 >= len2) { arr[k] = arr2[index2]; index2++; } } }
下面是測試代碼:性能
#include <stdio.h> #include <stdlib.h> void mergeArrSorted(int* arr, int len, int *arr1, int len1, int *arr2, int len2) { int index1 = 0; int index2 = 0; for (int k = 0; k < len; k++) { if (index1 < len1 && index1 < len2) { if (arr1[index1] <= arr2[index2]) { arr[k] = arr1[index1]; index1++; } else { arr[k] = arr2[index2]; index2++; } } else if (index1 < len1 && index1 >= len2) { arr[k] = arr1[index1]; index1++; } else if (index1 >= len1 && index1 >= len2) { arr[k] = arr2[index2]; index2++; } } } int main (void) { int arr1[] = {1, 3, 4, 5, 8}; int arr2[] = {2, 7, 10, 18, 21}; int len1 = sizeof(arr1) / sizeof(int); int len2 = sizeof(arr2) / sizeof(int); int len = len1 + len2; int* arr = (int*)malloc(len * sizeof(int)); mergeArrSorted(arr, len, arr1, len1, arr2, len2); for (int i = 0; i < len; i++) { printf("%d ", arr[i]); } printf("\n"); }
給你兩個有序整數數組 nums1 和 nums2,請你將 nums2 合併到 nums1 中,使 nums1 成爲一個有序數組。
條件說明:測試
讀完題目能夠知道,題目要求將子數組nums2合併到nums1,由於nums1有足夠的空間可以容納兩個數組的全部元素。跟上面合併數組比起來,有空間的要求,就是咱們必須把num2合併到num1,此外區別不大。所以,上面講過的兩種方法均可以實現數組合並。
方法一:
不須要額外的空間保存數組,只需將num2保存到num1後面,而後對nums1進行快速排序便可。所以,時間複雜度不變,仍是O((m+n)lg(m+n)),即快排的時間複雜度。空間複雜度爲O(1),由於不須要開闢額外的空間。
方法二:
由於須要將nums1做爲輸出數組,所以,要先將nums1的元素存儲到一個新的數組,而後使用雙指針法,比較兩個數組中的元素大小,並依次將其保存到nums1。所以須要額外開闢m的空間來存儲nums1中的元素。所以空間複雜度O(m),時間複雜度不變,爲O(m+n)。
方法三:
能夠看到,第一種方法空間複雜度很優秀,可是時間複雜度遜色於第二種方法。咱們須要找出時間複雜度如方法二通常優秀,空間複雜度如方法一通常優秀的算法,那就是雙指針法--從後往前遍歷。
雙指針從後往前遍歷,將較大的值存儲到nums1尾部,如此循環,直到全部元素都有序存儲到nums1中。時間複雜度O(m+n),空間複雜度O(1)。ui
void merge(int *nums1, int nums1Size, int m, int *nums2, int nums2Size, int n) { int len = m + n; m = m - 1; n = n - 1; for (int i = len - 1; i >= 0; i--) { if (n >= 0 && m >= 0) { if (nums2[n] > nums1[m]) { nums1[i] = nums2[n]; n--; } else { nums1[i] = nums1[m]; m--; } } else if (n >= 0 && m < 0) { nums1[i] = nums2[n]; n--; } else if (n < 0 && m >= 0) { nums1[i] = nums1[m]; m--; } } }
下面是測試代碼,有興趣的能夠拿去編譯運行一下:spa
#include <stdio.h> #include <stdlib.h> void merge(int *nums1, int nums1Size, int m, int *nums2, int nums2Size, int n) { int len = m + n; m = m - 1; n = n - 1; for (int i = len - 1; i >= 0; i--) { if (n >= 0 && m >= 0) { if (nums2[n] > nums1[m]) { nums1[i] = nums2[n]; n--; } else { nums1[i] = nums1[m]; m--; } } else if (n >= 0 && m < 0) { nums1[i] = nums2[n]; n--; } else if (n < 0 && m >= 0) { nums1[i] = nums1[m]; m--; } } } int main (void) { int arr1[10] = {1, 2, 3, 8, 20}; int arr2[] = {6, 9, 10}; int len1 = sizeof(arr1) / sizeof(*arr1); int len2 = sizeof(arr2) / sizeof(*arr2); int m = 5; int n = len2; int len = m + n; merge(arr1, len1, m, arr2, len2, n); for (int i = 0; i < len; i++) { printf("%d ", arr1[i]); } printf("\n"); }