leet code 第六题 Z字形变换

具体题目就不详细说了,大致意思如下:

为了说明这个题目我们用STR表示这个给定的字符串,用NUM表示这个传入的数字,同时就用原题的测试例子来表示

STR = “PAYPALISHIRING”

NUM = 3

首先这个题目并不是很难,一般而言我们可以通过模拟这个例子中的过程,来实现我们的算法,大致就两步

1. 按照顺序画出这个Z字形

2. 按照从左到右的顺序从Z字形中读出我们想要的字符串

那么怎么画出这个Z字形呢?一种比较简单的做法是初始化一个二维数组,然后用某个字符来填充这个二维数组,表示空位,接下来只要按照某个规律把STR填进去就好了,这时候不难发现这个填充方式是由周期性的,先是向下移动,再是向右上方向移动,而且这个移动的距离(字符个数)也是固定的,这样一来我们就可以很轻松的完成这个二维数组了。

下面详细说说这个过程

首先我们先确定一下这个周期cycle = 2 * NUM - 2,这个应该很容易发现

接下来对于STR中的每个字符假设处在第len个长度,我们可以计算这个字符在周期中所处的位置:

pos = len % cycle

我们假设上一个字符存放的位置为(row, col),row表示行,col表示列

那么可以知道当前的字符应该存放在哪个位置

当pos < row && pos != 0, 则这个字符应该存放在(row + 1, col)的位置

当pos >=row && pos == 0,则这个字符应该存放在(row - 1, col)的位置

如此一来就能画出这个Z字形了。

下面是主要的代码:

char **
mat_init(int row, int col) {
        char **mat = (char **)malloc(row * sizeof(char *));
        int i, j;
        for (i = 0; i < row; ++i) {
                *(mat + i) = (char *)malloc(col * sizeof(char));
                for (j = 0; j < col; ++j) {
                       *(*(mat + i) + j) = '*';
                }
        }
        return mat;
}

void
mat_free(char **mat, int row) {
        int i;
        for (i = 0; i < row; ++i) {
                free(*(mat + i));
        }
        free(mat);
}

void
show_mat(char **mat, int row, int col) {
        int i, j;
        for (i = 0; i < row; ++i) {
                for (j = 0; j < col; ++j) {
                        printf("%c ", mat[i][j]);
                }
                printf("\n");
        }
}

char*
convert_way1(char* s, int numRows) {
        if (NULL == s || numRows < 1) {
                return "";
        }
        if (1 == numRows) {
                return s;
        }
        int s_len = strlen(s);

        char *result = (char *)malloc((s_len + 1) * sizeof(char));
        int cycle = 2 * numRows - 2;
        int num_col = ((s_len / cycle ) + 1) * (numRows - 1);
        //初始化二维数组,'*'表示空白位置
        char **mat = mat_init(numRows, num_col);

        show_mat(mat, numRows, num_col);
        int i, pos, j;
        //初始化标记的起点
        int cur_row = 1;
        int cur_col = -1;
        for (i = 0; i < s_len; ++i) {
                pos = i % cycle;
                if (pos != 0 && pos < numRows) {
                        cur_row++;
                } else {
                        cur_col++;
                        cur_row--;
                }
                mat[cur_row][cur_col] = s[i];
        }
        show_mat(mat, numRows, num_col);
        pos = 0;
        //根据已经画好的Z字形,读出我们需要的字符串
        for (i = 0; i < numRows; ++i) {
                for (j = 0; j < num_col; ++j) {
                        if (mat[i][j] == '*') {
                                continue;
                        }

                        *(result + pos) = mat[i][j];
                        pos++;
                        if (pos == s_len) {
                                break;
                        }
                }
        }

        result[s_len] = '\0';
        mat_free(mat, numRows);
        return result;
}

简单的结果展示:

这个算法其实并不复杂,相反从实现逻辑上来看,它很简单,但问题是不管是内存还是执行时间 都让人不是很满意

那么有没有更快速的算法呢?实际也是有的,我们真的需要老老实实的通过画出的Z字形才能确定字符串的顺序吗?答案是否定的,实际上当我们确定了NUM,那么STR中的第x个字符所在的位置y(新字符串的第y个位置)也是确定的,所以我们仅仅是需要需找一个映射,把映射到y,那么就可以完成这道题目了,那么如何确定这个映射关系呢?当然了我们要寻找的映射实际是函数

y = f(x)的反函数,我们希望能通过y来确定x的位置。

还记得我们上面提到的这个STR其实满足一定的周期性,那么我先一个满周期的STR为例子经行说明

这个例子中的STR刚刚好为两个周期

cycle = 2 * NUM - 2 = 2 * 5 - 2 = 8

我们先来看看第一行,第一行有两个字符在STR中的位置分别为0 以及 (0 + 1 * cycle)

再来看看最后一行,同样是两个字符,分别位于4 以及 (4 + 1 * cycle)

