一、背景
这篇文章主要通过几个具体的例子来看怎么使用二维数组。
PS:每次我都会把对应的练习题地址发送到群里,建议想学算法的朋友耐心的做一下这些题。
二、二维数组
二维数组和一维数组一样,也是有一系列元素组成。
但是二维数组的元素排列在矩形网格里,而不是一条直线。
当然,有些语言里面,二维数组是通过一维数组实现的,而在其他一些语言中,则不存在二维数组的概念。
例如在C/C++中,二维数组底层储存为一维数组。
下图显示了大小为 M * N 的数组 A 的实际结构:
![c651ccf34b38537858a65599598a31aa.png](https://i-blog.csdnimg.cn/blog_migrate/4d87514eb89746c9b61eaa8b4e7ab490.jpeg)
因此,如果我们将 A 定义为也包含 M * N 个元素的一维数组,那么实际上 A[i][j] 就等于 A[i * N + j]。
而在Java中,二维数组其实就是包含M个元素为一维数组,而每个元素又是一个大小为N的一维数组。
下图显示了 Java 中二维数组 A 的实际结构:
![754a5d6fe167c2777fe14753d465a89e.png](https://i-blog.csdnimg.cn/blog_migrate/00e61a747711c840bf22501ea86ac850.jpeg)
而对于动态二维数组,则是一维动态数组的元素还是一维动态数组。
具体可以看下面的题吧。
三、对角线遍历
告诉你一个N * M的二维数组,求按对角线遍历这个二维数组,并输出遍历的元素。
遍历规则可以参考下图。
![8ccefe01ffea309264a216ca1d48ba1a.png](https://i-blog.csdnimg.cn/blog_migrate/f42de853412c7f1096d647a8809d7bbf.jpeg)
其实这道题看着很简单,但是做起来会发现很复杂,需要考虑的情况太多。
一会是对角线向上,一会是对角线向下。
一会是左边越界,一会是右边越界,或者上边越界,或者下边越界。
所以我们需要对这道题进行拆解划分,来减少边界的判断。
简单看下上图,可以发现答案是沿着对角线以S型的方式走一遍。
所以我们可以分三步走。
第一步得到所有的对角线(从上到下)。
第二步判断对角线是否需要翻(有方向,可能需要改成从下到上)。
第三步将对角线拼接到答案上。
第一步,我们可以以对角线最上面的顶点的行坐标来划分。
分两部分:第一部分行坐标是矩阵的最上边,从左到右。第二部分行坐标是矩阵的最右边,从上到下。
有了最上面顶点的坐标,我们就可以循环斜着扫描,每扫描下一行,列数依次减一,只要没越界,都属于这个对角线上的合法坐标。
![6c79ecc6c4e0c078c85d837a0737adaa.png](https://i-blog.csdnimg.cn/blog_migrate/d95ba203beee69a86afc771f35c5badc.jpeg)
第二步,可以发现对角线的方向按奇偶性在翻转,所以我们使用一个flag来标记即可。
第三步就是将对角线的数据拼接到答案上,循环拼接即可。
一、背景
这篇文章主要通过几个具体的例子来看怎么使用二维数组。
PS:每次我都会把对应的练习题地址发送到群里,建议想学算法的朋友耐心的做一下这些题。
二、二维数组
二维数组和一维数组一样,也是有一系列元素组成。
但是二维数组的元素排列在矩形网格里,而不是一条直线。
当然,有些语言里面,二维数组是通过一维数组实现的,而在其他一些语言中,则不存在二维数组的概念。
例如在C/C++中,二维数组底层储存为一维数组。
下图显示了大小为 M * N 的数组 A 的实际结构:
![006cbdd561d7628ff7957f3e6e8f702b.png](https://i-blog.csdnimg.cn/blog_migrate/26a652cecc504e28d33403d4ba262985.jpeg)
因此,如果我们将 A 定义为也包含 M * N 个元素的一维数组,那么实际上 A[i][j] 就等于 A[i * N + j]。
而在Java中,二维数组其实就是包含M个元素为一维数组,而每个元素又是一个大小为N的一维数组。
下图显示了 Java 中二维数组 A 的实际结构:
![55ca493ed30341eb854d68224bdffb91.png](https://i-blog.csdnimg.cn/blog_migrate/dc36983c4fb3c968fc48b12214385aef.jpeg)
而对于动态二维数组,则是一维动态数组的元素还是一维动态数组。
具体可以看下面的题吧。
三、对角线遍历
告诉你一个N * M的二维数组,求按对角线遍历这个二维数组,并输出遍历的元素。
遍历规则可以参考下图。
![29eba21c5c50b1b82348c4b5c44122fa.png](https://i-blog.csdnimg.cn/blog_migrate/e8643d311aa1dd7a8382cb471cff086e.jpeg)
其实这道题看着很简单,但是做起来会发现很复杂,需要考虑的情况太多。
一会是对角线向上,一会是对角线向下。
一会是左边越界,一会是右边越界,或者上边越界,或者下边越界。
所以我们需要对这道题进行拆解划分,来减少边界的判断。
简单看下上图,可以发现答案是沿着对角线以S型的方式走一遍。
所以我们可以分三步走。
第一步得到所有的对角线(从上到下)。
第二步判断对角线是否需要翻(有方向,可能需要改成从下到上)。
第三步将对角线拼接到答案上。
第一步,我们可以以对角线最上面的顶点的行坐标来划分。
分两部分:第一部分行坐标是矩阵的最上边,从左到右。第二部分行坐标是矩阵的最右边,从上到下。
有了最上面顶点的坐标,我们就可以循环斜着扫描,每扫描下一行,列数依次减一,只要没越界,都属于这个对角线上的合法坐标。
![6044104fb06def5cd5d8497d6d290e6e.png](https://i-blog.csdnimg.cn/blog_migrate/f864cd1075bb5c8efcdbe4e286532e70.jpeg)
第二步,可以发现对角线的方向按奇偶性在翻转,所以我们使用一个flag来标记即可。
第三步就是将对角线的数据拼接到答案上,循环拼接即可。
![058bb78bf562e65f1756e0fcf1ca2293.png](https://i-blog.csdnimg.cn/blog_migrate/00b44428a7cf154ea3b635e221b6f8b5.jpeg)
四、螺旋矩阵
给一个N * M的矩阵,按顺时针输出所有元素。
![d8952c2f01b366093b67018ea49235f2.png](https://i-blog.csdnimg.cn/blog_migrate/a79b11660957e0598f1473466b83ab85.jpeg)
上面那道题对角线是两个方向,现在螺旋遍历则是四个方向了。
此时需要考虑的特殊情况更多了。
我们逐一写出所有方向的逻辑也可以,但是很容易漏写。
面对矩阵的这种问题,常用的方法是模板化(状态机)。
具体来说就是将所有操作进行抽象提取,划分为不同的状态,每个状态有自己的模板操作。
模板化后的好处是我们可以直接循环就可以完成遍历矩阵了。
![8a0ab748179c11e1b085255e0cdd70c5.png](https://i-blog.csdnimg.cn/blog_migrate/bc6c14cc6284508ddb9133f1608bccf9.jpeg)
比如上面这个代码。
第一个是将操作按顺时针标号模板化,这样的好处是我们可以通过运算得到下一个操作。
第二个是将上下左右的边界base进行模板化,同时储存一个维护边界的辅助数组baseInc。
第三个是将最关键的矩阵遍历计算规则xyInc模板化。
通过上面三个模板化,我们就可以快速判断当前是不是处理完了(最外层循环)。
也可以快速判断当前方向的遍历是否完成了(内层循环)。
当我们遍历完一个方向后,x,y肯定是越界的,所以需要回滚,并更新边界。
然后转换到下个方向状态,又由于回滚后的坐标之前已经处理,所以需要在新的方向移动一下,到达新的未处理的位置。
就这样,一道很复杂的螺旋遍历矩阵问题,就转化为了两层循环的问题了。
一、背景
这篇文章主要通过几个具体的例子来看怎么使用二维数组。
PS:每次我都会把对应的练习题地址发送到群里,建议想学算法的朋友耐心的做一下这些题。
二、二维数组
二维数组和一维数组一样,也是有一系列元素组成。
但是二维数组的元素排列在矩形网格里,而不是一条直线。
当然,有些语言里面,二维数组是通过一维数组实现的,而在其他一些语言中,则不存在二维数组的概念。
例如在C/C++中,二维数组底层储存为一维数组。
下图显示了大小为 M * N 的数组 A 的实际结构:
![81a019ddf41b9dab5bc4a73181586bf2.png](https://i-blog.csdnimg.cn/blog_migrate/37dc4b3aab2c8531530aee63e15274fd.jpeg)
因此,如果我们将 A 定义为也包含 M * N 个元素的一维数组,那么实际上 A[i][j] 就等于 A[i * N + j]。
而在Java中,二维数组其实就是包含M个元素为一维数组,而每个元素又是一个大小为N的一维数组。
下图显示了 Java 中二维数组 A 的实际结构:
![8717259d40ff6347beb5fabb74229702.png](https://i-blog.csdnimg.cn/blog_migrate/480a0186e55fcceb727ad6a2f7bff171.jpeg)
而对于动态二维数组,则是一维动态数组的元素还是一维动态数组。
具体可以看下面的题吧。
三、对角线遍历
告诉你一个N * M的二维数组,求按对角线遍历这个二维数组,并输出遍历的元素。
遍历规则可以参考下图。
![6afe4974d17e091cfe5ceb29f39bf3ba.png](https://i-blog.csdnimg.cn/blog_migrate/0f3ec69e41e77fd503cd8ce94a7df5ca.jpeg)
其实这道题看着很简单,但是做起来会发现很复杂,需要考虑的情况太多。
一会是对角线向上,一会是对角线向下。
一会是左边越界,一会是右边越界,或者上边越界,或者下边越界。
所以我们需要对这道题进行拆解划分,来减少边界的判断。
简单看下上图,可以发现答案是沿着对角线以S型的方式走一遍。
所以我们可以分三步走。
第一步得到所有的对角线(从上到下)。
第二步判断对角线是否需要翻(有方向,可能需要改成从下到上)。
第三步将对角线拼接到答案上。
第一步,我们可以以对角线最上面的顶点的行坐标来划分。
分两部分:第一部分行坐标是矩阵的最上边,从左到右。第二部分行坐标是矩阵的最右边,从上到下。
有了最上面顶点的坐标,我们就可以循环斜着扫描,每扫描下一行,列数依次减一,只要没越界,都属于这个对角线上的合法坐标。
![4fc0d73dde39223d0fd6ecfc9d021e4e.png](https://i-blog.csdnimg.cn/blog_migrate/dd0367865e56486c9795ded8db89d84f.jpeg)
第二步,可以发现对角线的方向按奇偶性在翻转,所以我们使用一个flag来标记即可。
第三步就是将对角线的数据拼接到答案上,循环拼接即可。
![010519eda0de6dffba927c50a9f73bcb.png](https://i-blog.csdnimg.cn/blog_migrate/d377e01c7661f56ba98afd14de10b08b.jpeg)
四、螺旋矩阵
给一个N * M的矩阵,按顺时针输出所有元素。
![c9d474a599a10b5a7523c2922011cdf4.png](https://i-blog.csdnimg.cn/blog_migrate/9e9993f7d15e0390695724df1a5688d1.jpeg)
上面那道题对角线是两个方向,现在螺旋遍历则是四个方向了。
此时需要考虑的特殊情况更多了。
我们逐一写出所有方向的逻辑也可以,但是很容易漏写。
面对矩阵的这种问题,常用的方法是模板化(状态机)。
具体来说就是将所有操作进行抽象提取,划分为不同的状态,每个状态有自己的模板操作。
模板化后的好处是我们可以直接循环就可以完成遍历矩阵了。
![9e66bdcfaf5610162b42fe4811101e2c.png](https://i-blog.csdnimg.cn/blog_migrate/86c1fcef78ec90e3025c5e5ae4d2a5d8.jpeg)
比如上面这个代码。
第一个是将操作按顺时针标号模板化,这样的好处是我们可以通过运算得到下一个操作。
第二个是将上下左右的边界base进行模板化,同时储存一个维护边界的辅助数组baseInc。
第三个是将最关键的矩阵遍历计算规则xyInc模板化。
通过上面三个模板化,我们就可以快速判断当前是不是处理完了(最外层循环)。
也可以快速判断当前方向的遍历是否完成了(内层循环)。
当我们遍历完一个方向后,x,y肯定是越界的,所以需要回滚,并更新边界。
然后转换到下个方向状态,又由于回滚后的坐标之前已经处理,所以需要在新的方向移动一下,到达新的未处理的位置。
就这样,一道很复杂的螺旋遍历矩阵问题,就转化为了两层循环的问题了。
五、杨辉三角
输入一个数字N,输出高度为N的杨辉三角。
注:杨辉三角参考下图。
![2bff1a4b7b880a8dd5de20367c69164a.gif](https://i-blog.csdnimg.cn/blog_migrate/3e1853f8a1eab370928d3b446fc4a89e.gif)
这道题与前面两道题相比就简单多了。
根据动图中的效果,我们可以发现一个规律:下一行某个位置的值时上一行相同位置和前一个位置值的和。
即f(x, y) = f(x-1, y) + f(x-1, y-1)。
而对于每一行的第一个和最后一个,不满足上面的规律(越界了),但值都是1,特殊处理即可。
于是我们就可以按照这个规律写出答案了。
![c96fb42d8e50f32e8f0db18a4b8569ce.png](https://i-blog.csdnimg.cn/blog_migrate/2d1b54604a2b2fc2f83f20c08e981be8.jpeg)
七、最后
在二维数组里面,对角线遍历和螺旋遍历就有点技术含量了。
当然,对于计算机专业的人来说,那个在学习第一门语言的时候,大家肯定都实现过这样的问题的。
这里主要是提供一种思路,可以较为方便的处理这个问题。
如果你有更好的思路,可以告诉我。
-EOF-