8086怎么输出数组_【算法】二维数组就是这么简单

一、背景

这篇文章主要通过几个具体的例子来看怎么使用二维数组。

PS:每次我都会把对应的练习题地址发送到群里,建议想学算法的朋友耐心的做一下这些题。

二、二维数组

二维数组和一维数组一样,也是有一系列元素组成。

但是二维数组的元素排列在矩形网格里,而不是一条直线。

当然,有些语言里面,二维数组是通过一维数组实现的,而在其他一些语言中,则不存在二维数组的概念。

例如在C/C++中,二维数组底层储存为一维数组。

下图显示了大小为 M * N 的数组 A 的实际结构:

c651ccf34b38537858a65599598a31aa.png

因此,如果我们将 A 定义为也包含 M * N 个元素的一维数组,那么实际上 A[i][j] 就等于 A[i * N + j]。

而在Java中,二维数组其实就是包含M个元素为一维数组,而每个元素又是一个大小为N的一维数组。

下图显示了 Java 中二维数组 A 的实际结构:

754a5d6fe167c2777fe14753d465a89e.png

而对于动态二维数组,则是一维动态数组的元素还是一维动态数组。

具体可以看下面的题吧。

三、对角线遍历

告诉你一个N * M的二维数组,求按对角线遍历这个二维数组,并输出遍历的元素。

遍历规则可以参考下图。

8ccefe01ffea309264a216ca1d48ba1a.png

其实这道题看着很简单,但是做起来会发现很复杂,需要考虑的情况太多

一会是对角线向上,一会是对角线向下。

一会是左边越界,一会是右边越界,或者上边越界,或者下边越界。

所以我们需要对这道题进行拆解划分,来减少边界的判断。

简单看下上图,可以发现答案是沿着对角线以S型的方式走一遍

所以我们可以分三步走。

第一步得到所有的对角线(从上到下)。

第二步判断对角线是否需要翻(有方向,可能需要改成从下到上)。

第三步将对角线拼接到答案上。

第一步,我们可以以对角线最上面的顶点的行坐标来划分。

分两部分:第一部分行坐标是矩阵的最上边,从左到右。第二部分行坐标是矩阵的最右边,从上到下。

有了最上面顶点的坐标,我们就可以循环斜着扫描,每扫描下一行,列数依次减一,只要没越界,都属于这个对角线上的合法坐标。

6c79ecc6c4e0c078c85d837a0737adaa.png

第二步,可以发现对角线的方向按奇偶性在翻转,所以我们使用一个flag来标记即可。

第三步就是将对角线的数据拼接到答案上,循环拼接即可。

一、背景

这篇文章主要通过几个具体的例子来看怎么使用二维数组。

PS:每次我都会把对应的练习题地址发送到群里,建议想学算法的朋友耐心的做一下这些题。

二、二维数组

二维数组和一维数组一样,也是有一系列元素组成。

但是二维数组的元素排列在矩形网格里,而不是一条直线。

当然,有些语言里面,二维数组是通过一维数组实现的,而在其他一些语言中,则不存在二维数组的概念。

例如在C/C++中,二维数组底层储存为一维数组。

下图显示了大小为 M * N 的数组 A 的实际结构:

006cbdd561d7628ff7957f3e6e8f702b.png

因此,如果我们将 A 定义为也包含 M * N 个元素的一维数组,那么实际上 A[i][j] 就等于 A[i * N + j]。

而在Java中,二维数组其实就是包含M个元素为一维数组,而每个元素又是一个大小为N的一维数组。

下图显示了 Java 中二维数组 A 的实际结构:

55ca493ed30341eb854d68224bdffb91.png

而对于动态二维数组,则是一维动态数组的元素还是一维动态数组。

具体可以看下面的题吧。

三、对角线遍历

告诉你一个N * M的二维数组,求按对角线遍历这个二维数组,并输出遍历的元素。

遍历规则可以参考下图。

