题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。

例如:如果输入如下矩阵:

1              2              3              4
5              6              7              8
9              10           11           12
13           14           15           16

则依次打印出数字1, 2, 3, 4, 8, 12, 16, 15, 14, 13, 9, 5, 6, 7, 11, 10。

分析:第一次看到这个题目的时候,觉得这个题目很简单,完全不需要用到数据结构或者算法的知识,因此没有兴趣做这道题。后来听到包括Autodesk、EMC在内的多家公司在面试或者笔试里采用过这道题,于是想这么多家公司用它来检验一个程序员的编程功底总是有原因的,于是决定自己写一遍试一下。真正写一遍才发现,要完整写出这道题的代码,还真不是件容易的事情。

解决这道题的难度在于代码中会包含很多个循环,而且还有多个边界条件需要判断。如果在把问题考虑得很清楚之前就开始写代码,不可避免地会越写越混乱。因此解决这个问题的关键,在于先要形成清晰的思路,并把复杂的问题分解成若干个简单的问题。下面分享我分析这个问题的过程。

通常当我们遇到一个复杂的问题的时候,我们可以用图形帮助我们思考。由于我们是以从外圈到内圈的顺序依次打印,我们在矩阵中标注一圈作为我们分析的目标。在下图中,我们设矩阵的宽度为columns,而其高度为rows。我们我们选取左上角坐标为(startX, startY),右下角坐标为(endX, endY)的一个圈来分析。

startX, startY

endX, endY

由于endX和endY可以根据startX、startY以及columns、rows来求得,因此此时我们只需要引入startX和startY两个变量。我们可以想象有一个循环,在每一次循环里我们从(startX, startY)出发按照顺时针打印数字。

接着我们分析这个循环结束的条件。对一个5×5的矩阵而言,最后一圈只有一个数字,对应的坐标为(2, 2)。我们发现5 > 2 * 2。对一个6×6的矩阵而言,最后一圈有四个数字,对应的坐标仍然为(2, 2)。我们发现6 > 2 * 2依然成立。于是我们可以得出,让循环继续的条件是columns > startX * 2 && rows > startY * 2。有了这些分析,我们就可以写出如下的代码:

void PrintMatrixClockwisely(int** numbers, int columns, int rows)
{if(numbers == NULL || columns <= 0 || rows <= 0)return;int startX = 0;int startY = 0;while(columns > startX * 2 && rows > startY * 2){PrintMatrixInCircle(numbers, columns, rows, startX, startY);++startX;++startY;}
}

接下来我们分析如何在PrintMatrixInCircle中按照顺时针的顺序打印一圈的数字。如同在图中标注的那样,我们可以分四步来打印:第一步是从左到右打印一行(上图中黄色区域),第二步是从上到下打印一列(上图中绿色区域),第三步从右到左打印一行(上图中蓝色区域),最后一步是从下到上打印一列(上图中紫色区域)。也就是我们把打印一圈数字这个问题,分解成四个子问题。我们可以为每个子问题定义一个函数。四个步骤对应的函数名称我们分别定义为:PrintARowIncreasingly,PrintAColumnIncreasingly,PrintARowDecreasingly和PrintAColumnDecreasingly。

现在我们暂时不考虑如何去实现这四个函数,而是先考虑我们需要分别给这些函数传入哪些参数。第一步打印一行时,所有的数字的行号是固定的(startY),不同数字的列号不同。我们需要传入一个起始列号(startX)和终止列号(endX)。第二步打印一列时,所有的数字的列号是固定的,不同的数字的行号不同。我们需要传入一个起始行号(startY + 1)和一个终止行号(endY)。第三步和第四步和前面两步类似,读者可以自己分析。