那么中间3行呢?他们有4个字符,怎么处理呢?这里要注意的是,虽然有4个字符,但是它们也只是2个周期。比如这本例中,我们先看看第三行,Y与R分别位于第2以及(2 + 1 * cycle),I的位置可以通过Y来确定,我们假设他们之间的间隔为dis,实际上A与R的间隔同样也是dis,同理其他行数也可以这么处理

这里总结一下,对首行和尾行的,我们只需确定一个基础值base,然后往上加n个周期就可以,这里只需要确定周期数即可

而对于中间的几行而言,仅仅是一个基础值和周期还是不够,我们需要确定2n以及2n + 1的字符之间的间隔space,

现在我们假设cur_line表示我们要计算的行数,它以0开始(要注意起始值)

那么我们可以得到(可以拿真实数据自己算算哦)

base = cur_line

space = cycle - 2 * cur_line

到了这一步,我们实际上已经基本掌握了这个所谓的映射,下一步我们要确定周期,每一行的周期

之前我们假设了STR是完整的周期长度,如果是这样显然不论是那一行周期数=cycle就可以了,但是真实情况是STR不一定是完整的周期,甚至对于中间的那几行甚至可能存在半周期。所以我们需要用一个标记位来表示,中间的 那几行是否是半周期,如果是的话,我们还需要单独的打出某个字符(正常周期的,需要打印两个字符)。

考虑这种情况

实际上除第一行外,第二,三行都是3个周期,但是第二行却输出了7个字符,这和我们前面讨论的有些不同?这里需要用到我们的标记位了,当标记位记为1的时候,我们就要在多打一个字符

需要考虑几种情况:

最后一个字符所在的位置pos>=NUM 以及pos<NUM的情况,通过考虑这两种情况我们生成两个数组times_array(记录每一行的周期数)以及flag_array(记录每一行的标记位)

这样子这道题基本的解题思路已经给出,下面贴一下代码

void set_array(int *array, int start, int end, int value) {
        int i;
        for (i = start; i < end; ++i) {
                array[i] = value;
        }
}
//初始化每一行对应的周期以及标记数
void get_array_message(int *flag_array, int *times_array, int str_len, int pos, int cycle, int row) {
        set_array(flag_array, 0, row, 0);
        set_array(times_array, 0, row, cycle);
        printf("row %d pos %d", row, pos);
        if (pos != 0) {
                set_array(times_array, 0, 1, cycle + 1);
                if (pos >= row - 1) {
                        set_array(times_array, 2 * (row - 1) - (pos - 1), row, cycle + 1);
                        set_array(flag_array, 0, 2 * (row - 1) - (pos - 1), 1);
                } else {
                        set_array(times_array, 1, row, cycle);
                        set_array(flag_array, 1, pos, 1);
                }
        }

}
//首行以及尾行的调用方法
void single_set(char *result, char *s, int *pos, int base, int cycle, int cycle_num) {
        int i;
        for (i = 0; i < cycle_num; ++i) {
                result[*pos] = s[base + cycle * i];
                (*pos) ++;
        }
}
//中间行的调用方法
void double_set(char *result, char *s, int *pos, int base, int cycle, int space, int flag, int cycle_num) {
        int i;
        for (i = 0; i < cycle_num; ++i) {
                result[*pos] = s[base + cycle * i];
                (*pos) ++;

                result[*pos] = s[base + cycle * i + space];
                (*pos) ++;
        }
        if (flag == 1) {
                result[*pos] = s[base + cycle * i];
                (*pos) ++;
        }
}

char*
convert_way2(char* s, int numRows) {
        int s_len = strlen(s);
        int cycle = 2 * numRows - 2;
        char *result = (char *)malloc((s_len + 1) * sizeof(char));
        if (NULL == result) {
                printf("malloc fail\n");
                return NULL;
        }

        int pos, cur_line, i, *times_array, *flag_array;
        int cycle_num = s_len / cycle;
        pos = cur_line = 0;
        times_array = (int *)malloc(numRows * sizeof(int));
        flag_array = (int *)malloc(numRows * sizeof(int));
        get_array_message(flag_array, times_array, s_len, s_len % cycle, cycle_num, numRows);

        while (1) {
                if (cur_line == 0 || cur_line == numRows - 1) {
                        single_set(result, s, &pos, cur_line, cycle, times_array[cur_line]);
                } else {
                        int space = cycle - cur_line * 2 ;
                        double_set(result, s, &pos, cur_line, cycle, space, flag_array[cur_line], times_array[cur_line]);             
                }
                cur_line ++;
                if (cur_line >= numRows) {
                        break;
                }
        }
        result[s_len] = '\0';
        return result;
}

 

使用了这个算法,再来看看时间以及内存的消耗

对比之前的算法有了极大的提升,当然了,这道题目可能还有很多更优的解法,这等我后续有时间在考虑考虑,最近会在leet_code上刷刷题,会把一些题目记录下来,也欢迎大神们交流交流想法。

ps: 之前在提交这部分代码上去的时候,报了内存检查的错误,实际上是因为没有加result[s_len] = ''\0"这一行,虽然在自己的编译环境下测试通过,但是在线上提交的时候,还是要注意这种细节!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值