每日算法之ZigZag 转换问题

783 阅读5分钟

The ZigZag Conversion Problem ZigZag 转换问题

(leetcode.com/problems/zi…).
今天的算法是 ZigZag 转换问题。给你一个字符串和一些行。这个想法是,给定的字符串以锯齿形模式写入,并且该函数应该返回该字符串在逐行读取时的读取结果。

我认为这个问题的写法特别令人困惑,所以让我们看一个例子。

如果给定的字符串是“ALGORITHMOFTHEDAY”,并且行数是 4,则它看起来像这样:

A      T      H
L   I  H   T  E
G  R   M  F   D  Y
O      O      A

逐行读取,您会得到字符串“ATHLIHTEGRMFDYOOA”,这将是该函数的输出。

我认为这是一种分解示例可以帮助您找到解决方案的算法。因此,我将首先通过一个示例并考虑如何解决该问题,然后我将讨论代码。

解决锯齿形调整浪问题

假设给定字符串“ABCDEFGH”,锯齿形的行数为 3。写下来,看起来像这样:

String is set to equal "ABCDEFGH" and number of rows equals 3. Beneath that is a box with three rows. In the first row is "AE", second row is "BDFH", and third row is "CG". When taken as a whole, it looks like a zig-zag.

去掉所有字母,我们有三行,可以将其视为三个数组。

Box with three empty rows

现在,为了构建这个锯齿形单词,我们可以在给定的字符串中逐个字母。从前三个字母“ABC”开始,我们可以将它们放在三行(或数组)的开头。一旦我们到达底行,我们就知道我们无法在该方向添加更多字母,因此我们必须开始反转方向。

Box with three rows. At the start of the first row is "A", at the start of the second row is "B", at the start of the third row is "C". Beneath "C" is a blue arrow that's pointing up, signifying the direction we'll be going.

我们将在这个相反的方向上添加“D”和“E”,但是一旦我们到达第一行,我们再次知道我们不能继续这个方向。

Box with three rows. First row is "A [space] E", second row is "B D", third row is "C". After "E" there is a red arrow pointing down, signifying what direction it'll be going next.

我们可以继续做同样的事情,沿一个方向添加字母,直到到达最后一行,然后反转方向,直到添加了字符串的所有字母。

Two boxes. The first box has three rows. The first row is "A [space] "E", the second row is "B D F", the third row is "C [space] G". Under "G" is a blue arrow pointing up, signifying the next direction to go. The second box is very similar, but now in the second row there is an "H" at the end.

去掉数组的行(本质上是将这些数组转换为字符串),我们得到三个字符串。

First string is "AE", then a plus sign next to the second string, "BDFH", then a plus sign next to the third string, "CG". Beneath all of this is the final string, "AEBDFHCG".

将它们逐行相加,我们得到结果:“AEBDFHCG”。

这个例子展示了我将如何解决这个问题:为给定的行构建相同数量的数组,将给定字符串的字母添加到每个数组,直到到达最后一个数组,然后反转方向。一旦我们到达第一个数组,再次反转方向。继续这样做,直到我们用完输入字符串中的字母为止。最后,将各个数组的字母连接起来形成字符串,然后将这些字符串连接起来形成一个最终字符串。

Coding the ZigZag Problem 编写 ZigZag 问题的代码

现在我们已经完成了一个示例,我们可以继续编写解决方案。在该问题中,我们给出了字符串 s 和一些行 numRows 。要做的第一件事是考虑基本情况:如果只有一行,那么甚至不可能出现锯齿形,因此我们可以返回字符串。另一种基本情况是,如果字符串比给定的行数短,在这种情况下,锯齿形也不可能出现,因此我们可以再次返回字符串。

function convert(s, numRows) {
  if (numRows === 1 || s.length < numRows) {
    return s;
  }
  //...
}

现在我们需要构建一些变量。首先是一个存储其他数组 rows 的数组。 rows 中的每个数组将存储一行之字形图案。我们还需要为当前所在行构建一个计数器 currentRow ,该计数器从 0 开始。我们需要一个等于布尔值的变量,该变量表示我们是否正在切换方向 < b3>。最后,我们需要创建一个最后返回的空字符串 result 。

function convert(s, numRows) {
  if (numRows === 1 || s.length < numRows) {
    return s;
  }
  let rows = [];
  let currentRow = 0;
  let reverse = false;
  let result = "";

  //...
}

