題目:html
給定一個int數組,長度爲n,數組中每一個元素爲隨機整數,可能爲負數,可能爲0,可能爲正數,要求將數組按照符號排序,全部的負數在左邊,正數在右邊,零在中間,負數和負數之間不須要有序,正數和正數之間也不須要有序。java
數據約束:數組
0 < n <= 300000000dom
例子:
輸入:
[0, 3, 5, -10, -1]函數
輸出:測試
下面任意一個都是合法輸出:指針
[-10, -1, 0, 3, 5]htm
[-1, -10, 0, 3, 5]blog
[-10, -1, 0, 5, 3]排序
[-1, -10, 0, 5, 3]
這道題中的難點是出現了三種類型的數值,不能簡單地採用兩個指針從兩端向中間收縮的遍歷方式,除非使用額外的空間(這個空間還不是臨時的,而是在函數執行完畢仍然可能被佔用的)。
即新申請一個長度爲n的int數組,由於int數組中元素默認值爲0,至關於不用考慮0的狀況,將三種狀況轉爲了兩種狀況,就比較容易處理了,只須要將原數組中的值按照負數放在新數組的左端,正數放在新數組的右端便可,這種方式時間複雜度爲O(n),只須要遍歷一遍數組便可,可是須要額外的至少4n字節的內存空間。
實現代碼以下:
package org.cc11001100.alg.sortBySymbol; /** * 須要額外空間的按符號排序,時間複雜度是O(n),可是須要額外的內存空間n * * @author CC11001100 */ public class SortBySymbolSolutionNeedMoreMemory implements SortBySymbol { // 拷貝到一個新的數組中 public int[] sortBySymbol(int[] nums) { int[] result = new int[nums.length]; int left = 0, right = result.length - 1; for (int i = 0; i < nums.length; i++) { if (nums[i] < 0) { result[left++] = nums[i]; } else if (nums[i] > 0) { result[right--] = nums[i]; } } return result; } public static void main(String[] args) { SortBySymbol sortBySymbol = new SortBySymbolSolutionNeedMoreMemory(); // int[] nums = RandomIntArrayGenerator.random(100000000); // long start = System.currentTimeMillis(); // nums = sortBySymbol.sortBySymbol(nums); // long cost = System.currentTimeMillis() - start; // System.out.println(cost); // System.out.println(SortBySymbolJudge.judge(nums)); // for (int i = 0; i < nums.length; i++) { // System.out.println(nums[i]); // } SortBySymbolJudge.judge(sortBySymbol); } }
上面的代碼只是實現了基本的要求,咱們確定可以作到更好對吧,對於用到了額外空間的解法,咱們應該想一下是否能夠不使用額外空間就解決這個問題呢?
上面說了,這個問題複雜就複雜在要在一個數組中操縱三種類型的數據,指針不夠用,那麼咱們能夠將這個問題轉化一下,好比能夠將問題分爲兩步:
1. 先按照負數和非負數(包括零和正數)進行排序,這一步將負數的順序排好了,可是零和正數仍是無序的。
2. 再按照零和正數排序,排完以後總體有序了。
這樣每一步都是兩種類型的數據,使用兩個指針徹底夠用了,並且不須要使用到額外的內存了,缺點就是須要多遍歷一遍數組,時間複雜度是O(2n),約掉常數複雜度仍然是O(n)。
代碼實現:
package org.cc11001100.alg.sortBySymbol; /** * 不須要額外空間,可是須要掃描兩次數組,時間複雜度是O(2n),可是不須要額外空間 * * @author CC11001100 */ public class SortBySymbolSolutionTwoTimesSort implements SortBySymbol { // 將整個排序過程看作是兩個步驟,先按負數和非負數排序,而後將非負數部分按照零和正數排序 public int[] sortBySymbol(int[] nums) { int left = negativeAndNotNegativeSort(nums); zeroAndPositiveSort(nums, left); return nums; } // 負數和非負數排序 private int negativeAndNotNegativeSort(int[] nums) { int left = 0, right = nums.length - 1; while (left <= right) { if (nums[left] >= 0) { right = findFirstNegativeIndexFromRight(nums, right); if (right < left) { break; } swap(nums, left, right); left++; right--; } else { left++; } } return left; } private int findFirstNegativeIndexFromRight(int[] nums, int right) { while (right >= 0) { if (nums[right] < 0) { return right; } right--; } return -1; } private void swap(int[] nums, int a, int b) { int t = nums[a]; nums[a] = nums[b]; nums[b] = t; } // 零和正數排序 private void zeroAndPositiveSort(int[] nums, int left) { int right = nums.length - 1; while (left <= right) { if (nums[left] != 0) { right = findFirstZeroIndexFromRight(nums, right); if (right < left) { break; } swap(nums, left, right); left++; right--; } else { left++; } } } private int findFirstZeroIndexFromRight(int[] nums, int right) { while (right >= 0) { if (nums[right] == 0) { return right; } else { right--; } } return -1; } public static void main(String[] args) { SortBySymbol sortBySymbol = new SortBySymbolSolutionTwoTimesSort(); // int[] nums = RandomIntArrayGenerator.random(100000000); // long start = System.currentTimeMillis(); // sortBySymbol.sortBySymbol(nums); // long cost = System.currentTimeMillis() - start; // System.out.println(cost); // System.out.println(SortBySymbolJudge.judge(nums)); // for (int i = 0; i < nums.length; i++) { // System.out.println(nums[i]); // } SortBySymbolJudge.judge(sortBySymbol); } }
寫一個類測試代碼是否正確,首先爲了測試方便將兩個類的類型統一:
package org.cc11001100.alg.sortBySymbol; public interface SortBySymbol { int[] sortBySymbol(int[] nums); }
而後是生成測試數據的類:
package org.cc11001100.alg.sortBySymbol; /** * 用於生成測試數據 * * @author CC11001100 */ public class RandomIntArrayGenerator { public static int[] random(int length) { int[] result = new int[length]; for (int i = 0; i < result.length; i++) { int n = (int) (Math.random() * 100); int t = n % 5; if (t < 2) { // n ∈ [0, 1] n *= -1; } else if (t == 2) { // n == 2 n = 0; } else { // n ∈ [3, 4] // n = n; } result[i] = n; } return result; } public static void main(String[] args) { int[] nums = random(20); for (int i = 0; i < nums.length; i++) { System.out.println(nums[i]); } } }
而後是測評類:
package org.cc11001100.alg.sortBySymbol; /** * 用於評測SortBySymbol輸出結果是否正確 * * @author CC11001100 */ public class SortBySymbolJudge { public static boolean judge(int[] nums) { int index = 0; if (nums[index] < 0) { while (index < nums.length) { if (nums[index] >= 0) { break; } index++; } } if (index < nums.length && nums[index] == 0) { while (index < nums.length) { if (nums[index] > 0) { break; } index++; } } while (index < nums.length) { if (nums[index] <= 0) { return false; } index++; } return index == nums.length; } public static void judge(SortBySymbol sortBySymbol) { int[] numLengths = new int[]{ 1, 2, 3, 5, 10, 1000, 10000, 100000, 1000000, 10000000, 100000000, 100000000, 100000000, 200000000, 200000000, 200000000, 300000000 }; for (int i = 0; i < numLengths.length; i++) { int[] nums = RandomIntArrayGenerator.random(numLengths[i]); long start = System.currentTimeMillis(); nums = sortBySymbol.sortBySymbol(nums); long cost = System.currentTimeMillis() - start; boolean judgeResult = judge(nums); System.out.println("num length=" + numLengths[i] + ", cost=" + cost + "ms, judge=" + judgeResult); } } }
測評結果:
能夠看到,對於使用額外內存的解法當數據量足夠大時它就撐不住了,不少問題就是這樣,沒有CPU資源能夠跑慢一點,終究仍是可以跑起來的,可是沒有內存資源根本跑都跑不起來。
.