前言
本文用图解的方式,以最简单的思路带你手把手解决这三个问题,但更重要的是要学会举一反三。
当然,三个题的解法肯定都不唯一,我这里给出来的解法可能并不是最优,只是以相对简单清晰的思路去分析问题,所以没有考虑复杂度的问题。
回型矩阵
题目描述
问题分析
先看一张图:
对于 n 为奇数和偶数,两个矩阵还不一样。
先看两种矩阵的共同点:
由内而外,先填外圈后填内圈,所以把填一圈抽象成一个大循环,每一个大循环又包含了四个小循环,每个小循环分别填一条边。
所以只需写出每个循环即可解决问题。
两种矩阵还有不同点:
对于 n 为偶数的情况,走完整数次大循环即可填满整个矩阵。
而对 n 为奇数的情况,走完整数次大循环之后会空下中间一个位置。
这个问题可以通过动态边界来解决:
定义两个边界 min 和 max ,没走完一次大循环让 min++, max–;
如果 n 为偶数,经过整数次大循环之后 min > max ,
如果 n 为奇数,经过整数次大循环之后 min = max 。
所以可以利用这一点加个判断条件填上中间那个元素。
代码实现
int main()
{
int n = 0;
scanf("%d", &n);
int i, j;
//根据输入值n分配一块空间给二维数组
int** arr = malloc(sizeof(int*) * n);
for (i = 0; i < n; i++)
arr[i] = (int*)malloc(sizeof(int) * n);
//计数器和边界的定义与初始化
int count = 1;
int min = 0, max = n - 1;
//大循环,跳出条件为 count < n * n
while (count < n * n)
{
//第一次小循环 - 填充上边
//每次填充完行号不变,列号+1,直至列号碰到边界max
for (i = min; i < max; i++)
arr[min][i] = count++;
//第二次小循环 - 填充右边
//每次填充完列号不变,行号+1,直至行号碰到边界max
for (i = min; i < max; i++)
arr[i][max] = count++;
//第三次小循环 - 填充下边
//每次填充完行号不变,列数-1,直至列号碰到边界min
for (i = max; i > min; i--)
arr[max][i] = count++;
//第四次小循环 - 填充左边
//每次填充完列号不变,行号-1, 直至行号碰到边界min
for (i = max; i > min; i--)
arr[i][min] = count++;
//每次大循环结束边界缩减
min++;
max--;
}
//填补 n 为奇数时的中间元素
if (min == max)
arr[min][max] = count;
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
printf("%d ", arr[i][j]);
printf("\n");
}
free(arr);
return 0;
}
蛇形矩阵
题目描述
问题分析
相较上一个问题,这个就比较单纯了,没涉及到奇偶数。
还是先看图:
(图片与牛客网上的一篇讨论极为相像,因为那篇讨论是我写的)
每个折线形状都相同,都有三条路径,那么只需实现三条路径上的打印,然后放在 while 大循环内即可。
但是看图可以发现,循环难免会出现越界的情况,那么只需在赋值前加个判断条件即可,即不越界才能赋值。
代码实现
int main()
{
int n = 0;
scanf("%d", &n);
int i, j;
//老样子,分配一块空间给二维数组
int** arr = malloc(sizeof(int*) * n);
for (i = 0; i < n; i++)
arr[i] = (int*)malloc(sizeof(int) * n);
//计数器和行号列号
int count = 1;
int row = 0;
int col = 0;
//一个大循环,内部实现三条路径上的打印
while (count <= n * n)
{
//第一个路径,只包含两个元素,任何时候都是
//每次执行完行号不变,其实都是0,列号+1
for (int i = 0; i < 2; i++)
{
if (row < n && col < n)
arr[row][col] = count++;
col++;
}
//第一个路径打印完列号会超出一个,给它抓回来
col--;
//第二个路径
//每次执行完行号+1,列号-1
while (col)
{
row++;
col--;
if (row < n && col < n)
arr[row][col] = count++;
}
//第二个路径打印完行号会超,给它拽回来
row++;
//第三个路径
//每次执行完行号-1,列号+1
while (row)
{
if (row < n && col < n)
arr[row][col] = count++;
row--;
col++;
}
}
for (i = 0; i < n; i++)
{
for (j = 0; j < n; j++)
printf("%d ", arr[i][j]);
printf("\n");
}
free(arr);
return 0;
}
Z字形转换
题目描述
问题分析
这个题有个比较坑的地方就是 示例3 给的那种情况,numRows = 1,此时字符串实际上没变,直接返回即可,所以把它列为一种特殊情况处理。
一般情况还是看图:
通过图可以发现这何尝不是一种循环呢,而且每次循环的路径还都一样。
只需要实现两个路径,把它放入 while 循环,当完全遍历字符串内容之后跳出循环即可完成。
设置一个二维数组存放字符,遍历字符串之后,再遍历一遍二维数组重新对 s 进行赋值并将其返回。
代码实现
char * convert(char * s, int numRows)
{
//处理特殊情况
if(numRows == 1)
return s;
int len = strlen(s); //统计字符串长度
int row = 0; //行号
int col = 0; //列号
int pos = 0; //指向字符串 s 的第 pos 个元素,每次
//执行后 pos++,当 pos = len 时跳出循环。
//定义一个二维数组接收字符,由于不知道具体大小,就不动态内存分配了。
char ch[1000][1000] = {0};
while(pos < len)
{
//第一条路径 - 竖着下来
//每次赋值完行号+1,列号不变
while(row < numRows)
{
if(pos == len)
break;
ch[row++][col] = s[pos++];
}
//走完第一条路径后行号会超,给它拽回来
row--;
//第二条路径 - 斜着上去
//每次行号-1,列号+1
while(row > 1)
{
if(pos == len)
break;
ch[--row][++col] = s[pos++];
}
//将行号列号指向下一次循环开始的位置
row--;
col++;
}
//重新对字符串 s 进行排列
pos = 0;
for(int i=0; i<numRows; i++)
for(int j=0; j<col; j++)
if(ch[i][j] != 0)
s[pos++] = ch[i][j];
return s;
}
小尾巴
最后,还是那句话,上面的解法不一定最优,但一定最好理解。
所以如果有更好的想法可以留言噢~
封面图送上: