题目:
给定一个int数组,长度为n,数组中每个元素为随机整数,可能为负数,可能为0,可能为正数,要求将数组按照符号排序,所有的负数在左边,正数在右边,零在中间,负数和负数之间不需要有序,正数和正数之间也不需要有序。
数据约束:
0 < n <= 300000000
例子:
输入:
[0, 3, 5, -10, -1]
输出:
下面任意一个都是合法输出:
[-10, -1, 0, 3, 5]
[-1, -10, 0, 3, 5]
[-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资源可以跑慢一点,终究还是能够跑起来的,但是没有内存资源根本跑都跑不起来。
.