1. 荷兰国旗问题。只包含0,1,2的整数数组进行排序,要求使用交换、原地排序,不是利用计数进行排序。
原地排序就是指不申请多余的空间来进行的排序,就是在原来的排序数据中比较和交换的排序。例如堆排序等都是原地排序,合并排序(根据TAOCP,合并排序也有原地排序的版本),计数排序等不是原地排序。属于原地排序的是:希尔排序、冒泡排序、插入排序、选择排序、堆排序、快速排序。
本题主要过程与快速排序划分过程类似;可以做到时间复杂度为o(n),空间复杂度为o(1)。
最左边开辟一个长度为0的“0“区域,最右边开辟一个长度为0 的“2“区域,然后从左到右遍历数组,直到当前遍历的值已经位于“2”数组中,则算法停止!!
class ThreeColor {
public:
vector<int> sortThreeColor(vector<int> A, int n) {
// write code here
//其中最左边的left指向的是第一个不为0的数据;这个数据只可能为0或者1
//最右边的right指向的是第一个不为2的数据,这个数据可能为0 1 2都有可能;
int left=0;
int right=n-1;
int cur=0;
int temp;
while(cur<=right){
if(A[cur]==1)
cur++;
else if(A[cur]==0){
temp=A[cur];
A[cur]=A[left];
A[left]=temp;
cur++;//这里的cur需要加加,因为这里调换过来的数一定为1,所以直接cur++就可以了!!!因为left指向的是第一个不为0的数,而这个数一定是1;
left++;
}else if(A[cur]==2){
temp=A[cur];
A[cur]=A[right];
A[right]=temp;
right--;//这里的cur不能加加,因为并不确定调换过来的数到底是0 1 还是2
}
}
return A;
}
};
对于上面的程序来说,一定更要注意cur right left的加与减,
2, 给定一个二维数组,在二维数组中,每一行每一列都是排好序的,再给定一个数,判断在二维数组中是否包含这个数。
如果二维数组为m*n,那么这道题的最优解的时间复杂度可以做到o(m+n)。额外空间复杂度为o(1).
这种题不管是从大到小排还是从小到大排,都应该从右上角或者左下角开始着手,因为这两个角上的数据,它的两个相邻的方向的顺序是不同的。比如对于下面的例子来说:
0 1 2 5
2 3 4 7
4 4 4 8
5 7 7 9
对于上面的数组来说,每一行每一列都是从小到大排序的。对于右上角的5来说,它的左边逗比它小,它的下面都比它大,是两个相反的方向,所以可以从它开始着手;
对于左下角的5来说道理也是一样的,它的上面和它的右边的大小也正好的相反的;但是对于左上角和右下角就不是这样了,所以这两个角不可以作为着手点!!!
9 7 7 5
8 4 4 4
7 4 3 2
5 2 1 0
对于上面的数组来说,每一行每一列都是从大到小排序的。同理,对于右上角的5来说,它的左边都比它大,它的下面都比它小,是两个相反的方向,所以可以从它开始着手;
对于左下角的5来说道理也是一样的,它的上面和它的右边的大小也正好的相反的;但是对于左上角和右下角就不是这样了,所以这两个角不可以作为着手点!!!
class Finder {
public:
bool findX(vector<vector<int> > mat, int n, int m, int x) {
// write code here
int j=m-1;//代表列
int i=0;//代表行
while(j>=0&&i<=n-1){
if(mat[i][j]>x){
i=i;
j=j-1;
}
else if(mat[i][j]<x){
i=i+1;
j=j;
}
else if(mat[i][j]==x){
return true;
}
}
return false;
}
};
具体的程序如上面所示!
3. 给定给一个数组,返回数组中需要排序的最短子数组的长度。
[1 5 4 3 2 6 7]返回的是4,因为只有[5 4 3 2]需要排序。
这道题的最优解可以做到时间复杂度为o(n),额外空间复杂度为o(1).
从左向右遍历数组,用一个变量记录遍历过的最大值,当遍历过的最大值大于当前数的时候,我们知道这个最大值在排序之后至少在当前数的位置或者更右的位置,最后只记录发生这种情况的最右的位置;下再从右向左遍历数组,用一个变量记录遍历过的最小值,当遍历过的最小值小于当前数的时候,我们知道这个最小值在排序之后至少在当前数的位置或者更左的位置,最后只记录发生之中情况的最左的位置!!!
最后最左的位置和右的位置中间的范围(包括最左和最右)就是要排序的最短数组。
class Subsequence {
public:
int shortestSubsequence(vector<int> A, int n) {
// write code here
int max,min,right,left;
max=A[0];
min=A[n-1];
right=0;
left=0;
for(int i=0;i<n;i++){
if(A[i]>max)
max=A[i];
else if(A[i]<max){
right=i;
}
}
for(int j=n-1;j>=0;j--){
if(A[j]<min)
min=A[j];
else if(A[j]>min){
left=j;
}
}
if(right==0&&left==0){
return 0;
}
else{
return (right-left+1);
}
}
};
具体的实现如上所示!
4. 给定一个无序的整形数组arr,返回如果排序之后,相邻两数的最大差值。
最优解的时间复杂度为o(n),额外空间复杂度为(o(n))。
思想来自于桶排序,并且和数值具体的范围无关。
(主要是想在不进行排序的情况下,找出这个最大差值。)
首先找到数组中的最小值和最大值,等量的将最小值和最大值之间分成n个区间,这个n就是数组中数据的数量。每个区间分别对应一个桶,将最大值放到第n+1号桶中,因为桶的数量为n+1个,数据一共n个,所以中间一定有空桶。我们知道同一个桶中数据的差值不会大于桶区间,而来自空桶两侧的桶中的数据的差值一定大于桶区间。需要注意的是每个区间的取值范围是[ )这种形式的,因此最大值才需要单独放到一个桶。
不用考虑同一个桶的相邻数,只用考虑桶间的相邻数。并且只需要记录用后一个桶的最小值减去前一个桶的最大值中得到的最大值就是最后相邻两数的最大差值。
class Gap
{
public:
int
maxGap (vector<int> A, int n)
{
// write code here
int max = A[0];
int min = A[0];
for (int i = 0; i < n; i++)
{
if (A[i] < min)
min = A[i];
if (A[i] > max)
max = A[i];
}
//一共有n+1个桶,最后一个桶放最大值
int interval = max - min; //必须用double类型,如果用整形,区间的计算长度经常都是分数,就会失真!!!
//可以申请两个数组,长度都是n,一个存放每个桶中的最大值,一个存放每个桶中的最小值
//int interval=(max-min)/n;一开始本来想直接计算出区间的,但是区间通常都是分数,如果变成int类型就会失真,导致后面的结果发生错误!!!
vector<int> maxBucket (n + 1, INT_MIN);
vector<int> minBucket (n + 1, INT_MAX);
int index;
for (int i = 0; i < n; i++)
{
index = (A[i] - min) * n / interval; //这里为了让结果不失真,将本来是(A[i] - min)/((max-min)/n)进行了整顿直接变成这个公式,防止了Bucket数组的越界以及结果失真
if (A[i] > maxBucket[index])
maxBucket[index] = A[i];
if (A[i] < minBucket[index])
minBucket[index] = A[i];
}
int result = 0;
int pre = maxBucket[0]; //需要注意的是0桶里一定至少有一个值,就是整个数组中的最小值
for (int j = 1; j < n+1; j++)//这里一定是n+1!!!!!!因为是n+1个桶啊啊啊啊啊!!!!!!
{
if (minBucket[j] != INT_MAX)
{ //这里的判断就是为了越过空桶!!!
if (minBucket[j] - pre > result)
result = minBucket[j] - pre; //注意这里最好不要使用max min这样的<algrithm>头文件中的函数,因为不支持!
pre = maxBucket[j];
}
}
return result;
}
};
对于上面的程序来说,一定要时刻谨记,maxBucket以及minBucket的大小都是n+1!!!!这个很重要,如果弄错容易造成数组越界以及结果的错误!!!!并且在计算index 的时候一定要记住要保证区间为分数时不能约为小数,这样会造成数组越界等错误!!!!!