考研算法408,自命题可用(持续更新中...)

考研算法重的是思路,当你遇到不会的用暴力破解没问题,只要能表达清晰,可能一些细节没处理好,还有一起奇葩的案例实现不了,都没事,老师不会拿你代码去实现,也没有哪个时间。

注释很重要!!!

数组

  1. 数组是存放在连续内存空间上的相同类型数据的集合。

  2. 数组下标都是从0开始的。

  3. 数组内存空间的地址是连续的

  4. 数组的元素是不能删的,只能覆盖。

数组最常用的算法有双指针,前缀和,滑动窗格等,我认为最重要的就是双指针了,我认为在数组题上挺好用的,而且后续的链表,还有一些排序查找也有空能用到。接下来我就进行介绍

双指针

从两端向中间迭代数组。

这时你可以使用双指针技巧:一个指针从头部开始,而另一个指针从尾部开始。

前后指针

在这里插入图片描述

逆置数组

在这里插入图片描述

快慢指针

同时有一个慢指针和一个快指针。

解决这类问题的关键是:确定两个指针的移动策略。

删除数组中值为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]);
	}
}

链表

  1. 链表是一种链式数据结构,由若干节点组成,每个节点包含指向下一节 点的指针。
  2. 链表的物理存储方式是随机存储访问方式是顺序访问
  3. 查找链表节点的时间复杂度是O(n),中间插入、删除节点的时间复杂度是 O(1)。
  4. 链表是一种在物理上非连续、非顺序的数据结构
 public class ListNode {
     int val;
     ListNode next;
  }

链表的介绍

插入

首先我们考虑在某个位置插入一个车厢:

我们首先把前一个车厢的挂钩指向我们新增的车厢,时间复杂度是常数。

后把新增车厢的挂钩指向后一个车厢,就完成了我们的插入操作。同样,这一步的时间复杂度也是常数。

08.gif

所以对于链表,我们可以在 O(1) 的时间内完成插入操作。

删除

接下来我们来考虑删除操作,显然我们可以通过直接将要删除车厢的前一个车厢的挂钩,直接指向其后一个车厢来实现删除操作。当然我们还需要注意释放这个要删除的车厢所占的内存。因此删除操作的时间复杂度也是常数级别。

08.2.gif

可以 在常数时间内 完成插入和删除是链表的最大优势,如果我们使用的是普通数组的话,是无法达到这样的速度的。

双向链表

刚才我们说一个车厢只能到达其后面的车厢,现在拓宽这个限制。现在的火车一般是存在两个火车头的,然后每一节车厢我们可以到达其前面和后面的车厢,这么修改以后,我们的链表就变成了 双向链表。

8.3.gif

链表的定义(重要)

链表常考习题

删除链表的倒数第 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;
}

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));
}
  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值