我们现在想要构建 numRows 中给出的行数。为此,我们可以创建一个 for 循环,从 0 到 numRows ,并每次构建一个新的空白数组。

function convert(s, numRows) {
  if (numRows === 1 || s.length < numRows) {
    return s;
  }
  let rows = [];
  let currentRow = 0;
  let reverse = false;
  let result = "";

  for (let i = 0; i < numRows; i++) {
    rows[i] = [];
  }

  //...
}

现在,我们要遍历“s”中的每个字符,并将其推送到不同的行,直到完成每个字母。因此,这是使用 for 循环的好地方,从第一个字母(在索引 0 处)开始,直到最后一个字母(在 s.length 处)。

在 for 循环内,我们希望将此字母 ( s[i] ) 推送到基于 currentRow 的行。如果我们向下走, currentRow 就会变大;如果我们反转方向, currentRow 就会变小——所以我们应该在这里有一个条件语句。如果 reverse 为 true,则 currentRow 应该变小;否则, currentRow 应该变大。

考虑一下之前的示例, reverse 最初是 false ,因此 currentRow 计数继续变大。一旦我们到达底行, reverse 就被设置为等于 true ,此时 currentRow 计数继续变小。

On the left is a green column titled "currentRow", with the numbers 0, 1, and 2 beneath it. To the right is a three-row box. The first row is "A [space] "E", the second row is "B D", the third row is "C". Above the first column is "reverse: false" written in blue. From "A" to "B" to "C" are two blue arrows, with "+1" on each arrow. Beneath the first column is "reverse: true". Then there are red arrows from "C" to "D" to "E", with "-1" next to each of them.

因此,在 for 循环中,我们可以检查 reverse 是 true 还是 false。如果为 false,那么我们可以增加 currentRow 。否则,我们可以递减 currentRow 。

function convert(s, numRows) {
  if (numRows === 1 || s.length < numRows) {
    return s;
  }
  let rows = [];
  let currentRow = 0;
  let reverse = false;
  let result = "";

  for (let i = 0; i < numRows; i++) {
    rows[i] = [];
  }

  for (let i = 0; i < s.length; i++) {
    rows[currentRow].push(s[i]);
    if (reverse === false) {
      currentRow++;
    } else {
      currentRow--;
    }

    //...
  }

  //...
}

我们在 for 循环中要做的最后一件事是检查我们是在最后一行还是在第一行。在这两种情况下,我们都希望朝着与刚才相反的方向前进,因此我们可以将 reverse 设置为等于 !reverse 。

function convert(s, numRows) {
  if (numRows === 1 || s.length < numRows) {
    return s;
  }
  let rows = [];
  let currentRow = 0;
  let reverse = false;
  let result = "";

  for (let i = 0; i < numRows; i++) {
    rows[i] = [];
  }

  for (let i = 0; i < s.length; i++) {
    rows[currentRow].push(s[i]);
    if (reverse === false) {
      currentRow++;
    } else {
      currentRow--;
    }

    if (currentRow === numRows - 1 || currentRow === 0) {
      reverse = !reverse;
    }
  }

  //...
}

一旦 for 循环执行完毕,我们将得到多个数组。我们希望将每个数组转换为字符串,然后将这些字符串相互添加。

为此,我们可以在 rows 中的每一行调用 .forEach() 。对于每一行,我们可以使用 .join() 将其转换为字符串。然后我们可以将每个字符串添加到 result 中。最后,在 forEach 方法之外,我们可以返回结果。

function convert(s, numRows) {
  if (numRows === 1 || s.length < numRows) {
    return s;
  }
  let rows = [];
  let currentRow = 0;
  let reverse = false;
  let result = "";

  for (let i = 0; i < numRows; i++) {
    rows[i] = [];
  }

  for (let i = 0; i < s.length; i++) {
    rows[currentRow].push(s[i]);
    if (reverse === false) {
      currentRow++;
    } else {
      currentRow--;
    }

    if (currentRow === numRows - 1 || currentRow === 0) {
      reverse = !reverse;
    }
  }

  rows.forEach((row) => {
    result += row.join("");
  });

  return result;
}

如果您对如何解决此问题有任何疑问或其他想法,请在评论中告诉我!