Hi 你们好,我是张小猪。欢迎来到『宝宝也能看懂』系列之 leetcode 周赛题解。git
这里是第 172 期的第 2 题,也是题目列表中的第 1324 题 -- 『竖直打印单词』github
给你一个字符串 s
。请你按照单词在 s
中的出现顺序将它们所有竖直返回。
单词应该以字符串列表的形式返回,必要时用空格补位,但输出尾部的空格须要删除(不容许尾随空格)。
每一个单词只能放在一列上,每一列中也只能有一个单词。shell
示例 1:segmentfault
输入:s = "HOW ARE YOU" 输出:["HAY","ORO","WEU"] 解释:每一个单词都应该竖直打印。 "HAY" "ORO" "WEU"
示例 2:数组
输入:s = "TO BE OR NOT TO BE" 输出:["TBONTB","OEROOE"," T"] 解释:题目容许使用空格补位,但不容许输出末尾出现空格。 "TBONTB" "OEROOE" " T"
示例 3:优化
输入:s = "CONTEST IS COMING" 输出:["CIC","OSO","N M","T I","E N","S G","T"]
提示:spa
1 <= s.length <= 200
s
仅含大写英文字母。MEDIUMcode
题目会给定一个包含多个单词的字符串,其中每一个单词都是由英文大写字母组成,而且它们之间必定是由一个空格分隔开来。不过返回的结果直接看起来会怪怪的,由于是把每一个单词从上到下纵向排列,而后各个单词从左到右组合在一块儿造成的一个字符串数组。也就是返回的数组中,第一个字符串是每一个单词的第一个字母按照单词从左到右出现的顺序组成的,第二个字符串是每一个单词的第二个字母,以此类推。blog
这里须要特别注意的一点是,因为每一个单词的长度不同,因此返回的字符串可能会有补位空格,不过并不保留多余的后缀空格。这一点应该就是这道题最容易弄错的地方啦。关于空格的问题,能够具体看看下面这个例子,应该就能明白:leetcode
那么咱们接下来看看思路吧。首先从结果来看,从上面的例子咱们能够看出,需求的这个字符串数组是能够直接由每一个单词的第一个字母、第二个字母...这样子直接拼接起来的。而原始字符串中每一个单词的分隔符固定是一个空格。因此很是容易能想到,咱们能够先把原始字符串拆分红单词组,而后遍历拼接便可。
思路大致成型以后,须要注意一下其中的细节,也就是特殊空格的处理。
因为后面的单词可能会比前面的长,因此对于前面的单词,咱们是有可能须要去拼接补位空格的。例如上面图中例子里的那个 P R
,这里就须要补充空格。
那是否意味着咱们能够直接对于空缺的内容用空格来填充呢?固然并非的。由于当后面没有较长的单词的时候,咱们这时候若是再去拼接空格就会变成了空格后缀。而按照题目要求,这个类型的空格是须要去掉的。例如上面图中例子里的那个 Y
。
这个问题的处理相信小伙伴也很容易能想到,小猪这里就不作过多分析啦。也就是咱们能够把可能的空格先记录下来,可是并不作拼接。在后续有须要的时候再做为补位空格拼接进去便可。基于这个思路,具体流程以下:
根据最长的单词长度,遍历每一个单词中对应的字符:
基于这个流程,咱们能够实现相似这样的代码:
const printVertically = s => { const words = s.split(' '); let maxLen = 0; for (let i = 0; i < words.length; ++i) { if (words[i].length > maxLen) maxLen = words[i].length; } const ret = new Array(maxLen); for (let i = 0; i < maxLen; ++i) { ret[i] = ''; let prevSpace = ''; for (let j = 0; j < words.length; ++j) { words[j][i] === undefined ? (prevSpace += ' ') : ((ret[i] += prevSpace + words[j][i]), (prevSpace = '')); } } return ret; };
上面的流程里,咱们会发现,其实存在着蛮屡次遍历的,而且咱们还须要去拆分原始字符串为单词组。对于这一部分,咱们是否有办法能够优化呢?
回看刚才的思路和代码,咱们的作法是去取每一个单词的第 n 个字符,而后直接组成结果里的第 n 个字符串。若是把结果数组想象成是一个由字符组成的二维数组的话,这也就至关于咱们是一行一行的拼接结果的。因此这也就须要咱们先去拆出每一个单词,并求得最长的单词的长度。说到这里,有的小伙伴可能会想到,除了一行一行的拼接以外,咱们是否能够一列一列的拼接呢?
咱们来看看,若是是一列一列的拼接的话,那么从第一列开始日后,其实不就是咱们的每一个单词原本的顺序么,也就是咱们原始字符串原本的顺序。那若是这样拼接的话,就根本不须要去拆分单词组,也不须要去根据各个单词的字符来依次遍历了。咱们直接从头开始遍历原始字符串不就行了么。妙鸭。
不过这里仍是须要注意特殊空格的问题。因为咱们是直接遍历原始字符串,因此在推动的过程当中,咱们并不知道是否应该去填补空格。例如对于前面图中的那个例子,对于 "YEAR" 中的 "R",咱们会直接把它和 "P" 拼在一块儿获得 "PR",而不是 "P R"。那么咱们如何解决这个问题呢?
仔细看上面 "R" 字母的例子,咱们为何须要的是 "P R",是由于中间的那个单词这里是空缺的。换句话说就是,第一个单词这里有字母,第二个单词这里空缺,而第三个单词这里也有字母。再换句话说就是,对于第三个单词这里的字母,它指望的是前面已经有了两个字母,也就是字符串的长度为 2。因此当前面字符串的长度不足 2 的时候,咱们须要用空格去填充。说到这里相信小伙伴们都明白了吧,咱们只须要根据当前字母所属的是第几个单词,结合前面字符串的长度,便可完成对于补位空格的补充了。
基于这个思路,咱们须要加入一个变量,用于记录当前遍历到第几个单词。具体流程以下:
遍历整个原始字符串:
基于这个流程,咱们能够实现相似这样的代码:
const printVertically = s => { const ret = []; for (let i = 0, idx = 0, word = 0; i < s.length; ++i, ++idx) { const char = s[i]; if (char === ' ') { ++word; idx = -1; continue; } if (ret[idx] === undefined) { ret[idx] = ' '.repeat(word) + char; continue; } ret[idx].length !== word && (ret[idx] += ' '.repeat(word - ret[idx].length)); ret[idx] += char; } return ret; };
这段代码以 36ms 暂时 beats 100%。
这是一道思路并不复杂的题,特别是其中直接方案的思路应该是能够根据题目要求瓜熟蒂落获得的。但是最后咱们却发现,直接方案其实绕了一圈。而这些发现,正是来自于咱们对于一些例子和结果对应关系的观察和思考。因此有时候真的不妨写一些可能的结果出来康康,而后咱们就会发现其实它超勇的,由于答案就在里面了。>.<
因此,小伙伴们要不要来给小猪康康鸭,小猪超勇的,哈哈哈哈嗝。