29eba21c5c50b1b82348c4b5c44122fa.png

其实这道题看着很简单,但是做起来会发现很复杂,需要考虑的情况太多

一会是对角线向上,一会是对角线向下。

一会是左边越界,一会是右边越界,或者上边越界,或者下边越界。

所以我们需要对这道题进行拆解划分,来减少边界的判断。

简单看下上图,可以发现答案是沿着对角线以S型的方式走一遍

所以我们可以分三步走。

第一步得到所有的对角线(从上到下)。

第二步判断对角线是否需要翻(有方向,可能需要改成从下到上)。

第三步将对角线拼接到答案上。

第一步,我们可以以对角线最上面的顶点的行坐标来划分。

分两部分:第一部分行坐标是矩阵的最上边,从左到右。第二部分行坐标是矩阵的最右边,从上到下。

有了最上面顶点的坐标,我们就可以循环斜着扫描,每扫描下一行,列数依次减一,只要没越界,都属于这个对角线上的合法坐标。

6044104fb06def5cd5d8497d6d290e6e.png

第二步,可以发现对角线的方向按奇偶性在翻转,所以我们使用一个flag来标记即可。

第三步就是将对角线的数据拼接到答案上,循环拼接即可。

058bb78bf562e65f1756e0fcf1ca2293.png

四、螺旋矩阵

给一个N * M的矩阵,按顺时针输出所有元素

d8952c2f01b366093b67018ea49235f2.png

上面那道题对角线是两个方向,现在螺旋遍历则是四个方向了。

此时需要考虑的特殊情况更多了。

我们逐一写出所有方向的逻辑也可以,但是很容易漏写。

面对矩阵的这种问题,常用的方法是模板化(状态机)

具体来说就是将所有操作进行抽象提取,划分为不同的状态,每个状态有自己的模板操作。

模板化后的好处是我们可以直接循环就可以完成遍历矩阵了。

8a0ab748179c11e1b085255e0cdd70c5.png

比如上面这个代码。

第一个是将操作按顺时针标号模板化,这样的好处是我们可以通过运算得到下一个操作。

第二个是将上下左右的边界base进行模板化,同时储存一个维护边界的辅助数组baseInc。

第三个是将最关键的矩阵遍历计算规则xyInc模板化。

通过上面三个模板化,我们就可以快速判断当前是不是处理完了(最外层循环)。

也可以快速判断当前方向的遍历是否完成了(内层循环)。

当我们遍历完一个方向后,x,y肯定是越界的,所以需要回滚,并更新边界。

然后转换到下个方向状态,又由于回滚后的坐标之前已经处理,所以需要在新的方向移动一下,到达新的未处理的位置。

就这样,一道很复杂的螺旋遍历矩阵问题,就转化为了两层循环的问题了。

一、背景

这篇文章主要通过几个具体的例子来看怎么使用二维数组。

PS:每次我都会把对应的练习题地址发送到群里,建议想学算法的朋友耐心的做一下这些题。

二、二维数组

二维数组和一维数组一样,也是有一系列元素组成。

但是二维数组的元素排列在矩形网格里,而不是一条直线。

当然,有些语言里面,二维数组是通过一维数组实现的,而在其他一些语言中,则不存在二维数组的概念。

例如在C/C++中,二维数组底层储存为一维数组。

下图显示了大小为 M * N 的数组 A 的实际结构:

81a019ddf41b9dab5bc4a73181586bf2.png

因此,如果我们将 A 定义为也包含 M * N 个元素的一维数组,那么实际上 A[i][j] 就等于 A[i * N + j]。

而在Java中,二维数组其实就是包含M个元素为一维数组,而每个元素又是一个大小为N的一维数组。

下图显示了 Java 中二维数组 A 的实际结构:

8717259d40ff6347beb5fabb74229702.png

而对于动态二维数组,则是一维动态数组的元素还是一维动态数组。

具体可以看下面的题吧。

三、对角线遍历

