【前端也得会算法】6. Z 字形变换 [ 中等 ]

151 阅读1分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

一、题目描述:

将一个给定字符串 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:
输入: s = "PAYPALISHIRING", numRows = 3
输出: "PAHNAPLSIIGYIR"

示例 2:
输入:s = "PAYPALISHIRING", numRows = 4
输出:"PINALSIGYAHRPI"
解释:
P     I    N
A   L S  I G
Y A   H R
P     I

示例 3:
输入: s = "A", numRows = 1
输出: "A"

提示:

  • 1 <= s.length <= 1000
  • s 由英文字母(小写和大写)、',' 和 '.' 组成
  • 1 <= numRows <= 1000

二、题解:

方法一 纵向数组法

  • 原理。根据题意遍历字符串纵向排列出结果数组,再纵向遍历得出字符串即可。
  • 思路。
    • 遍历字符串
    • 对每一个纵向进行生成数组
    • 尾指针为0时,直接放入当前字符
    • 尾指不针为0时,将当前字符替换数组序号为尾指针的那个,且尾指针--,跳出当前循环
    • 每遍历numRows一次,尾指针重新赋值
    • 在纵向遍历求出字符串即可

image.png

代码:

var convert = function(s, numRows) {
    let res = ''
    let arr = []
    let lastLenth = 0
    let index = 0
    // 先遍历请求调整后的arr数组
    while(index < s.length){
        let newArr = new Array(numRows).fill('')
        for(let i = 0 ;i<numRows;i++){
            let item = s[index]?s[index]:''
            index++
            if(lastLenth === 0){
                newArr[i] = item
            }else {
            // 非正规情况 尾指针减一请跳出当前循环
                newArr[lastLenth] = item
                lastLenth--
                break
            }
            // 如果遍历完一次 对尾指针重赋值
            if(i === numRows-1){
                lastLenth = numRows>1?numRows-2:numRows-1
            }
        }
        arr.push(newArr)
    }
    
    // 再纵向遍历求出字符串
    for(let i = 0 ;i<numRows;i++){
        for(let item of arr){
            res+= item[i]?item[i]:''
        }
    }
    return res
};

image.png

方法二 横向数组法

  • 原理。根据题意遍历字符串横向排列出结果数组,再纵向遍历得出字符串即可。
  • 思路。
    • 遍历字符串
    • 对每一个横向进行生成数组
    • 尾指针为0时,resArr[i][count]放入当前字符
    • 尾指不针为0时,resArr[lastLenth][count],且尾指针--,count++,跳出当前循环
    • 每遍历numRows一次,尾指针重新赋值,count++
    • 在纵向遍历求出字符串即可

代码:

var convert = function(s, numRows) {
    let res = ''
    let lastLenth = 0
    let index = 0
    let resArr = new Array(numRows).fill(0).map(item => [])
    let count = 0
    while(index < s.length){
        for(let i = 0 ;i<numRows;i++){
            let item = s[index]?s[index]:''
            index++
            if(lastLenth === 0){
                resArr[i][count] = item
            }else {
                resArr[lastLenth][count] = item
                lastLenth--
                count++
                break
            }
            if(i === numRows-1){
                lastLenth = numRows>1?numRows-2:numRows-1
                count++
            }
            
        }
    }
    for(let i = 0 ;i<resArr.length;i++){
        res+= resArr[i].join('')
    } 
    return res
};

image.png

二维数组的方式感觉略微慢了点,想到直接使用字符串替换第二维呢?

优化后代码:

var convert = function(s, numRows) {
    if(numRows === 1) return s
    let lastLenth = 0
    let index = 0
    let resArr = new Array(numRows).fill('')
    while(index < s.length){
        for(let i = 0 ;i<numRows;i++){
            let item = s[index]?s[index]:''
            index++
            // 这里不是赋值,而是+=
            if(lastLenth === 0){
                resArr[i] += item
            }else {
                resArr[lastLenth]+= item
                lastLenth--
                break
            }
            if(i === numRows-1){
                lastLenth = numRows-2
            }
        }
    }
    return resArr.join('')
};

image.png

方案比较

  • 纵向数组法 时间复杂度 O(n2)?。
  • 横向数组法 时间复杂度 O(n)。

三、总结

  • 此题可以纵向数组法横向数组法两种方案
  • 纵向数组法主要是根据题意遍历字符串纵向排列出结果数组,再纵向遍历得出字符串即可。
  • 横向数组法主要是根据题意遍历字符串横向排列出结果数组,再纵向遍历得出字符串即可。

文中如有错误,欢迎在评论区指正