顺序表的考察范围覆盖多指针, 排序, 散列表, 二分, 顺序表逆置算法
1.顺序表删除元素
题目:顺序表删除元素x
思路:
方法1:
双指针
遇到一个等于x就停下来,此时k的值即为要删除元素下标,直到元素去替代他的位置,非x与非x之间始终相差k个单位,然后k++更新, 顺序表的长度即为k的个数, 因为k是遇到一个非k的数++, 因为k的个数就是非x的个数。
代码:
#include<iostream>
#define ElemType int
using namespace std;
const int Maxn = 50;
struct SqList {
int length;
ElemType data[Maxn] = {1, 2, 2, 3, 4, 5, 5, 6};
};
//方法1
void DeleteElem(SqList &l, int x) {
int i = 0, k = 0;
while(i < l.length) {
if(l.data[i] != x) {
l.data[k] = l.data[i];
k++;
}
i++;
}
l.length = k;
}
int main () {
SqList l;
l.length = 8;
int x;
x = 2;
DeleteElem(l, x);
for(int i = 0; i < l.length; i++)
cout << l.data[i] << " ";
return 0;
}
方法2:
双指针
遇到一个x就记出来k++, 直到遇到不是x的元素,将后面不为k的元素整体往前推k个单位就想当于删除x了,k为x 的个数。
代码:
#include<iostream>
#define ElemType int
using namespace std;
const int Maxn = 50;
struct SqList {
int length;
ElemType data[Maxn] = {1, 2, 2, 3, 4, 5, 5, 6};
};
//方法2
void DeleteSameElem(SqList &l, int x) {
int i = 0, k = 0;
while(i < l.length) {
while(i < l.length) {
if(l.data[i] == x) {
k++;
} else {
l.data[i - k] = l.data[i];
}
i++;
}
l.length = l.length - k;
}
}
int main () {
SqList l;
l.length = 8;
int x;
x = 2;
DeleteSameElem(l, x);
for(int i = 0; i < l.length; i++)
cout << l.data[i] << " ";
return 0;
}
//我更喜欢方法2
如果是有序表删除满足范围[s, t]内的元素, 就直接找到第一个大于t的元素, 把t前面的元素整体删除即可
2.删除有序顺序表相同的元素
题目:删除有序顺序表的相同元素, 只保留一个
思路:因为是有序顺序表, 所以相同的元素肯定是连续的,关键就是找到两个数不同的时候的临界点, 用两个指针, 都从左边走,i指向第一个相同元素, j往右边跑,当j和i指向元素不同的时候, 就可以让a[j]后面的元素往前移动了, 移动到i指向的元素后面一个的位置,因为要将相同元素删只剩一个。
代码:
#include<iostream>
#define ElemType int
using namespace std;
const int Maxn = 50;
struct SqList {
int length;
ElemType data[Maxn] = {1, 2, 2, 2, 2, 3, 3, 3, 4, 4, 5};
};
void DeleteSame(SqList &l) {
int i = 0, j = 0;
for(i,j; i < l.length && j < l.length; j++) {
if(l.data[j] != l.data[i]) {//相同就不管他, 让j接着往前走直到两个不同的时候就可以换到i后面了, 绕过相同的元素;
l.data[++i] = l.data[j];
}
}
l.length = i + 1; //i都是指向要剩下的哪个唯一数, 因此i+1就是最终元素的个数;
}
int main () {
SqList l;
l.length = 11;
DeleteSame(l);
for(int i = 0; i < l.length; i++)
cout << l.data[i] << " ";
return 0;
}
//感觉顺序表删除都是用双指针比较多, 时间和空间复杂度最低, 其实和哪个删除满足某个范围内的数的那题很像, 只不过是将范围内的数换成了相同的数;
3.归并排序
题目:将两个有序顺序表合并为一个有序顺序
代码:
#include<iostream>
#define ElemType int
using namespace std;
const int Maxn = 50;
struct SqList {
int length;
ElemType data[Maxn];
};
//两个有序序列合并为一个有序序列;
void Merge(SqList &a, SqList &b, SqList &c) {
int i = 0 , j = 0, k = 0;
while(i < a.length && j < b.length) {
if(a.data[i] < b.data[j])//不能写成while,写成while就成死循环了,其中一个数组会一直往右边走,因为0肯定比另一个数组的正整数小;
c.data[k++] = a.data[i++];
else
c.data[k++] = b.data[j++];
}
while(i < a.length) c.data[k++] = a.data[i++];
while(j < b.length) c.data[k++] = b.data[j++];
c.length = k;
}
int main () {
SqList a, b, c;
int n = 3;
int m = 4;
a.length = 3;
b.length = 4;
/*
测试数据
1 3 5
2 4 5 6
*/
for(int i = 0; i < n; i++) cin >> a.data[i];
for(int i = 0; i < m; i++) cin >> b.data[i];
Merge(a, b, c);
for(int i = 0; i < c.length; i++)
cout << c.data[i] << " ";
return 0;
}
//归并排序是一个先分再合并的排序, 合并后的区间是有序的, 最后实现整个数组有序;
4.
二分
题目:
在有序的顺序表找x并和后面的数交换,如果x是最后一个元素不用交换, 不存在就插入顺序表,插入后仍是有序的
思路:有序顺序表找某个元素,第一个应该想到使用二分法找到该元素的下标,
代码:
#include<iostream>
#define ElemType int
using namespace std;
const int Maxn = 50;
struct SqList {
int length;
ElemType data[Maxn] = {1, 2, 4, 5, 6};
};
//两个有序序列合并为一个有序序列;
void FindElem(SqList &l, int x, int n) {
int left = 0, right = n - 1, mid, temp;
while(left <= right) {
mid = (left + right) / 2;
if(l.data[mid] == x) {
if(mid != n - 1) {
temp = l.data[mid];
l.data[mid] = l.data[mid + 1];
l.data[mid + 1] = temp;
}
break;
}
if(l.data[mid] < x) {
left = left + 1;
} else {
right = right - 1;
}
}
if(left > right) {
for(int i = n - 1; i >= left; i--) {//left的位置就是x应该插入的位置, 这个位置要腾出来给x, 因此是要包括Left的;
l.data[i + 1] = l.data[i];
}
l.data[left] = x;
l.length++;
}
}
int main () {
SqList c;
c.length = 5;
int x;
cin >> x;
FindElem(c, x, c.length);
for(int i = 0; i < c.length; i++)
cout << c.data[i] << " ";
return 0;
}
5.
顺序表逆置算法,时间复杂度为O(n), 空间复杂度为O(1)
题目:
有一个序列, 里面存放两个序列,分别是a和b,长度分别是n, m, 要求原来a在b前面改成b在a前面
思路:
1.将整个数组反转, a1a2变成a2a1
区间a2逆转变正序
区间a1变成正序
最后变成a2正序a1正序
#include<iostream>
#define ElemType int
using namespace std;
const int Asize = 50;
//将所有元素倒置,考元素逆置的时候也可以使用;
bool Reverse(ElemType A[], int left, int right, int Asize) {
if(left >= right || right >= Asize)
return false;
int mid = (left + right)/2;//求mid是为了求交换次数;
for(int i = 0; i <= mid - left; i++) {//mid - left才是真正的交换次数,这是要等于;
//mid - left = (right - left)/2;
//序列个数为偶数两两交换, 奇数最后一次自己跟自己交换;
ElemType temp = A[left + i];
A[left + i] = A[right - i];
A[right - i] = temp; //上面三行的left, right加减i是为了限制区间;
}
}
void ReverseList(ElemType A[], int m, int n) {
Reverse(A, 0, n + m - 1, Asize);//让整个数组内的元素反转;
Reverse(A, 0, n - 1, Asize);//让序列a2反转变成正序;
Reverse(A, n, n + m - 1, Asize);//让序列a1反转变成正序;
}
int main () {
int m = 5;
int n = 3;
int a1[m] = {1, 2, 3, 4, 5};
int a2[n] = {6, 7, 8};
int A[Asize] = {0};
int k = 0;
for(int i = 0; i < m; i++)
A[k++] = a1[i];
for(int i = 0; i < n; i++)
A[k++] = a2[i];
for(int i = 0; i < k; i++)
cout << A[i];
cout << "\n";
ReverseList(A, m , n);
for(int i = 0; i < k; i++)
cout << A[i];
return 0;
}
//该算法的时间复杂度为O(n), 空间复杂度为O(1);
6.
二分法求两个长度相同的有序顺序表合并后的有序顺序表的中位数, 时间复杂度为O(log n ), 空间复杂度为O(1)
题目: 求两个长度相同的有序顺序表的中位数(2011统考真题)
思路:
不断求两个序列的中位数,通过两个中位数的值来缩小序列的范围大小,最后两种情况
当当前两个序列的中位数相等,则为合并后的序列的中位数。
两个缩小的序列都只剩最后一个元素,小的哪个就是合并后序列的中位数。
代码:
#include<iostream>
using namespace std;
int MidSearch(int a[], int b[], int n) {
int s1 = 0, s2 =0, d1 = n - 1, d2 = n - 1;
int m1, m2;
while(s1 != d1 && s2 != d2) {//序列只剩一个元素的时候就结束,小的哪个就是中位数;
m1 = (s1 + d1) / 2;
m2 = (s2 + d2) / 2;
if(a[m1] == b[m2]) //两序列的中位数相等, 则该点上的中位数即为大序列的中位数;
return a[m1];
if(a[m1] < b[m2]) {//小的不要左侧,大的不要右侧
if((s1 + d1) % 2 == 0) {//如果为奇数的时候, m1, m2都有可能是中位数;
s1 = m1;
d2 = m2;
} else { //偶数的时候,小的mid不可能是中位数;
s1 = m1 + 1;
d2 = m2;
}
} else {
if((s1 + d1) % 2 == 0) {
d1 = m1;
s2 = m2;
} else {
d1 = m1;
s2 = m2 + 1;
}
}
}
return a[s1] < b[s2] ? a[s1] : b[s2];//不是返回mid,因为mid是上次遍历的mid
}
int main() {
int n = 5;
int a[n] = {11, 13, 15, 17, 19};
int b[n] = {2, 4, 6, 8, 20};//中位数为4
int mid = MidSearch(a, b, n);
cout << mid;
return 0;
}
//难点不是很理解序列的中位数和合并后的序列的中位数有啥关系??
//因为是升序序列, 可以使用归并排序, 成一个新序列, 然后返回其中位数,时间复杂是O(n), 空间复杂度为O(1), 因为整理出来得哪个数组是要返回得, 不属于辅助数组。
这不是一般人想得出来得, 两个有序序列合并为一个有序序列, 我们应该想到归并排序
代码:
#include<iostream>
using namespace std;
//返回第i个元素,使用得是向上取整,向上取整就是9/2 = 4/5 = 5
//对付向上取整可以用最后一个元素的下标/2得到就是第i个元素得下标;
int Merge(int a[], int b[], int c[], int n, int m) {
int i = 0, j = 0, k = 0;
while(i < n && j < m) {
if(a[i] < b[j])
c[k++] = a[i++];
else
c[k++] = b[j++];
}
while(i < n) c[k++] = a[i++];
while(j < m) c[k++] = b[j++];
return c[(k-1)/2];
}
int main() {
int n = 5, m = 5;
int a[n] = {11, 13, 15, 17, 19};
int b[m] = {2, 4, 6, 8, 20};
int c[m + n] = {0};
int num = Merge(a, b, c, m, n);
for(int i = 0; i < m + n; i++) {
if(i != 0)
cout << " ";
cout << c[i];
}
cout << "\n" << num;
return 0;
}
7.
多指针求多个数组的元素的组合满足某个特征值
题目:给出3个递增序列,3个数列分别取一个值记作a, b, c,求d = abs(a - b) + abs(b - c) + abs(c - a)最小的d
思路:
利用三个指针指向三个数组最左侧
//分析
核心思想:
数轴上若a < b < c,则d1 = 2Lac, 若b < a < c, 则d2 =2Lbc
若 c < b < a, 则d3 = 2Lca等等等
可以看出d的值即为最大值和最小值之间的距离, 当且仅当最小值向右移动的时候d才有可能变小
如若 a < b < c, a最为最小值, 我们称a, b, c为一个组合, 在数轴上表示出来为组合结构
1. 如果a在a,b之间移动,a还是最小值, 没有改变结构还是a < b < c, 此时d肯定比上一次小;
2. 如果a在b, c之间移动,改变了结构,变成了 b < a < c, 最小值变成了b, 然后b再往右边移动看看,看看d有没有变小,但不一定比上一个结构的d小;
3. 如果a在b, c之间移动,和2同理;
因此只需要不断改变最小值,不断更新组合结构并找到其Dis,才能找到最小的Dis;
疑问:为什么遍历的边界为while(i < n1 && j < n2 && k < n3)
答:
倘若组合为A, B, C且A<B<C, A, B, C的最大值分别为Amax, Bmax ,Cmax
A B C Amax Bmax Cmax
——————————————————————————————>数轴
由图可知A只要在Amax之间枚举都是有效的,B, C同理, 因此边界就是Amax, Bmax,Cmax
因为是递增序列,所以也就是各个数组的最后一个值。
代码:
#include<iostream>
#include<cmath>
using namespace std;
//最好时间复杂度为是O(min(n1, n2, n3)),最差的时间复杂度为O(n1 + n2 + n3),空间复杂度为O(1);
void MinDis(int s1[], int s2[], int s3[], int n1, int n2, int n3) {
int i = 0, j = 0, k = 0;//三个指针分别指向数组的最左侧;
int Min_Dis = 0x7fffffff;//ox7fffffff是int范围内的最大值;
int ans_s1 = s1[0], ans_s2 = s2[0], ans_s3 = s3[0];//距离最短的时候的组合;
while(i < n1 && j < n2 && k < n3) {
int Dis = abs(s1[i] - s2[j]) + abs(s2[j] - s3[k]) + abs(s3[k] - s1[i]);
//因为每次变更组合,那么在数轴上的结构就会改变,因此每次改变都要算一次距离;
if(Dis < Min_Dis) {
Min_Dis = Dis;
ans_s1 = s1[i];
ans_s2 = s2[j];
ans_s3 = s3[k];
}//这个组合结构比上一次的组合算出来的距离更短;
//找出三个数的最小值, 移动最小值, 更新组合结构;
//只有最小值向右侧移动的时候才有可能使得距离变小, 但是也可能会改变组合结构
/*假如a, b, c为三个数组选出的三个数, 且a < b < c(方便讨论而已,便于判断)
a最为最小值
1. 如果a在a,b之间移动,没有改变结构还是a < b < c, 此时Dis肯定比上一次小;
2. 如果a在b, c之间移动,改变了结构,变成了 b < a < c, 最小值变成了b, 然后b再往右边移动,看看d有没有变小,但不一定比上一个结构的Dis小;
3. 如果a在b, c之间移动,和2同理;
因此只需要不断改变最小值,不断更新组合结构并找到其Dis,才能找到最小的Dis;
*/
if(s1[i] < s2[j] && s1[i] < s3[k])//s1是最小的时候, 最小值向右移动才可能Dis变小;
i++;
else if(s2[j] < s1[i] && s2[j] < s3[k])//s2为最小的时候
j++;
else //s3为最小值的情况;
k++;
}
cout << Min_Dis << endl;
cout << ans_s1 << " " << ans_s2 << " " << ans_s3;
}
int main() {
int n1 = 3 , n2 = 4, n3 = 5;
int s1[n1] = {-1, 0, 9};
int s2[n2] = {-25, -10, 10, 11};
int s3[n3] = {2, 9, 17, 30 ,41};
MinDis(s1, s2, s3, n1, n2, n3);
return 0;
}
8.
散列表
题目1:
找出顺序表内的主元素, 主元素是出现次数最多且出现次数 > n/2的值
思路:
散列表时间换空间
代码:
#include<iostream>
#include<cstring>
#define ElemType int
using namespace std;
const int Maxn = 50;
struct SqList {
int length;
ElemType data[Maxn] = {1, 2, 4, 5, 6};
};
int FindMajor(int A[], int n) {
int Max = A[0], MaxCnt = -1, MaxNum;
for(int i = 1; i < n; i++) {
if(A[i] > Max) {
Max = A[i];
}
}
int *hashtable = new int[Max + 1];
memset(hashtable, 0, sizeof(hashtable));//记得给开辟的空间初始化,否则开辟的空间内的值初始可能不为0;
for(int i = 0; i < n; i++) {
hashtable[A[i]]++;
}
for(int i = 0; i < n; i++) {
if(hashtable[A[i]] > MaxCnt) {
MaxCnt = hashtable[A[i]];
MaxNum = A[i];
}
}
delete []hashtable;
return MaxCnt > n / 2 ? MaxNum : -1;
}
int main () {
int A[] = {0, 5, 5, 3, 5, 1, 5, 7};
int num = FindMajor(A, 8);
cout << num;
return 0;
}
题目2:
找出顺序表缺失的最小正整数(最小正整数不包括0), 如1, 3, 5, 7缺失的最小正整数即为2;
思路:
散列表除了可以统计出现的次数还可以用来判断某元素是否出现过, 通过这个性质, 遍历散列表,为值为0就说明没出现过, 说明缺失
代码:
#include<iostream>
#include<cstring>
#define ElemType int
using namespace std;
const int Maxn = 50;
struct SqList {
int length;
ElemType data[Maxn] = {1, 2, 4, 5, 6};
};
int FindElem(int a[], int n) {
int Max = a[0];
for(int i = 1; i < n; i++) {
if(Max < a[i])
Max = a[i];
}
int hashtable[Max + 1 + 1] = {0};//第一个+1是因为开的下标的范围为[0,Max-1]因此要多开一个单位,第二个+1是因为要判断最后一个元素的后面一个为位置是否为最小正整数
for(int i = 0; i < n; i++)
hashtable[a[i]]++;
for(int i = 1; i <= Max + 1; i++) {
if(hashtable[i] == 0)
return i;
}
}
int main () {
int n = 5;
int a[n] = {1, 2, 3, 4, 5};
int num = FindElem(a, n);
cout << num;
return 0;
}