每天一种算法(一) --模拟算法
什么是程序?程序的本质就是解决问题,而解决问题的核心就是算法。对于程序员来说,算法能力可以真实反应你的逻辑思维与开发潜力。当然,算法能力不是与生俱来的,它是天才与积累的结合,算法能力的提升需要我们不断地进行练习、思考与总结。从今天开始,我会带着大家一起来学习算法,来领会编程的艺术。
Z字形变换
首先,我们来看一道很有意思的算法题目:
将一个给定字符串 s 根据给定的行数 numRows ,以从上往下、从左到右进行 Z 字形排列。
比如输入字符串为 "PAYPALISHIRING" 行数为 3 时,排列如下:
P A H N
A P L S I I G
Y I R
之后,你的输出需要从左往右逐行读取,产生出一个新的字符串,比如:"PAHNAPLSIIGYIR"。
请你实现这个将字符串进行指定行数变换的函数:
string convert(string s, int numRows);
大家先思考一下,如果是自己来解这道题,会如何来做?
思考结束,我们来分析一下这道题目。
这道题目是非常典型的模拟算法题,介绍内容给到我们:输入、转变的条件、期待的结果,甚至连解题的思路都给我们提供了。不信?我们来看看:
- 输入是一个字符串和一个行数,从上往下排列。
- 到了尾行时进行翻转,从左到右斜向上排列,到了顶行时再次翻转,再从上往下排列。
- 重复过程2,到了字符串尾部时,结束翻转。
算法实现如下:
var convert = function(s, numRows) {
// 如果只有一列,直接输出即可
if(numRows === 1) return s;
// 计算出翻转的规律
const roundNum = 2 * (numRows - 1);
// nowRow标明当前行,flag标明向上移动还是向下移动
let nowRow = 0, flag = true;
// 声明一个长度为行数的字符串数组,用于存储每一行的字符
const fianlArr = new Array(numRows).fill('');
for(let i = 0; i<s.length; i++) {
// 当到首行时,翻转flag,从上往下排列
if(i % roundNum === 0 ) {
flag = true;
}
// 当到尾行时,翻转flag,从下往上排列
if(i % roundNum === (numRows - 1)) {
flag = false;
}
// 把对应的行的内容填充到行数组中
fianlArr[nowRow] += s[i];
// 行数根据flag进行上移或下移
nowRow = flag ? (nowRow + 1) : (nowRow - 1);
}
// 将行数组中的字符串拼接后返回
return fianlArr.join('');
}
上述算法大家可能会有疑惑的地方应该在于const roundNum = 2 * (numRows - 1);这一行,我们来解释一下:
排除一行的情况,我们把剩余行数的顶点位置来列出来:
| 行数 | 首行 | 尾行 | 首行 | 尾行 | ... |
|---|---|---|---|---|---|
| 2 | 0 | 1 | 2 | 3 | ... |
| 3 | 0 | 2 | 4 | 6 | ... |
| 4 | 0 | 3 | 6 | 9 | ... |
| 5 | 0 | 4 | 8 | 12 | ... |
我们会发现,每个首行和首行、尾行和尾行的下标的差就是我们计算出来的const roundNum = 2 * (numRows - 1),当下标位置可以整除roundNum时,证明当前在首行,翻转flag,从上往下排列;当下标位置除以roundNum的余数为numRows - 1时,证明当前在尾行,翻转flag,从下往上排列。
回形打印二维数组
我们再来看一道类似的题目:
输入两个数字 行数和列数,回形打印出一个二维数组,大小按回形的方向依次递增。
如: 输入: 5 、4
输出:
0 1 2 3
13 14 15 4
12 19 16 5
11 18 17 6
10 9 8 7
如: 输入: 3,2
输出:
0 1
5 2
4 3
在这道题目中,我们采用和Z字形变换相似的解题方式来分析:
-
输入两个数字:m、n,分别对应行数和列数
我们可以通过m和n的乘积得到一共要处理的次数,生成一个 m x n 的 二维数组来存放最终结果。
-
从0开始
从左往右填充一个二维数组 -
当到了最后一列时,更改填充方向为
从上往下 -
到达最后一行时,更改填充方向为
从右往左 -
到达第一列时,更改填充方向为
从下往上 -
当到达第二行时,我们发现第一行已经有被填充过了(我们通过arr[x][y] !== undefined来判断数据是否被填充过),这时我们更改填充方向为
从左往右 -
不断重复3~6的步骤,直到执行了
m x n次后,处理结束。 我们会发现:经过m x n的处理,我们完美地模拟了题目描述的回形打印二维数组。
总结
眼过千遍不如手过一遍,回形打印二维数组这道题目留给各位,希望大家在评论区积极留言,分享自己的题解或是问题。我们也会在下一次的文章中将作者的题解分享处理。
我是何以庆余年,如果文章对你起到了帮助,希望可以点个赞,谢谢!
如有问题,欢迎在留言区一起讨论。