告诉你一个N * M的二维数组,求按对角线遍历这个二维数组,并输出遍历的元素。

遍历规则可以参考下图。

6afe4974d17e091cfe5ceb29f39bf3ba.png

其实这道题看着很简单,但是做起来会发现很复杂,需要考虑的情况太多

一会是对角线向上,一会是对角线向下。

一会是左边越界,一会是右边越界,或者上边越界,或者下边越界。

所以我们需要对这道题进行拆解划分,来减少边界的判断。

简单看下上图,可以发现答案是沿着对角线以S型的方式走一遍

所以我们可以分三步走。

第一步得到所有的对角线(从上到下)。

第二步判断对角线是否需要翻(有方向,可能需要改成从下到上)。

第三步将对角线拼接到答案上。

第一步,我们可以以对角线最上面的顶点的行坐标来划分。

分两部分:第一部分行坐标是矩阵的最上边,从左到右。第二部分行坐标是矩阵的最右边,从上到下。

有了最上面顶点的坐标,我们就可以循环斜着扫描,每扫描下一行,列数依次减一,只要没越界,都属于这个对角线上的合法坐标。

4fc0d73dde39223d0fd6ecfc9d021e4e.png

第二步,可以发现对角线的方向按奇偶性在翻转,所以我们使用一个flag来标记即可。

第三步就是将对角线的数据拼接到答案上,循环拼接即可。

010519eda0de6dffba927c50a9f73bcb.png

四、螺旋矩阵

给一个N * M的矩阵,按顺时针输出所有元素

c9d474a599a10b5a7523c2922011cdf4.png

上面那道题对角线是两个方向,现在螺旋遍历则是四个方向了。

此时需要考虑的特殊情况更多了。

我们逐一写出所有方向的逻辑也可以,但是很容易漏写。

面对矩阵的这种问题,常用的方法是模板化(状态机)

具体来说就是将所有操作进行抽象提取,划分为不同的状态,每个状态有自己的模板操作。

模板化后的好处是我们可以直接循环就可以完成遍历矩阵了。

9e66bdcfaf5610162b42fe4811101e2c.png

比如上面这个代码。

第一个是将操作按顺时针标号模板化,这样的好处是我们可以通过运算得到下一个操作。

第二个是将上下左右的边界base进行模板化,同时储存一个维护边界的辅助数组baseInc。

第三个是将最关键的矩阵遍历计算规则xyInc模板化。

通过上面三个模板化,我们就可以快速判断当前是不是处理完了(最外层循环)。

也可以快速判断当前方向的遍历是否完成了(内层循环)。

当我们遍历完一个方向后,x,y肯定是越界的,所以需要回滚,并更新边界。

然后转换到下个方向状态,又由于回滚后的坐标之前已经处理,所以需要在新的方向移动一下,到达新的未处理的位置。

就这样,一道很复杂的螺旋遍历矩阵问题,就转化为了两层循环的问题了。

五、杨辉三角

输入一个数字N,输出高度为N的杨辉三角。

注:杨辉三角参考下图。

2bff1a4b7b880a8dd5de20367c69164a.gif

这道题与前面两道题相比就简单多了。

根据动图中的效果,我们可以发现一个规律:下一行某个位置的值时上一行相同位置和前一个位置值的和。

即f(x, y) = f(x-1, y) + f(x-1, y-1)。

而对于每一行的第一个和最后一个,不满足上面的规律(越界了),但值都是1,特殊处理即可。

于是我们就可以按照这个规律写出答案了。

c96fb42d8e50f32e8f0db18a4b8569ce.png

七、最后

在二维数组里面,对角线遍历和螺旋遍历就有点技术含量了。

当然,对于计算机专业的人来说,那个在学习第一门语言的时候,大家肯定都实现过这样的问题的。

这里主要是提供一种思路,可以较为方便的处理这个问题。

如果你有更好的思路,可以告诉我。

-EOF-

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值