最近在算法练习中遇到了几个有趣的二维数组问题,涵盖了杨辉三角、矩阵分区、发牌模拟、数字金字塔和稀疏矩阵转换等经典题型。这些问题看似基础,但想要写出高效且正确的解法,需要对二维数组的特性有深入理解。下面我将分享这些问题的解决思路和代码实现,希望能帮助到正在学习算法的朋友们。
杨辉三角是中国古代数学的杰出成果之一,它具有以下几个显著特征:
在实际编程中,我们通常用二维数组来表示杨辉三角。初始化时,首先设置每行的首尾元素为1,然后通过递推关系计算中间元素的值。
c复制#include<stdio.h>
int main(){
int T;
scanf("%d",&T);
int n[T];
for(int m=0;m<T;m++){
if(m>0){
printf("\n"); // 组间空行分隔
}
scanf("%d",&n[m]);
int arr[20][20]={0};
// 构建杨辉三角
for(int i=0;i<n[m];i++){
arr[i][0]=1; // 行首为1
arr[i][i]=1; // 行尾为1
for(int j=1;j<i;j++){
arr[i][j]=arr[i-1][j-1]+arr[i-1][j]; // 递推关系
}
}
// 格式化输出
for(int i=0;i<n[m];i++){
for(int j=0;j<=i;j++){
if(j==0)
printf("%d",arr[i][j]);
else
printf(" %d",arr[i][j]); // 数字间用空格分隔
}
printf("\n");
}
}
return 0;
}
注意事项:输出格式是这类题目的关键点之一。题目要求每行开头和结尾的"1"前后不能有多余空格,因此需要特别处理第一个元素的输出方式。
这个问题要求根据输入整数N,生成一个(2N+1)×(2N+1)的矩阵,并按照特定规则填充不同区域:
理解这些区域的定义和边界条件是解决问题的关键。通过分析可以发现,这些区域实际上是通过行号和列号的关系来划分的。
c复制#include<stdio.h>
int main(){
int N;
while(scanf("%d",&N)!=EOF){
int arr[2*N+1][2*N+1];
// 填充对角线
for(int i=0;i<2*N+1;i++){
arr[i][i]=1; // 主对角线
arr[i][2*N-i]=1; // 副对角线
}
// 填充上半三角区域
for(int i=0;i<N;i++){
for(int j=i+1;j<2*N-i;j++){
arr[i][j]=2;
}
}
// 填充下半三角区域
for(int i=N+1;i<2*N+1;i++){
for(int j=2*N-i+1;j<i;j++){
arr[i][j]=3;
}
}
// 填充左半三角区域
for(int j=0;j<N;j++){
for(int i=j+1;i<2*N-j;i++){
arr[i][j]=4;
}
}
// 填充右半三角区域
for(int j=N+1;j<2*N+1;j++){
for(int i=2*N-j+1;i<j;i++){
arr[i][j]=5;
}
}
// 输出矩阵
for(int i=0;i<2*N+1;i++){
for(int j=0;j<2*N+1;j++){
if(j==0)
printf("%d",arr[i][j]);
else
printf(" %d",arr[i][j]);
}
printf("\n");
}
}
return 0;
}
实操心得:在处理这种复杂矩阵填充问题时,建议先画出小规模的矩阵示例(如N=1或N=2),明确各个区域的边界条件,然后再编写代码。这样可以避免因边界条件不清导致的错误。
这个问题模拟了四人扑克牌的发牌过程:
关键在于理解牌的排列顺序和发牌顺序之间的关系。实际上,第i张牌(i从0开始)应该发给(i%4)号玩家,牌的花色是i/13,牌的点数是i%13。
c复制#include<stdio.h>
int main(){
char huase[4]={'c','d','h','s'}; // 花色数组
char person_huase[4][13]; // 存储每人每张牌的花色
int person_num[4][13]; // 存储每人每张牌的点数
// 初始化牌组并分配
for(int i=0;i<52;i++){
int person_id=i%4; // 确定玩家
int huase_id=i/13; // 确定花色
int num=i%13; // 确定点数
int card_id=i/4; // 玩家手中的第几张牌
person_huase[person_id][card_id]=huase[huase_id];
person_num[person_id][card_id]=num;
}
int n;
while(scanf("%d",&n)!=EOF){
for(int i=0;i<13;i++){
if(i>0)
printf(" %c %d",person_huase[n-1][i],person_num[n-1][i]);
else
printf("%c %d",person_huase[n-1][i],person_num[n-1][i]);
}
printf("\n");
}
return 0;
}
常见问题:初学者容易混淆牌的索引和玩家的索引关系。记住i%4决定玩家,i/13决定花色,i%13决定点数,i/4决定玩家手中的牌序,这样就能正确分配每张牌。
这个问题要求从金字塔顶端到底部任意一点的最大路径和,每次只能向下或向右下方移动。这是一个典型的动态规划问题,可以从下往上进行计算:
这种方法避免了递归带来的重复计算,时间复杂度为O(n²),空间复杂度也可以优化到O(n)。
c复制#include <stdio.h>
int max(int a,int b){
return a>b?a:b;
}
int main(){
int R;
scanf("%d",&R);
int arr[R][R];
for(int i=0;i<R;i++){
for(int j=0;j<=i;j++){
scanf("%d",&arr[i][j]);
}
}
int dp[R][R];
// 初始化最后一行
for(int j=0;j<R;j++){
dp[R-1][j]=arr[R-1][j];
}
// 自底向上计算
for(int i=R-2;i>=0;i--){
for(int j=0;j<=i;j++){
dp[i][j]=arr[i][j]+max(dp[i+1][j],dp[i+1][j+1]);
}
}
printf("%d",dp[0][0]);
return 0;
}
优化建议:如果金字塔的层数很大,可以考虑将dp数组优化为一维数组,因为计算第i行时只需要第i+1行的数据。这样可以减少空间复杂度。
稀疏矩阵是指大部分元素为零的矩阵。为了节省存储空间,我们可以只存储非零元素,使用三元组(行号,列号,值)的形式表示。这种方法特别适合大规模稀疏矩阵的存储和处理。
转换过程:
c复制#include<stdio.h>
int main(){
int M,N;
while(scanf("%d%d",&M,&N)!=EOF){
int num[M][N];
int res[400][3]; // 存储三元组
int count=0; // 非零元素计数器
// 读取矩阵并记录非零元素
for(int i=0;i<M;i++){
for(int j=0;j<N;j++){
scanf("%d",&num[i][j]);
if(num[i][j]!=0){
res[count][0]=i+1; // 行号(从1开始)
res[count][1]=j+1; // 列号(从1开始)
res[count][2]=num[i][j]; // 值
count++;
}
}
}
// 输出三元组
for(int i=0;i<count;i++){
printf("%d %d %d\n",res[i][0],res[i][1],res[i][2]);
}
printf("\n"); // 组间空行
}
return 0;
}
注意事项:题目要求行号和列号从1开始,而C语言中数组索引从0开始,因此在存储时需要将索引加1。另外,输出格式要求严格,每组数据之间要有空行,但最后一行后面不能有空行。
通过这五个问题的练习,我总结了以下几点经验:
在实际编程中,我建议先写出伪代码或解题思路,再着手编写具体代码。这样可以帮助理清思路,避免陷入细节而忽略整体逻辑。