2022-07-10-106期-作业讲解

 今天我们还是讲作业题:

第一题:

作业内容
实现一个函数,可以左旋字符串中的k个字符。



例如:



ABCD左旋一个字符得到BCDA

ABCD左旋两个字符得到CDAB

答案:

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>

void left_rotate(char arr[], int k)
{
	int i = 0;
	char tmp = 0;
	int a = strlen(arr);
	for (i = 0; i < k; i++)
	{
		tmp = arr[0];
		int j = 0;
		for (j = 0; j < a - 1; j++)
		{
			arr[j] = arr[j + 1];
		}
		arr[a - 1] = tmp;
	}
}
int main()
{
	char arr[10] = "hellobit";
	int k = 0;
	scanf("%d", &k);
	left_rotate(arr, k);
	printf("%s", arr);
	return 0;
}

我们首先确定思路:我的思路是这样的,我们可以采用两次for循环的模式,外层的for循环执行我们左旋的位数,内层的for循环执行我们其他元素的前置。

如图所示,这就是我们字符串的内容,我们假如左旋一位 

 我们可以发现,左旋一位的结果就是把首字符先拿出来,然后其他字符前置1个空格,然后把首字符拿到末位。

假如我们要左旋n位的话,我们首先要执行一个for循环,循环次数为n,在循环内部,我们把其他元素前置,前置后把首字符放到末位。这就是我们整体的思路

这种思路比较简单,我们换一种另外的思路:

#include<stdio.h>
#include<string.h>
#include<assert.h>
void revease(char*left,char*right)
{
	assert(left&&right);
	while (left < right)
	{

		char tmp = 0;
		tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}
}
void left_rotate(char arr[], int k)
{
	int len = strlen(arr);
	revease(arr, arr + k - 1);
	revease(arr + k, arr + len - 1);
	revease(arr, arr + len - 1);
}
int main()
{
	char arr[10] = "hellobit";
	int k = 0;
	scanf("%d", &k);
	left_rotate(arr, k);
	printf("%s", arr);
	return 0;
}

我们画一个图像:

 这是最开始的图像

假如我们要左旋两位,我们可以这样设计

我们首先让前两位进行反转。再让后六位进行反转。

最后再全部进行反转,得到的结果为

 恰好是我们要求做的左旋的位数

下一题:

杨氏矩阵
题目内容:
有一个数字矩阵,矩阵的每行从左到右是递增的,矩阵从上到下是递增的,请编写程序在这样的矩阵中查找某个数字是否存在。



要求:时间复杂度小于O(N);

首先,我们要介绍一下时间复杂度,比如我们的矩阵中有n个元素,我们的时间复杂度小于o(n)的意思是:我们查找某个元素花费的时间必须小于查找所有元素加起来所花费的时间。

#include<stdio.h>
int find_num(char arr[3][3], int r, int c, int k)
{
	int x = 0;
	int y = c - 1;
	while (x <= r - 1 && y >= 0)
	{
		if (k > arr[x][y])
		{
			x++;
		}
		else if (k < arr[x][y])
		{
			y++;
		}
		else
			return 1;
	}
	return 0;
}
int main()
{
	char arr[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int k = 0;
	scanf("%d", &k);
	int ret = find_num(arr, 3, 3, k);
	printf("%d", ret);
	return 0;
}

我们说一下思路:

 假如这是我们的杨氏矩阵,这里面有两个元素是比较特殊的,那就是3,7,也就是右上角和左下角。

因为假如我们要搜索的k值大于3,我们就可以划掉第0行的数字,假如我们要搜索的值小于3,我们就可以划掉第2列的数字。

对于7也同理,所以我们可以通过这种方法来逐渐缩小k的范围。

划掉的意思是这样:假如我们搜索的值小于3,我们直接让y值-1

假如我们搜索的值大于3,我们让x值+1.

当下标x,y对应的值与我们要搜索的k值相等,我们直接返回1,如果执行完while循环都没有相等,那我们就返回0.

但是我们虽然返回了1,但是我们并没有打印出我们要搜索值的位置,这时候怎么办呢?

有的同学会这样写

int find_num(char arr[3][3], int r, int c, int k)
{
	int x = 0;
	int y = c - 1;
	while (x <= r - 1 && y >= 0)
	{
		if (k > arr[x][y])
		{
			x++;
		}
		else if (k < arr[x][y])
		{
			y++;
		}
		else
		{
			printf("%d %d", x, y);
			return 1;
		}

	}
	return 0;
}

这种方法虽然简单,但是却不好,假如别人就是想用一下代码,而不像打印出来,那我们的代码就失去了作用,我们可以换一种写法,我们可以通过创建结构体的想法

#include<stdio.h>
struct P
{
	int x;
	int y;
};
struct P find_num(char arr[3][3], int r, int c, int k)
{
	int x = 0;
	int y = c - 1;
	struct P p = { -1, -1 };
	while (x <= r - 1 && y >= 0)
	{
		if (k > arr[x][y])
		{
			x++;
		}
		else if (k < arr[x][y])
		{
			y++;
		}
		else
		{
			p.x = x;
			p.y = y;
			return p;
		}

	}
	return p;
}
int main()
{
	char arr[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int k = 0;
	scanf("%d", &k);
	struct P ret = find_num(arr, 3, 3, k);
	printf("%d %d", ret);
	return 0;
}

我们创建结构体p,初始化结果为{-1,-1},因为负数不会和我们的行数列数产生歧义。

我们也可以使用指针的方法返回对应的坐标。

#include<stdio.h>

int Find_num(char arr[3][3], int *px, int *py, int k)
{

	int x = 0;
	int y = *px - 1;
	while (x <= *px&&y>= 0)
	{
		if (k > arr[x][y])
		{
			x++;
		}
		else if (k < arr[x][y])
		{
			y--;
		}
		else
		{

			*px = x;
			*py = y;
			return 1;
		}
	}
	*px = -1;
	*py = -1;
	return 0;
}
int main()
{
	char arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
	int k = 0;
	scanf("%d", &k);
	int a = 3;
	int b = 3;
	int  ret = Find_num(arr, &a, &b, k);
	if (ret)
	{
		printf("%d %d", a, b);
	}
	else
	{
		printf("%d %d", a, b);
	}
	return 0;
}

这里的思路是我们把a和b的地址传递到函数内部,我们通过函数来使a和b对应的值等于我们所要求的坐标,然后将其打印即可

px,py这里叫做返回型参数。

下一道题目

题目名称:
字符串旋转结果
题目内容:
写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串。



例如:给定s1 =AABCD和s2 = BCDAA,返回1

给定s1=abcd和s2=ACBD,返回0.



AABCD左旋一个字符得到ABCDA

AABCD左旋两个字符得到BCDAA

AABCD右旋一个字符得到DAABC

和我们之前做的左旋那个题目十分相似,我们来操作一下

#include<stdio.h>
#include<string.h>
int is_left_move(char arr1[], char arr2[])
{
	int len = strlen(arr1);
	int i = 0;
	for (i = 0; i < len; i++)
	{
		char tmp = arr1[0];
		int j = 0;
		for (j = 0; j < len - 1; j++)
		{
			arr1[j] = arr1[j + 1];
		}
		arr1[len - 1] = tmp;
		if (strcmp(arr1, arr2) == 0)
		{
			return 1;
		}
	}
	return 0;
}
int main()
{
	char arr1[] = "hellobit";
	char arr2[] = "ellobith";
	int ret = is_left_move(arr1, arr2);
	if (ret == 1)
	{
		printf("ok");
	}
	else
	{
		printf("no");
	}
	return 0;
}

我们主要介绍一下思路:我们要比较一个字符串是否是另一个字符串左旋得到的,一个字符串左旋产生的不同的字符串最多有n个,n是字符串的元素个数,所以我们可以让字符串左旋一位,我们进行判断,再进行左旋,循环判断。所以我们可以通过两次for循环实现我们的目的:第一个for循环:字符串左旋的次数。第二个for循环,字符串左旋对应的函数,在第一个for循环的内部要进行字符串比较,判断时候是左旋的字符串。

接下来,我们用一下更简单的方法,使用两个库函数就能解决我们的问题

#include<stdio.h>
#include<string.h>
int is_left_move(char arr1[], char arr2[])
{
	int len1 = strlen(arr1);
	int len2 = strlen(arr2);
	if (len1 != len2)
		return 0;
	strncat(arr1, arr2, len1);
	char*p=strstr(arr1, arr2);
	if (p == NULL)
		return 0;
	else
		return 1;
}
int main()
{
	char arr1[20] = "hellobit";
	char arr2[20] = "ellobith";
	int ret = is_left_move(arr1, arr2);
	if (ret == 1)
		printf("ok");
	else
		printf("no");
	return 0;
}

我们说一下思路:我们进行字符串追加,注意要使用strncat函数,因为strcat不能追加自身,追加过后的字符串是hellobithellobit,这里面有所有的我们旋转产生的情况,我们只需要使用strstr函数,该函数能够判断一个字符串中是否包含有另一个字符串,假如我们包含的有的话,我们返回第一个字符串中对应另一个字符串的起始地址,否则的话我们返回空指针。

至于这里为什么不能用strcat函数,我们画个图进行解释:

 

 这是我们追加的过程:我们是把首元素也就是a追加到了\0的位置,逐步进行追加,到最后我们会把被追加字符串的末位的\0也追加到最后,但是我们的\0的值已经在字符串追加的一开始就发生改变了,变成了a,所以我们的字符串是没有结束标志的,所以我们就死循环追加。所以我们的strcat并不能用来追加自身。

二维数组传参可以省略行但是不能省略列。

下一道题:

描述
KiKi有一个矩阵,他想知道转置后的矩阵(将矩阵的行列互换得到的新矩阵称为转置矩阵),请编程帮他解答。

输入描述:
第一行包含两个整数n和m,表示一个矩阵包含n行m列,用空格分隔。 (1≤n≤10,1≤m≤10)

从2到n+1行,每行输入m个整数(范围-231~231-1),用空格分隔,共输入n*m个数,表示第一个矩阵中的元素。

输出描述:
输出m行n列,为矩阵转置后的结果。每个数后面有一个空格。
示例1
输入:
2 3
1 2 3
4 5 6
复制
输出:
1 4 
2 5 
3 6 

这道题其实非常简单,答案:

#include<stdio.h>
int main()
{
	char arr[10][10] = { 0 };
	int i = 0;
	int j = 0;
	int m = 0;
	int n = 0;
	scanf("%d%d", &n, &m);
	for (i = 0; i < n; i++)
	{
		for (j = 0; j < m; j++)
		{
			scanf("%d", &arr[i][j]);
		}
	}
	for (i = 0; i < m; i++)
	{
		for (j = 0; j < n; j++)
		{
			printf("%d ",arr[j][i]);
		}
		printf("\n");
	}
	return 0;
}

对应的思路是:我们首先输入一个要创建的框架,然后输入我们对应的数字到框架里去,接下来,我们需要构建一个行数和列数和之前创建的相反的框架,然后把我们对应的数组元素倒置一下,就能打印出我们要的结果。

KiKi想知道一个n阶方矩是否为上三角矩阵,请帮他编程判定。上三角矩阵即主对角线以下的元素都为0的矩阵,主对角线为从矩阵的左上角至右下角的连线。

下一道题:

KiKi想知道一个n阶方矩是否为上三角矩阵,请帮他编程判定。上三角矩阵即主对角线以下的元素都为0的矩阵,主对角线为从矩阵的左上角至右下角的连线。

输入描述:
第一行包含一个整数n,表示一个方阵包含n行n列,用空格分隔。 (1≤n≤10)

从2到n+1行,每行输入n个整数(范围-231~231-1),用空格分隔,共输入n*n个数。

输出描述:
一行,如果输入方阵是上三角矩阵输出"YES"并换行,否则输出"NO"并换行。

示例1
输入:
3
1 2 3
0 4 5
0 0 6
复制
输出:
YES

对应的答案:

#include<stdio.h>
int main()
{
	int arr[10][10] = { 0 };
	int a = 0;
	scanf("%d", &a);
	int i = 0;
	int j = 0;
	int flag = 1;
	for (i = 0; i < a; i++)
	{
		for (j = 0; j < a; j++)
		{
			scanf("%d", &arr[i][j]);
		}
	}
	for (i = 0; i < a; i++)
	{
		for (j = 0; j < i; j++)
		{
			if (arr[i][j] != 0)
			{
				int flag = 0;
				goto end;
			}
		}
	}
end:
	if (flag == 0)
	{
		printf("No\n");
	}
	else
	{
		printf("Yes\n");
	}
	return 0;
}

我们解析一下思路:我们要写的是上三角矩阵,也就是说下三角对应的元素全部为0,我们首先执行两次for循环,构建起框架并把我们要输入到矩阵中的元素放在对应的框架里。

放在框架里之后,我们进行思考,下三角元素对应的特征是什么?进行思考,下三角对应的元素的特征是:i>j,我们再执行两次for循环,在循环内部进行判断,如果下三角对应的元素不是全是0,对应的就不是下三角矩阵。

结构体:

我们之前已经讲过一部分的结构体知识,今天我们再补充一些:

首先,匿名结构体类型

struct
{
	int a;
	char b;
	float c;
}x;

就像这种:只有结构体关键字,没有结构体标签的结构题就叫做匿名结构体类型,

匿名结构体类型只能使用一次。

匿名结构体还有一个问题:

struct
{
	int a;
	char b;
	float c;
}x;
struct
{
	int a;
	char b;
	float c;
}a[20],*p;
int main()
{
	p = &x;
	return 0;
}

两个完全相同类型的匿名j结构体类型,第一个创建了一个变量x,第二个创建了一个结构体数组a还有一个结构体指针p。因为我们是匿名结构体,所以编译器不认为这两种类型是相同的,所以第一个结构体变量并不能存储到第二个结构体数组中,并且第一个结构体变量的地址并不能存储到结构体指针中。

接下来,我们介绍结构体的另一个功能:帮助实现链表

数据结构:数据在内存中的存储结构。

数据结构在内存中包括:线性数据结构和树形数据结构

线性数据结构包括顺序表,链表。

树性数据结构包括二叉树等等。

顺序表:在内存中连续存放数值,图像如下:

 链表:不连续存放,例如

 那么链表是如何寻址

其中,a的空间内包含b的节点,b的空间又包含c的节点,这才能实现寻址。

设计链表的结点时,我们就需要结构体了

struct Node
{
	int a;
	struct Node next;
}

我们用设计一个结构体,结构体分别存放了数据和节点下一个结构体节点

这时候,问题来了:这个节点有多大呢?

答:

int main()
{
	printf("%d", sizeof(struct Node));
	return 0;
}

我们要计算这个结构体的大小,第一个整型是四个字节,那第二个结构体呢,结构体的所占空间的大小是不明确的。

我们可以用指针的方法解决:我们可以在第一个结构体中存放第二个结构体的地址,第二个结构体中存放第三个结构体的地址,因为是地址,所占的空间是明确的,就是四个字节,在最后一个结构体内部,我们传递一个空指针。

struct Node
{
	int a;
	struct Node*next;
}

这样的结构体分为两个域。

 上面的方法就是结构体的自引用:结构体中包含下一个同类型结构体的指针。

有同学会这样写代码

typedef struct
{
	int a;
	Node*next;
}Node;

我们首先写一个匿名结构体,然后把匿名结构体重命名为Node,然后我们在结构体的定义内部添加Node*的指针next,可行吗?

答:不可行,因为我们的typedef只能对已有的类型进行重命名,已有的类型说明我们必须有Node的类型,但是我们的Node的类型是对结构体重命名产生的,所以产生了矛盾

所以我们这里并不能使用匿名结构体,我们可以这样书写:

typedef struct Node
{
	int a;
	struct Node*next;
}Node;

linklist怎么用:

typedef struct Node
{
	int a;
	struct Node*next;
}*linklist;

这里的linklist就等价于

typedef struct Node*linklist;

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值