• 算法—6、Z字形变换


    题目

      将一个给定字符串s根据给定的行数numRows,以从上往下、从左到右进行Z字形排列.
      比如输入字符串为PAYPALISHIRING,行数为3时,排列如下:

    P		A		H		N
    A	P	L	S	I	I
    Y		I		R		G
    
    • 1
    • 2
    • 3

    之后,输入需要从左往右逐行读取,产生一个新的字符串,比如PAHNAPLSIIGYIR
      请实现这个将字符串进行指定行数变换的函数:

    string convert(string s,int numRows);
    
    • 1

    示例1

    输入: s = "PAYPALISHIRING",numRows = 3
    输出: ”PAHNAPLSIIGYIR“
    
    • 1
    • 2

    示例2

    输入: s = "PAYPALISHIRING", numRows = 4
    输出:”PINALSIGYAHRPI“
    解释:
    P			I			N
    A		L	S		I	G
    Y	A		H	R
    P			I
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    示例3

    输入: s = "A",numRows = 1
    输出: ”A“
    
    • 1
    • 2

    思路

    方法一:利用二维矩阵模拟
      设n为字符串s的长度,r = numRows。对于r=1(只有一行)的情况,答案与s相同,可以直接返回s。对于其余情况,考虑创建一个二维矩阵,然后在矩阵上俺Z字形填写字符串s,最后逐行扫描矩阵中的非空字符,组成答案、
      根据题意,当在矩阵上填写字符时,会向下填写r个字符,然后向右上继续填写r-2个字符,最后回到第一行,因此Z字形变换的周期t=r+r-2 =2r-2,每个周期会占用矩阵上的1+r-2 = r-1列。因此有[n/t]个周期(最后一个周期视作完整周期),乘上每个周期的列数,得到矩阵的列数c=[n/t]*(r-1);
      创建一个r行c列的矩阵,然后遍历字符串s并按Z型填写。具体来说,设当前填写的位置为(x,y),即矩阵的x行y列。初始(x,y)=(0,0),即矩阵左上角。若当前字符下标i满足 i mod t < r -1,则向下移动,否则向右上移动。
      填写完成后,逐行扫描矩阵中的非空字符,组成答案。

    class Solution {
        public String convert(String s, int numRows) {
            int n = s.length(), r = numRows;
            if (r == 1 || r >= n) {
                return s;
            }
            int t = r * 2 - 2;
            int c = (n + t - 1) / t * (r - 1);
            char[][] mat = new char[r][c];
            for (int i = 0, x = 0, y = 0; i < n; ++i) {
                mat[x][y] = s.charAt(i);
                if (i % t < r - 1) {
                    ++x; // 向下移动
                } else {
                    --x;
                    ++y; // 向右上移动
                }
            }
            StringBuffer ans = new StringBuffer();
            for (char[] row : mat) {
                for (char ch : row) {
                    if (ch != 0) {
                        ans.append(ch);
                    }
                }
            }
            return ans.toString();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    复杂度分析

    • 时间复杂度: O(r*n),其中r = numRows,n为字符串s的长度。时间主要消耗在矩阵的创建和遍历上,矩阵的行数为r,列数可以视为O(n)
    • 空间复杂度:O(rn).矩阵需要O(rn)的空间。
      方法二:压缩矩阵空间
        方法一中的矩阵有大量的空间没有被使用,能否优化呢?
        注意到每次往矩阵的某一行添加字符时,都会添加该行上一个字符的右侧,且最后组成答案时只会用到每行的非空字符。因此可以将矩阵的每行初始化为一个空列表,每次向某一行添加字符时,添加到该行的列表末尾即可。
    class Solution {
        public String convert(String s, int numRows) {
            int n = s.length(), r = numRows;
            if (r == 1 || r >= n) {
                return s;
            }
            StringBuffer[] mat = new StringBuffer[r];
            for (int i = 0; i < r; ++i) {
                mat[i] = new StringBuffer();
            }
            for (int i = 0, x = 0, t = r * 2 - 2; i < n; ++i) {
                mat[x].append(s.charAt(i));
                if (i % t < r - 1) {
                    ++x;
                } else {
                    --x;
                }
            }
            StringBuffer ans = new StringBuffer();
            for (StringBuffer row : mat) {
                ans.append(row);
            }
            return ans.toString();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    复杂度分析

    • 时间复杂度: O(n)
    • 空间复杂度:O(n).压缩后的矩阵需要O(n)的空间。

    方法三:直接构造
      方法一中矩阵的每个非空字符都会对应到s的哪个下标(记作idx),从而直接构造出答案。
      由于Z字形变换的周期为t=2r-2,因此对于矩阵第一行的非空字符,其对应的idx均为t的倍数,及idx=0(mod t);同理,对于矩阵第一行的非空字符,应满足idx=r-1(mot t).
      对于矩阵的其余行(行号设为i),每个周期内有两个字符,第一个字符满足idx=i(mod t),第二个字符满足idx = t-i(mod t).

    func convert(s string, numRows int) string {
        n, r := len(s), numRows
        if r == 1 || r >= n {
            return s
        }
        t := r*2 - 2
        ans := make([]byte, 0, n)
        for i := 0; i < r; i++ { // 枚举矩阵的行
            for j := 0; j+i < n; j += t { // 枚举每个周期的起始下标
                ans = append(ans, s[j+i]) // 当前周期的第一个字符
                if 0 < i && i < r-1 && j+t-i < n {
                    ans = append(ans, s[j+t-i]) // 当前周期的第二个字符
                }
            }
        }
        return string(ans)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    复杂度分析

    • 时间复杂度: O(n),其中n为字符串s的长度。s中的每个字符仅会被访问一次,因此时间复杂度为O(n).
    • 空间复杂度: O(1).返回值不计入空间复杂度。

    练习代码

    public class Test_7 {
        public static String convert(String s, int numRows) {
            int n = s.length(),r=numRows;
            //只有一行或者行数等于个数时,直接返回
            if (r==1||r>=n){
                return s;
            }
            //Z字形周期
            int t = r*2-2;
            //列数 n/t*(r-1)
            int c = (n+t-1)/t*(r-1);
            char[][] mat = new char[r][c];
            for (int i = 0,x=0,y=0; i < n; ++i) {
                mat[x][y] = s.charAt(i);
                if (i%t<r-1){
                    ++x;
                }else {
                    --x;
                    ++y;
                }
            }
            StringBuffer ans = new StringBuffer();
            for (char[] row:mat){
                for (char ch:row){
                    if (ch!=0){
                        ans.append(ch);
                    }
                }
            }
            return ans.toString();
        }
    
        public static void main(String[] args) {
            System.out.println(convert("PAYPALISHIRING",3));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
  • 相关阅读:
    using的应用
    学习 C++ 编程,怎么才能找到合适的练手项目?
    动态规划总结
    9.3 链表从指定节点插入新节点
    铝合金钻孔铣削去毛刺加工之高速电主轴解决方案
    TRC神经科学丨艾美捷TRC成瘾研究领域
    中级java面试问题大全及答案大全
    虚拟机部署linux网络连接配置
    数据库与数据仓库关联和区别
    500-1000人的科技企业怎么做10多套业务系统和员工的统一认证管理?
  • 原文地址:https://blog.csdn.net/qq_32530561/article/details/125635599