考研算法重的是思路,当你遇到不会的用暴力破解没问题,只要能表达清晰,可能一些细节没处理好,还有一起奇葩的案例实现不了,都没事,老师不会拿你代码去实现,也没有哪个时间。
注释很重要!!!
图片来源于网络,如有侵权必删除
如果对大家有用的话,麻烦大家点个赞和关注,这对我很重要!!!
数组
-
数组是存放在连续内存空间上的相同类型数据的集合。
-
数组下标都是从0开始的。
-
数组内存空间的地址是连续的
-
数组的元素是不能删的,只能覆盖。
数组最常用的算法有双指针,前缀和,滑动窗格等,我认为最重要的就是双指针了,我认为在数组题上挺好用的,而且后续的链表,还有一些排序查找也有空能用到。接下来我就进行介绍
双指针
这时你可以使用双指针技巧:一个指针从头部开始,而另一个指针从尾部开始。
前后指针
逆置数组
快慢指针
同时有一个慢指针和一个快指针。
解决这类问题的关键是:确定两个指针的移动策略。
删除数组中值为2的数
小试牛刀——王道算法打卡表
01、顺序表删除最小值
从顺序表中删除具有最小值的元素(假设唯一)并由函数返回被删元素的值。空出的位置由最后一个元素填补
int minVal(int* num, int n) {
if(n<=0) return -9999;
int index = 0;
for (int i = 0; i < n; i++) {
if (num[i] < num[index]) index = i;
}
int min = num[index];
num[index] = num[n - 1];
return min;
}
int main() {
int num[] = { 9,2,4,5,7,8,6,1,3 };
printf("%d", minVal(num,9));
}
02、顺序表的逆置
设计一个高效算法,将顺序表 L 的所有元素逆置,要求算法的空间复杂度为 0(1)
void inverse(int* nums,int n) {//利用前后指针算法
int front = 0, last = n - 1;
int temp = 0;//中间变量用于交换变量的时候暂存元素
while (front<last)
{
temp = nums[front];
nums[front++] = nums[last];
nums[last--] = temp;
}
}
int main() {
int nums[] = { 1,2,3,4,5,6,7,8,9 };
inverse(nums, 9);
for (int i = 0; i < 9; i++) {
printf("%d\t",nums[i]);
}
}
03、删除顺序表中所有值为x的数据
对长度为 n 的顺序表编写一个时间复杂度为 O(n)、空间复杂度为 O(1) 的算法,该算法删除线性表中所有值为 x 的数据元素
int delect(int* nums, int n, int x) {
int slow = 0, fast = 0;
while (fast<n) {
if (nums[fast] != x) {//将不为x的值,放到数组的前段,然后输出这段的长度
nums[slow++] = nums[fast];
}
fast++;
}
return slow ;
}
int main() {
int nums[] = { 1,2,2,3,2,4,5,6,7,8,9,2 };
int n=delect(nums, 12, 2);
for (int i = 0; i < n; i++) {
printf("%d\t", nums[i]);
}
}
04、删除下标i~j(i<=j)的所有元素
从给定顺序表 L 中删除下标 i~j(i<=j)的所有元素,假定 i,j 都是合法的
int delect(int* nums,int n, int i, int j) {
int slow = i, fast = j+1;//直接将要删除的元素最后一位的后面序列往前覆盖
while (fast<n) {
nums[slow++] = nums[fast++];
}
return slow;
}
int main() {
int nums[] = { 1,2,3,4,5,6,7,8,9,2 };
int n=delect(nums,10, 3, 6);
for (int i = 0; i < n; i++) {
printf("%d\t", nums[i]);
}
}
05、顺序表前后迁移
有个存放整数类型的顺序表 L,设计算法将 L 中所有小于等于表头元素的整数放在前半部分,大于表头元素的整数放在后半部分
void sort(int* nums,int n) {
int x = nums[0];
int l = 1, f = n - 1;
while (l <= f) {
if (nums[l]>x && nums[f]<=x) {
int temp = nums[l];
nums[l] = nums[f];
nums[f] = temp;
}
if (nums[l] <= x) {
l++;
}
if (nums[f] > x) {
f--;
}
}
nums[0] = nums[f];
nums[f] = x;
}
int main() {
int nums[] = { 7,1,3,4,5,6,7,8,9,2 };
sort(nums,10);
for (int i = 0; i < 10; i++) {
printf("%d\t", nums[i]);
}
}
06、从有序顺序表中删除其值在给定值s与t之间的所有元素
从有序顺序表中删除其值在给定值 s 与 t 之间(要求 s<t)的所有元素,若 s 或 t 不合理或顺序表为空,则显示出错信息并退出运行
int delect(int* nums, int n, int s, int t) {
if (s > t || n <= 0) return -1;
int slow = 0, fast = 0;
while (fast<n){
if (nums[fast] >= s && nums[fast] <= t) fast++;
else nums[slow++] = nums[fast++];
}
return slow;
}
int main() {
int nums[] = { 1,2,3,4,5,6,7,8,9,2 };
int n=delect(nums, 10, 3, 6);
for (int i = 0; i < n; i++) {
printf("%d\t", nums[i]);
}
}
07、有序顺序表,删除重复的元素
从有序(升序)顺序表中删除所有其值重复的元素,使表中所有元素的值均不同
int delect(int* nums,int n) {//利用快慢指针
int slow = 0, fast = 1;
while (fast != n) {
if (nums[slow] == nums[fast])fast++;//当遇到重复元素时,fast指针后移动
else nums[++slow] = nums[fast++];//遇到不同元素时,将fast赋值给slow的后一位
}
return slow+1;
}
int main() {
int nums[] = { 1,2,2,3,4,5,6,6,7,8,9 };
int n=delect(nums,11);
for (int i = 0; i < n; i++) {
printf("%d\t",nums[i]);
}
}
链表
- 链表是一种链式数据结构,由若干节点组成,每个节点包含指向下一节 点的指针。
- 链表的物理存储方式是随机存储,访问方式是顺序访问。
- 查找链表节点的时间复杂度是O(n),中间插入、删除节点的时间复杂度是 O(1)。
- 链表是一种在物理上非连续、非顺序的数据结构
typedef struct MyLinkedList{
int data;
struct MyLinkedList* next;
} MyLinkedList;
链表的介绍
插入
首先我们考虑在某个位置插入一个车厢:
我们首先把前一个车厢的挂钩指向我们新增的车厢,时间复杂度是常数。
后把新增车厢的挂钩指向后一个车厢,就完成了我们的插入操作。同样,这一步的时间复杂度也是常数。
所以对于链表,我们可以在 O(1) 的时间内完成插入操作。
删除
接下来我们来考虑删除操作,显然我们可以通过直接将要删除车厢的前一个车厢的挂钩,直接指向其后一个车厢来实现删除操作。当然我们还需要注意释放这个要删除的车厢所占的内存。因此删除操作的时间复杂度也是常数级别。
可以 在常数时间内 完成插入和删除是链表的最大优势,如果我们使用的是普通数组的话,是无法达到这样的速度的。
双向链表
刚才我们说一个车厢只能到达其后面的车厢,现在拓宽这个限制。现在的火车一般是存在两个火车头的,然后每一节车厢我们可以到达其前面和后面的车厢,这么修改以后,我们的链表就变成了 双向链表。
循环链表
循环链表,顾名思义,就是链表首尾相连。
循环链表可以用来解决约瑟夫环问题。
链表的实现(重要)
单链表的实现
单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。
typedef struct MyLinkedList{
int data;
struct MyLinkedList* next;
} MyLinkedList;
//初始化链表
MyLinkedList* myLinkedListCreate() {
MyLinkedList* list=(MyLinkedList*)malloc(sizeof(MyLinkedList));
list->data=0;
list->next=NULL;
return list;
}
//获取指定位置的元素
int myLinkedListGet(MyLinkedList* obj, int index) {
MyLinkedList* list=obj;//定义指针,避免污染头指针
if(index<0) return -1;
while(index-->=0){//指针移动到index位置
if(list->next==NULL) return -1;
list=list->next;
}
return list->data;
}
//头插法
void myLinkedListAddAtHead(MyLinkedList* obj, int val) {
MyLinkedList* list=(MyLinkedList*)malloc(sizeof(MyLinkedList));//定义新结点
list->data=val;
list->next=obj->next;//有头指针的插入
obj->next=list;
}
//尾插法
void myLinkedListAddAtTail(MyLinkedList* obj, int val) {
MyLinkedList* list=(MyLinkedList*)malloc(sizeof(MyLinkedList));
list->data=val;
list->next=NULL;
MyLinkedList* pre=obj;
while(pre->next!=NULL) pre=pre->next;//移动到链表尾
pre->next=list;
}
//指定位置插入
void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
MyLinkedList* list=(MyLinkedList*)malloc(sizeof(MyLinkedList));
list->data=val;
list->next=NULL;
MyLinkedList* pre=obj;
while(index-->0){
if(pre->next==NULL) return;//当移动到链表尾部的时候直接退出
pre=pre->next;
}
list->next=pre->next;
pre->next=list;
}
//指定位置删除
void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
MyLinkedList* list=obj;
if(index<0) return ;
while(index-->0){ //移动到指定位置的前一个元素
if(list->next==NULL) return;//当移动到链表尾部的时候直接退出
list=list->next;
}
if(list->next==NULL) return;
list->next=list->next->next;
}
void myLinkedListFree(MyLinkedList* obj) {
free(obj);
}
双链表的实现
typedef struct MyLinkedList {
int data;
struct MyLinkedList* pre;
struct MyLinkedList* next;
} MyLinkedList;
MyLinkedList* myLinkedListCreate() {
MyLinkedList* list=(MyLinkedList*)malloc(sizeof(MyLinkedList));
list->data=0;
list->pre=NULL;
list->next=NULL;
return list;
}
int myLinkedListGet(MyLinkedList* obj, int index) {
MyLinkedList* list=obj;//定义指针,避免污染头指针
if(index<0) return -1;
while(index-->=0){//指针移动到index位置
if(list->next==NULL) return -1;
list=list->next;
}
return list->data;
}
void myLinkedListAddAtHead(MyLinkedList* obj, int val) {
MyLinkedList* list=(MyLinkedList*)malloc(sizeof(MyLinkedList));//定义新结点
list->data=val;
list->pre=obj;
list->next=obj->next;//有头指针的插入
if(obj->next!=NULL)obj->next->pre=list;
obj->next=list;
}
void myLinkedListAddAtTail(MyLinkedList* obj, int val) {
MyLinkedList* list=(MyLinkedList*)malloc(sizeof(MyLinkedList));
list->data=val;
list->next=NULL;
MyLinkedList* node=obj;
while(node->next!=NULL) node=node->next;//移动到链表尾
node->next=list;
list->pre=node;
}
void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
MyLinkedList* list=(MyLinkedList*)malloc(sizeof(MyLinkedList));
list->data=val;
list->next=NULL;
MyLinkedList* node=obj;
while(index-->0){
if(node->next==NULL) return;//当移动到链表尾部的时候直接退出
node=node->next;
}
list->next=node->next;
list->pre=node;
if(node->next!=NULL)node->next->pre=list;
node->next=list;
}
void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
MyLinkedList* list=obj;
if(index<0) return ;
while(index-->0){ //移动到指定位置的前一个元素
if(list->next==NULL) return;//当移动到链表尾部的时候直接退出
list=list->next;
}
if(list->next==NULL) return;
if(list->next->next!=NULL) list->next->next->pre=list;
list->next=list->next->next;
}
void myLinkedListFree(MyLinkedList* obj) {
free(obj);
}
循环链表
typedef struct MyLinkedList{
int data;
struct MyLinkedList* next;
} MyLinkedList;
//初始化链表
MyLinkedList* myLinkedListCreate() {
MyLinkedList* list=(MyLinkedList*)malloc(sizeof(MyLinkedList));
list->data=0;
list->next=list;
return list;
}
//获取指定位置的元素
int myLinkedListGet(MyLinkedList* obj, int index) {
MyLinkedList* list=obj;//定义指针,避免污染头指针
if(index<0) return -1;
while(index-->=0){//指针移动到index位置
if(list->next==obj) return -1;
list=list->next;
}
return list->data;
}
//头插法
void myLinkedListAddAtHead(MyLinkedList* obj, int val) {
MyLinkedList* list=(MyLinkedList*)malloc(sizeof(MyLinkedList));//定义新结点
list->data=val;
list->next=obj->next;//有头指针的插入
obj->next=list;
}
//尾插法
void myLinkedListAddAtTail(MyLinkedList* obj, int val) {
MyLinkedList* list=(MyLinkedList*)malloc(sizeof(MyLinkedList));
list->data=val;
list->next=NULL;
MyLinkedList* pre=obj;
while(pre->next!=obj) pre=pre->next;//移动到链表尾
pre->next=list;
list->next=obj;
}
//指定位置插入
void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
MyLinkedList* list=(MyLinkedList*)malloc(sizeof(MyLinkedList));
list->data=val;
list->next=NULL;
MyLinkedList* pre=obj;
while(index-->0){
if(pre->next==obj) return;//当移动到链表尾部的时候直接退出
pre=pre->next;
}
list->next=pre->next;
pre->next=list;
}
//指定位置删除
void myLinkedListDeleteAtIndex(MyLinkedList* obj, int index) {
MyLinkedList* list=obj;
if(index<0) return ;
while(index-->0){ //移动到指定位置的前一个元素
if(list->next==obj) return;//当移动到链表尾部的时候直接退出
list=list->next;
}
if(list->next==obj) return;
list->next=list->next->next;
}
void myLinkedListFree(MyLinkedList* obj) {
free(obj);
}
链表常考习题
删除链表的倒数第 N 个结点
19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
int index=0;//记录要删除元素正序的位置
struct ListNode* fast=head;//快指针,用于指向要删除的结点
struct ListNode* pre=(struct ListNode*)malloc(sizeof(struct ListNode));//定义一个头结点,避免原链表只有一个元素,删除后无法返回的情况
pre->next=head;
struct ListNode* slow=pre;//慢指针,指向要删除结点前的一个元素
while(fast!=NULL){ //让快指针遍历到链表尾,记录链表长度
fast=fast->next;
index++;
}
index-=n;//保存正数的位置
fast=head;
while(index-->0){//让快慢指针移动要删除结点的位置,快——删除结点,慢——删除结点前一个结点
fast=fast->next;
slow=slow->next;
}
slow->next=fast->next;
return pre->next;
}
2009年408真题——链表
假设该链表只给出了头指针list。在不改变链表的前提下,请设计一个尽可能高效是算法,查找链表中倒数第k个位置上的结点(k为正整数)。若查找成功,算法输出该结点的data域的值,并返回1,否则,只返回0。
int kthToLast(struct ListNode* list, int k){
struct ListNode* fast=list;
struct ListNode* slow=list;
while(k-->0) fast=fast->next;
while(fast!=NULL){
fast=fast->next;
slow=slow->next;
}
if(slow!=list){
printf("%d",slow->val);
return 1;
}
else return 0;
}
2010年408真题——顺序表
设将n(n>1)个整数存放到一维数组R中。试设计一个在时间和空间两方面都尽可能高效的算法,将R中保存的序列循环左移p(0<p<n)个位置,即将R中的数据由(X0,X1,…Xn-1)变换为( Xp,XP+1,…,Xn-1,X0,X1,…,Xp-1)。
暴力破解:利用辅助空间
void move(int* nums, int n,int p) {
if (p<0|| p>n) return;
int* res = (int*)malloc(sizeof(int) *p);//辅助空间存储,用于存储0~p-1元素
for (int i = 0; i < p;i++) {
res[i] = nums[i];
}
for (int f = p,i=0; f < n; f++,i++) {//将p~n个元素前移
nums[i] = nums[f];
}
for (int i = n - p, j = 0; j < p; i++, j++) {//将辅助空间存储的元素存储到,p移动完后的位置
nums[i] = res[j];
}
}
int main() {
int nums[] = { 1,2,3,4,5,6,7,8,9,0 };
move(nums, 10, 2);
for (int i = 0; i < 10; i++) {
printf("%d\t", nums[i]);
}
}
}
优化:利用三次反转
假设我们有数组R = [1, 2, 3, 4, 5, 6, 7] 并且 p = 4,我们希望将数组循环左移4个位置。
原始数组:R = [1, 2, 3, 4, 5, 6, 7]
第一步反转整个数组:R = [7, 6, 5, 4, 3, 2, 1]
第二步反转前p个元素:R = [5, 6, 7, 4, 3, 2, 1]
第三步反转剩余的元素:R = [5, 6, 7, 1, 2, 3, 4]
// 反转数组中指定区间的元素
void reverse(int arr[], int start, int end) {
while (start < end) {
int temp = arr[start];
arr[start++] = arr[end];
arr[end--] = temp;
}
}
// 将数组循环左移p个位置
void rotateLeft(int arr[], int n, int p) {
// 如果p大于等于n,则取模以避免不必要的移动
p = p % n;
if (p == 0) return; // 如果p为0,则无需移动
// 第一步:反转整个数组
reverse(arr, 0, n - 1);
// 第二步:反转前p个元素
reverse(arr, 0, p - 1);
// 第三步:反转剩余的元素
reverse(arr, p, n - 1);
}
2011年408真题——顺序表
求两升序序列的中位数:一个长度为L(L>1)的升序序列S,处在第[L/2]个位置的数称为S的中位数。
例如,若序列S=(11,13,15,17,19),则S, 的中位数是 15。两个序列的中位数是含它们所有元素的升序序列的中位数。例如,若S=(2.4.6.8.20),则S和S2 的中位数是 11。
现有两个等长升序序列 A和 B,试设计一个在时间和空间两方面都尽可能高效的算法,找出两个序列A和B的中位数。
暴力破解:利用辅助空间
int medianSearch(int* S1, int* S2, int Size) {
int l=0,f=0;//l表示S1数值指针,f表示S2数组指针
int i=0;
int* nums=(int*)malloc(sizeof(int)*(Size*2));//定义一个新的数组
while(l<Size&&f<Size){//排序插入新数组
if(S1[l]<=S2[f]) nums[i]=S1[l++];
else nums[i]=S2[f++];
i++;
}
while(l!=Size)nums[i++]=S1[l++];//s1没遍历完,就将其全部放入新数组中
while(f!=Size)nums[i++]=S2[f++];
return nums[(i-1)/2];//输出中位数
}
2013年408真题——顺序表
已知一个整数序列 A=(a0,a1,…,an-1),其中 0≤ai<n(0<i<n)。若存在 ap1= ap2=…=apm=x且m>n/2(0<pk<n,l≤k<m),则称x为A的主元素。例如A=(0,5,5.3,5,7.5.5),则5为主元素;又如A=(0,5,5,3.5.1.5.7),则A中没有主元素。
假设A中的n个元素保存在一个一维数组中,请设计一个尽可能高效的算法,找出A的主元素。若存在主元素,则输出该元素;否则输出-1。
主元素的定义是它在数组中出现的次数超过数组长度的一半!!!
int total(int* nums,int n) {
int count = 0, totalNum = 0;//count统计次数,totalNum用于存储主元素
for (int i = 0; i < n; i++) {//找出主元素
if (count == 0) {//当count=0,开始记录主元
totalNum = nums[i];
count++;
}
else if (totalNum == nums[i]) count++;//遇到主元次数加+1
else count--;//不是主元,count--,主元的个数>n/2,所以遍历完整个数组个数是>1
}
count = 0;
for (int i = 0; i < n; i++)//验证是否是主元
if (totalNum == nums[i])count++;
return count > n / 2 ? totalNum : -1;
}
int main() {
int nums[] = {0,5,5,3,5,5,5,7};
printf("%d\n",total(nums, 8));
}
2018年408真题——顺序表
给定一个含 n(n>1)个整数的数组,请设计一个在时间上尽可能高效的算法,找出数组中未出现的最小正整数。例如,数组{-5,3,2,3}中未出现的最小正整数是 1;数组{1,2,3}中未出现的最小正整数是 4。
暴力: 快排+hash表+快慢指针
注:qsort()要背下来!!!
int cmp(const void* a, const void* b) {
return *(int*)a - *(int*)b;//升序
// return *(int *)b - *(int *)a;//降序
}
int firstNum(int* nums, int n) {
//对数组进行快排,让数组变得有序
qsort(nums, n, sizeof(nums[0]), cmp);//qsort(数组首地址,数组长度,数组数据元素的大小,比较函数)
int k = 0;//用于记录数组第一个元素的下标
for (int i = 0; i < n; i++) {
if (nums[i] < 0)k++;
}
if (k == n) return 1;//如果全是负数返回1
int length = n-k;//记录非负正数个数
int* hash = (int*)malloc(sizeof(int) * (length));//定义索引数组(1~k),用于判断缺少哪个元素
for (int i = 0; i < length; i++) hash[i] = i + 1;
int l = 0, f = k;//定义快慢指针
while (f < length) {//当代表nums的快指针移动到数组尾部时,表示缺少hash的该元素
if (nums[f] == nums[l])l++;//当数组中有索引表的值的时候,索引表指针后移动
f++;
}
return l==length?length:hash[l];
}
int main() {
int nums[] = {1,3,2,3};
printf("%d\n",firstNum(nums, 4));
}
优化: hashMap
int firstNum(int* nums, int n) {
//伪HashMap容器,利用下标当作hash索引表,元素当作value值
int* hashMap = (int*)malloc(sizeof(int) * (n+1));//定义索引数组(1~k),用于判断缺少哪个元素
int i = 0;
for ( i = 1; i <=n; i++) hashMap[i] = 0;//初始化hashMap
for ( i = 0; i < n; i++) {//统计元素nums出现的元素
if (nums[i] > 0) {
hashMap[nums[i]]=1;
}
}
for ( i = 1; i <= n; i++) {//遇到没出现的元素就退出了
if (hashMap[i] == 0) break;
}
return i;
}
int main() {
int nums[] = {-5,3,2,3};
printf("%d\n",firstNum(nums, 4));
/*for (int i = 0; i < n; i++) {
printf("%d\t", nums[i]);
}*/
}
2020年408真题——顺序表
定义三元组(a,b,c)(a,b,c均为整数)的距离D=|a-b|+|b-c|+|c-a|。给定3个非空整数集合S1、S2和S3,按升序分别存储在3个数组中。
请设计一个尽可能高效的算法,计算并输出所有可能的三元组(a,b,c)(a⋿S1,b⋿S2,c⋿S3)中的最小距离。例如 S1={-1,0,9},S2={25,-10,10,11},S3={2,9,17,30,41},则最小距离为 2,相应的三元组为(9,10,9)。
暴力破解:穷举法
int Triple(int* S1,int n1,int* S2,int n2,int* S3,int n3 ) {
int min = 9999;
for (int a = 0; a < n1; a++) {//将每一个元素都进行运算
for (int b = 0; b < n2; b++) {
for (int c= 0; c < n3; c++) {
int val =abs(S1[a]-S2[b])+ abs(S2[b] - S3[c])+ abs(S3[c]-S1[a]);
min=min > val ? val : min;
}
}
}
return min;
}
int main() {
int S1[3] = {-1,0,9};
int S2[4] = { 25,-10,10,11 };
int S3[5] = { 2,9,17,30,41 };
printf("%d\n", Triple(S1, 3,S2,4,S3,5));
}