面试 15:顺时针从外往里打印数字(剑指 Offer 第 20 题)
面试 15:顺时针从外往里打印数字
题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印每一个数字。例如输入: {{1,2,3}, {4,5,6}, {7,8,9}} 则依次打印数字为 1、2、3、6、9、8、7、4、5
这是昨天最后给大家留下的题目,相信大家也有去思考如何处理这道题目了。
初看这个题目,比较容易理解,也无需牵扯到数据结构或者高级的算法,看起来问题比较简单,但实际上解决起来且并没有想象中的容易。
大家极有可能想到循环嵌套的方式,套用几个 for 循环就可以啦。
- 首先打印第 1 行,然后第一个 for 循环从第一列打印到最后一列。
- 到最后一列,开始向下打印,为了防止重复打印第一行最后一列的数字,所以应该从第二行开始打印;
- 上面步骤 2 到底的时候,再在最后一行从倒数第二列开始往前打印一直到第一列;
- 用步骤 3 到最后一行第一列的时候再往上打印,第一行第一列由于步骤 1 已经打印过,所以这次只需要从倒数第二行第一列开始打印到顺数第二行第一列即可;
- 然后里面其实是一样的,不难看出里面其实就是对一个更小的矩阵重复上面的步骤 1 到步骤 4;
- 由于之前说了一定注意边界值,所以我们再步骤 1 之前严格注意一下传入矩阵为 null 的情况。
思路想好了,所以开始下笔写起代码:
public class Test15 {
private static void print(int[][] nums) {
if (nums == null)
return;
int rows = nums.length;
int columns = nums[0].length;
// 因为一次循环后 里面的矩阵会少 2 行,所以我们步长应该设置为 2
// 因为一次循环后 里面的矩阵会少 2 行,所以我们步长应该设置为 2
for (int i = 0; i * 2 < rows || i * 2 < columns; i++) {
// 向右打印,i 代表第 i 行,用 j 代表列,从 0 到 列数-1-2*i
for (int j = i; j < columns - 2 * i; j++) {
System.out.print(nums[i][j] + ",");
}
// 向下打印,j 代表行,列固定为最后一列-i*2
for (int j = i + 1; j < rows - 2 * i; j++) {
System.out.print(nums[j][rows - 1 - 2 * i] + ",");
}
// 向左打印,j 代表列,行固定为最后一列-i*2
for (int j = rows - 2 - 2 * i; j >= 2 * i; j--) {
System.out.print(nums[rows - 1 - 2 * i][j] + ",");
}
// 向上打印,j 代表行,列固定为第一列 +i*2
for (int j = rows - 2 - 2 * i; j > 2 * i; j++) {
System.out.print(nums[j][2 * i] + ",");
}
}
}
public static void main(String[] args) {
int[][] nums = {{1, 2, 3},
{4,5,6},
{7,8,9}};
print(nums);
}
复制代码
上面的代码可能大家会觉得看的很绕,实际上我也很晕,在这种很晕的情况下通常是极易出现问题的。不信?不妨我们分析来看看。
- 首先我们做了 null 的输入值判断,挺好的,这没问题;
- 然后我们做了一个循环,输出看成一个环一个环的输出,因为输出完成一个环后总会少 2 行和 2 列,最后一次输出例外,所以我们给出步长为 2 ,并且中间的判断采用 || 而不是 &&,这里也没啥问题;
- 我们直接代入题干中的例子试一试。
- rows = 3,columns = 3,最外层循环会进行 2 次,符合条件;
- 进入第一次循环,第一次打印向右,j 从 0 一直递增到 2 循环 3 次,打印出 1, 2, 3,没问题;
- 进入第二次循环,本次循环我们希望打印 6,9;我们从 i + 1 列开始,一直到最后一列,正确,没问题;
- 进入第三次循环,测试没问题,可以正常打印 8,7;
- 进入第四次循环,测试没问题,可以正常打印 4;
- 最外层循环进入第二次,此时 i = 1, i < 1,出现错误。额,这里循环结束条件应该 i <= columns - 2 * i
- ....
不知道小伙伴有没有被绕晕,反正我已经云里雾里了,我是谁?我在哪?
各种试,会发现坑还不少,其实上面贴的这个代码已经是经过上面这样走流程走了好几次修正的,但特别无奈,这个坑始终填不满。
有时候,不得不说,其实能有上面这般思考的小伙伴已经很优秀了,但在算法上还是欠了点火候。在面试中,我们当然希望竭尽全力完成健壮性很棒,又能实现功能的代码,但不得不说,人都有思维愚钝的时候,有时候就是怎么也弄不出来。
我们在解题前,其实不妨通过画图或者其他的方式先和面试官交流自己的思路,虽然他不会告诉你这样做对与否。但这其实就形成了一种非常好的沟通方式,当然也是展现你沟通能力的一种体现!
前面的思路其实没毛病,只是即使我们得到了正解,但这样的一连串代码,别说面试官,你自己可能都看的头大。
我们确实可以用这样先打印矩阵最外层环,打印完后把里面的再当做一个环,重复外面的情况打印。环的打印次数上面也提了,限制结束的条件就是环数 <= 行数的二分之一 && 环数 <= 列数的 二分之一。
所以我们极易得到这样的代码:
private static void print(int[][] nums) {
if (nums == null)
return;
int rows = nums.length;
int columns = nums[0].length;
for (int i = 0; i * 2 < rows && i * 2 < columns; i++) {
printRing(nums, i, rows, columns);
}
}
复制代码
我们着重是需要编写 printRing(nums,i)
的代码。
仔细分析,我们打印一圈实际上就分为四步:
- 从左到右打印一行;
- 从上到下打印一列;
- 从右到左打印一行;
- 从下到上打印一列;
不过值得注意的是,最后一圈有可能退化为只有一行,只有一列,甚至只有 1 个数字,因此这样的打印并不需要 4 步。下图是几个退化的例子,他们打印一圈分别只需要 3 步、2 步 甚至 1 步。
因此我们需要仔细分析打印时每一步的前提条件。
- 第一步总是需要的,不管你是一个数字,还是只有一行。
- 如果只有一行,那就不用第二步了,所以第二步能进去的条件是终止的行号大于起始的行号;
- 如果刚刚两行并且大于两列,则可进行第三步打印;
- 要想进行第四步的话,除了终止列号大于起始行号以外,还得至少有三行。
此外,依然得额外地注意:数组的下标是从 0 开始的,所以尾坐标总是得减 1 ,并且每进行一次循环,尾列和尾行的坐标总是得减去 1。
所以,完整的代码就奉上了:
public class Test15 {
private static void print(int[][] nums) {
if (nums == null)
return;
int rows = nums.length;
int columns = nums[0].length;
for (int i = 0; i * 2 < rows && i * 2 < columns; i++) {
printRing(nums, i, rows, columns);
}
}
private static void printRing(int[][] nums, int start, int rows, int columns) {
// 设置两个变量,endRow 代表当前环尾行坐标;endCol 代表当前环尾列坐标;
int endRow = rows - 1 - start;
int endCol = columns - 1 - start;
// 第一步:打印第一行,行不变列变,列从起到尾
for (int i = start; i <= endCol; i++) {
System.out.print(nums[start][i] + ",");
}
// 假设有多行才需要打印第二步
if (endRow > start) {
// 第二步,打印尾列,行变列不变,需要注意的是尾列第一行已经打印过
for (int i = start + 1; i <= endRow; i++) {
System.out.print(nums[i][endCol] + ",");
}
}
// 至少两行并且 2 列才会有第三步逆序打印
if (endCol > start && endRow > start) {
// 第三步,打印尾行,行不变,列变。需要注意尾行最后一列第二步已经打印
for (int i = endCol - 1; i >= start; i--) {
System.out.print(nums[endRow][i] + ",");
}
}
// 至少大于 2 行 并且大于等于 2 列才会有第四步打印
if (endRow > start && endCol - 1 > start) {
// 第四步,打印首列,行变,列不变。需要注意尾行和首行的都打印过
for (int i = endRow - 1; i >= start + 1; i--) {
System.out.print(nums[i][start] + ",");
}
}
}
public static void main(String[] args) {
int[][] nums = {{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}};
print(nums);
}
}
复制代码
用自己准备的测试用例输入测试,没有问题,通过。
上面的代码中用两个变量 endRow
和 endCol
以及画图完美地解决了我们思路混乱并且代码难以看明白的问题。「其实不用吐槽判断方法有重复的情况,我们都是为了看起来思路更加清晰。」
只看不练,很明显这样的题是容易被绕进去的,思路其实我们很好想到,但实现出来完全是另外一回事,所以大家不妨再去动手试试吧~
紧张之余,还是要留下明天的习题,记得提前思考和动手练习哟~
面试题:输入两个整数序列,第一个序列表示栈的压入顺序,请判断二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如:压入序列为{1,2,3,4,5},那{4,5,3,2,1} 就是该栈的弹出顺序,而{4,3,5,1,2} 明显就不符合要求;
面试 15:顺时针从外往里打印数字(剑指 Offer 第 20 题)的更多相关文章
- 【LeetCode】151. 翻转字符串里的单词(剑指offer 58-I)
151. 翻转字符串里的单词 知识点:字符串:双指针 题目描述 给你一个字符串 s ,逐个翻转字符串中的所有 单词 . 单词 是由非空格字符组成的字符串.s 中使用至少一个空格将字符串中的 单词 分隔 ...
- 牛客网剑指offer第19题——顺时针打印矩阵
这个题看似很简单: 题目: 输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 1 ...
- 面试 16:栈的压入压出队列(剑指 Offer 第 22 题)
我们今天继续来看看周五留下的习题: 面试题:输入两个整数序列,第一个序列表示栈的压入顺序,请判断二个序列是否为该栈的弹出顺序.假设压入栈的所有数字均不相等.例如:压入序列为{1,2,3,4,5},那{ ...
- 剑指offer第12题打印从1到n位数以及大整数加法乘法
字符和数字加减就是字符的ASCII码和数字直接加减. 方法一: 1)在字符串操作中给一个整形数字加(字符0)就是把它转化为字符,当然给一个字符减去(字符0)就可以把它转化为数字了:如果确实是最后 ...
- 3、从尾到头打印链表------------>剑指offer系列
题目 输入一个链表,按链表值从尾到头的顺序返回一个ArrayList. 分析 要了解链表的数据结构: val属性存储当前的值,next属性存储下一个节点的引用. 要遍历链表就是不断找到当前节点的nex ...
- 从尾到头打印链表——剑指Offer
https://www.nowcoder.net/practice/d0267f7f55b3412ba93bd35cfa8e8035?tpId=13&tqId=11156&tPage= ...
- [互联网面试笔试汇总C/C++-9] 实现赋值运算符函数-剑指offer
题目:如下为类型CMyString的声明,请为该类型添加赋值运算符函数. class CMyString { public: CMyString(char* pData = NULL); CMyStr ...
- 剑指 offer set 20 打印出和为 s 的连续正序序列
题目 100 可以由 9~16, 或者 18 ~ 22 组成 思路 1. 与 Leetcode Container With Most Water 有些类似, 依然是平移题目. 但这道更加复杂 2. ...
- 剑指offer第3题:从尾到头打印链表
方法一:采用栈来存储,用ArrayList保存.注意题目给出的输出结果是ArrayList import java.util.ArrayList; import java.util.Stack; pu ...
随机推荐
- git 入门教程之本地和远程仓库的本质
本地仓库和远程仓库在本质上没有太大区别,只不过一个是本地电脑,一个是远程电脑. 远程仓库不一定非得是 github 那种专门的"中央服务器",甚至局域网的另外一台电脑也可以充当&q ...
- 使用VSTS的Git进行版本控制(二)——提交保存工作
使用VSTS的Git进行版本控制(二)--提交保存工作 当对文件进行更改时,Git将在本地仓库中记录更改.可以通过选择变更来提交的对应更改.提交总是针对本地的Git仓库,因此不必担心提交是完美的,或者 ...
- openstack nova工作流程
工作流程请求:nova boot --image ttylinux --flavor 1 i-01nova-api 接受请求,一个tcp REST请求.nova-api 发送一个创建虚拟机的请求到消息 ...
- eclipse版本对应名称以及下载地址
Eclipse 1.0 2001年11月7日(Win32/Linux32 Motif) Eclipse 2.0 2002年6月27日(Linux32 Motif ...
- Windows Server2008 R2安装wampserver缺少api-ms-win-crt-runtime-l1-1-0.dll解决方案
安装wampserver经常会遇到缺少各种dll文件的问题,可以在安装之前先安装一下微软运行库合集,但此时仍有可能缺少api-ms-win-crt-runtime-l1-1-0.dll文件,那么可以尝 ...
- Java断言(Assertion)
断言(Assertion)是Java中一条语句,包含一个布尔表达式,当该布尔值为真,程序则被认为是正确的:当布尔值为假,则系统会抛出错误. 断言默认禁用的,在开发时候可开启功能,有利于纠正错误,增加可 ...
- [福大软工] Z班 软件工程实践总结 作业成绩
作业要求 http://www.cnblogs.com/easteast/p/8081265.html 评分细则 本次作业评分满分为20分,分为五个部分,分别如下: 回望过去(5'):以实际数据总结分 ...
- 《Java大学教程》—第9章 软件质量
软件质量:可维护性.可靠性.健壮性.可用性. 9.3 可维护性系统维护(maintaining)是指根据需求的变化更新现有系统的过程 9.3.1 封装的重要性连锁反应:对系统某一部分的改变可能会 ...
- Codeforces Round #542 B Two Cakes
B. Two Cakes time limit per test 1 second memory limit per test 256 megabytes input standard input o ...
- 接入天猫精灵auth2授权页面https发送ajax请求
已存在一个应用A,采用的是http交互, 在接入天猫精灵时,要求请求类型是https,所以在应用服务前加了个nginx转发https请求.在绑定授权页面,会发送ajax请求验证用户名和密码,采用htt ...