接下来我们需要考虑特殊情况。并不是所有数字圈都需要四步来打印。比如当一圈退化成一行的时候,也就是startY等于endY的时候,我们只需要第一步就把所有的数字都打印完了,其余的步骤都是多余的。因此我们需要考虑第二、三、四步打印的条件。根据前面我们分析,不难发现打印第二步的条件是startY < endY。对于第三步而言,如果startX等于endX,也就是这一圈中只有一列数字,那么所有的数字都在第二步打印完了;如果startY等于endY,也就是这一圈中只有一行数字,那么所有的数字都在第一步打印完了。因此需要打印第三步的条件是startX < endX && startX < endY。第四步最复杂,首先startX要小于endX,不然所有的数字都在一列,在第二步中就都打印完了。另外,这个圈中至少要有三行数字。如果只有一行数字,所有数字在第一步中打印完了;如果只有两行数字,所有数字在第一步和第三步也都打印完了。因此打印第四步需要的条件是startY < endY – 1。

有了前面的分析,我们就能写出PrintMatrixInCircle的完整代码如下:

void PrintMatrixInCircle(int** numbers, int columns, int rows,int startX, int startY)
{int endX = columns - 1 - startX;int endY = rows - 1 - startY;PrintARowIncreasingly(numbers, columns, rows, startY, startX, endX);if(startY < endY)PrintAColumnIncreasingly(numbers, columns, rows, endX, startY + 1, endY);if(startX < endX && startY < endY)PrintARowDecreasingly(numbers, columns, rows, endY, endX - 1, startX);if(startX < endX && startY < endY - 1)PrintAColumnDecreasingly(numbers, columns, rows, startX, endY - 1, startY + 1);
}

接下来我们考虑如何打印一行或者一列。这对我们来说不是一件很难的事情。以函数PrintARowIncreasingly为例,我们只需要一个循环,把行号为startY,列号从startX到endX的所有数字依次从数组中取出来并逐个打印就行了,对应的代码是:

void PrintARowIncreasingly(int** numbers, int columns, int rows,int y, int firstX, int lastX)
{for(int i = firstX; i <= lastX; ++i){int number = *(*(numbers + y) + i);printf("%d\t", number);}
}

剩下的三个函数与此类似,代码依次如下:

void PrintAColumnIncreasingly(int** numbers, int columns, int rows,int x, int firstY, int lastY)
{for(int i = firstY; i <= lastY; ++i){int number = *(*(numbers + i) + x);printf("%d\t", number);}
}
void PrintARowDecreasingly(int** numbers, int columns, int rows,int y, int firstX, int lastX)
{for(int i = firstX; i >= lastX; --i){int number = *(*(numbers + y) + i);printf("%d\t", number);}
}
void PrintAColumnDecreasingly(int** numbers, int columns, int rows,int x, int firstY, int lastY)
{for(int i = firstY; i >= lastY; --i){int number = *(*(numbers + i) + x);printf("%d\t", number);}
}

本文已经收录到《剑指Offer——名企面试官精讲典型编程题》一书中,有改动,书中的分析讲解更加详细。欢迎关注。

本题已被九度Online Judge系统收录,欢迎读者移步到http://ac.jobdu.com/hhtproblems.php在线测试自己的代码。

博主何海涛对本博客文章享有版权。网络转载请注明出处http://zhedahht.blog.163.com/。整理出版物请和作者联系。

