每天一种算法(一) --模拟算法

3,017 阅读4分钟

每天一种算法(一) --模拟算法

什么是程序?程序的本质就是解决问题,而解决问题的核心就是算法。对于程序员来说,算法能力可以真实反应你的逻辑思维与开发潜力。当然,算法能力不是与生俱来的,它是天才与积累的结合,算法能力的提升需要我们不断地进行练习、思考与总结。从今天开始,我会带着大家一起来学习算法,来领会编程的艺术。

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);

大家先思考一下,如果是自己来解这道题,会如何来做?


思考结束,我们来分析一下这道题目。

这道题目是非常典型的模拟算法题,介绍内容给到我们:输入、转变的条件、期待的结果,甚至连解题的思路都给我们提供了。不信?我们来看看:

  1. 输入是一个字符串和一个行数,从上往下排列。
  2. 到了尾行时进行翻转,从左到右斜向上排列,到了顶行时再次翻转,再从上往下排列。
  3. 重复过程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);这一行,我们来解释一下:

排除一行的情况,我们把剩余行数的顶点位置来列出来:

行数首行尾行首行尾行...
20123...
30246...
40369...
504812...

我们会发现,每个首行和首行、尾行和尾行的下标的差就是我们计算出来的const roundNum = 2 * (numRows - 1),当下标位置可以整除roundNum时,证明当前在首行,翻转flag,从上往下排列;当下标位置除以roundNum的余数为numRows - 1时,证明当前在尾行,翻转flag,从下往上排列。

回形打印二维数组

我们再来看一道类似的题目:

输入两个数字 行数和列数,回形打印出一个二维数组,大小按回形的方向依次递增。
如:  输入:  54
输出: 
0   1   2   3 
13  14  15  4
12  19  16  5
11  18  17  6
10  9   8   7

如:  输入: 32
输出:
0  1
5  2
4  3

在这道题目中,我们采用和Z字形变换相似的解题方式来分析:

  1. 输入两个数字:m、n,分别对应行数和列数

    我们可以通过m和n的乘积得到一共要处理的次数,生成一个 m x n 的 二维数组来存放最终结果。

  2. 从0开始从左往右填充一个二维数组

  3. 当到了最后一列时,更改填充方向为从上往下

  4. 到达最后一行时,更改填充方向为从右往左

  5. 到达第一列时,更改填充方向为从下往上

  6. 当到达第二行时,我们发现第一行已经有被填充过了(我们通过arr[x][y] !== undefined来判断数据是否被填充过),这时我们更改填充方向为从左往右

  7. 不断重复3~6的步骤,直到执行了m x n次后,处理结束。 我们会发现:经过m x n的处理,我们完美地模拟了题目描述的回形打印二维数组。

总结

眼过千遍不如手过一遍,回形打印二维数组这道题目留给各位,希望大家在评论区积极留言,分享自己的题解或是问题。我们也会在下一次的文章中将作者的题解分享处理。

我是何以庆余年,如果文章对你起到了帮助,希望可以点个赞,谢谢!

如有问题,欢迎在留言区一起讨论。