程序员面试题精选100题(51)-顺时针打印矩阵[算法]相关推荐

  1. 程序员面试题精选100题(51)-顺时针打印矩阵

    // 程序员面试题精选100题(51)-顺时针打印矩阵.cpp : 定义控制台应用程序的入口点. //#include "stdafx.h" #include <iostre ...

  2. 程序员面试题精选100题(37)-寻找丑数[算法]

    题目:我们把只包含因子 2. 3和 5的数称作丑数(Ugly Number).例如 6. 8都是丑数,但 14不是,因为它包含因子 7.习惯上我们把 1当做是第一个丑数.求按从小到大的顺序的第 150 ...

  3. 程序员面试题精选100题(28)-字符串的排列[算法]

    题目:输入一个字符串,打印出该字符串中字符的所有排列.例如输入字符串 abc ,则输出由字符 a . b . c 所能排列出来的所有字符串 abc . acb . bac . bca . cab 和 ...

  4. 程序员面试题精选100题(59)-字符串的组合[算法]

    题目:输入一个字符串,输出该字符串中字符的所有组合.举个例子,如果输入abc,它的组合有a.b.c.ab.ac.bc.abc. 分析:在本系列博客的第28题<字符串的排列>中,我们详细讨论 ...

  5. 程序员面试题精选100题(40)-扑克牌的顺子[算法]

    题目:从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的.2-10为数字本身,A为1,J为11,Q为12,K为13,而大小王可以看成任意数字. 分析:这题目很有意思,是一个典型的寓教于 ...

  6. 程序员面试题精选100题(58)-八皇后问题[算法]

    题目:在8×8的国际象棋上摆放八个皇后,使其不能相互攻击,即任意两个皇后不得处在同一行.同一列或者同一对角斜线上.下图中的每个黑色格子表示一个皇后,这就是一种符合条件的摆放方法.请求出总共有多少种摆法 ...

  7. 程序员面试题精选100题

    程序员面试题精选100题(01)-把二元查找树转变成排序的双向链表 题目:输入一棵二元查找树,将该二元查找树转换成一个排序的双向链表.要求不能创建任何新的结点,只调整指针的指向. 比如将二元查找树   ...

  8. [程序员面试题精选100题]13.第一个只出现一次的字符

    [题目] 在一个字符串中找到第一个只出现一次的字符.如输入abaccdeff,则输出b. [分析] [代码] /********************************* * 日期:2013- ...

  9. 程序员面试题精选100题:求从1到n的正数中1出现的次数

    // 程序员面试题精选100题(25):求从1到n的正数中1出现的次数 // 如 f(253) = (2!=0) * 100 + 2 * f(99) + (5!=0) * 10 + 5 * f(9) ...

最新文章

  1. 高德地图和百度地图接口封装遇到的问题(二):绘制带有方向箭头的折线
  2. xshell怎么连接windows server_未能连接一个Windows服务 怎么回事
  3. 用户细分_基于购买历史的用户细分
  4. swing学习一(swing介绍、swing样例、布局管理器)
  5. 开源3问:95%的技术人都不知道的开源真相
  6. 我国研发5款自主产权3D显卡;哈啰单车回应异常;Jboot 2.2.4发布 | 极客头条
  7. linux 4433端口,linux – 使用相同的openssl端口443绑定不同端口的apache ssl端口
  8. 排列图 - QCC小组活动推行知识系列介绍(三)
  9. 实现74HC151的8选1数据选择器功能以及用两片74HC151组成一个16选1数据选择器
  10. pptswot分析图怎么做_SWOT分析工具图表模板.ppt
  11. 同步Buck芯片的自举电容原理解析
  12. [vba]快速更新表格标题序号
  13. Html+CSS三栏式伸缩布局
  14. 【Python 基础教程】彻底解决python round函数的四舍五入不精确的问题
  15. win7虚拟机_win10使用hyperV创建虚拟机
  16. 中药图片拍照识别系统全套开源
  17. android 闪光灯程序,如何在Android中以编程方式打开前闪光灯?
  18. 关于精准打击自签名伪造SSL/TLS “受信任域名证书”的方案
  19. iOS 多渠道打包的解决方案
  20. 如何选择集团ERP管理系统_哪家的集团ERP管理系统好

热门文章

  1. 吴恩达Deeplearning.ai课程学习全体验:深度学习必备课程 By 路雪2017年8月14日 11:44 8 月 8 日,吴恩达正式发布了 Deepleanring.ai——基于 Cours
  2. 安徽一个班37人考进清华北大,老师发来一则短信,家长沉默了
  3. 网络招聘“草莽时代”该结束了
  4. RocketMQ-初体验RocketMQ(08)-IDEA拉取调测RocketMQ源码
  5. idea查看项目pid_idea启动时的端口号
  6. 安卓程序添加指纹解锁功能
  7. 接口测试工具-Jmeter压力测试使用
  8. 我的世界服务器物品绑定插件,我的世界灵魂绑定插件详解
  9. java enum 定义属性_java enum(枚举)使用详解 + 总结
  10. 求两个集合的差集代码_求求你了,不要再写循环求两个列表的交集,并集和差集了 | pythonic 小技巧...