中级农民
- 积分
- 116
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2018-4-21
- 最后登录
- 1970-1-1
|
68. Text Justification
这道题非常恶心,和上一道题一样恶心,没有任何算法技巧,单纯地考验编程。实际面试中,这种题根本不可能写对。
不过,我的做法,我认为比其他方法都更好更直观一些。因为我运行的结果是最快,且代码行数远远少于其他大部分答案。
说说我的解法。
首先,我们要解决第一个问题,是每一行都有哪些单词。这个问题,其实是一个滑动窗口问题,我们定义左右两个指针left right ,每次移动一次右指针right,然后判断窗口里的单词拼起来会不会超过最大长度。
这个判断就是第一个难点,单词之间必然有一个空格,所以若是窗口里面有n个单词,这n个单词一共有m个字母,那么它们之间至少有n -1个空格。所以此时,窗口内,最后拼出来的字符串,起码的长度有 n - 1 + m 个字符长。所以我们判断条件是,若是string[right]也加入窗口了,然后n - 1 + m + words[right]> maxWidth了,那么说明此时right指针的位置,就是一个换行的地方。也就是区间[left, right - 1]里面的单词,就是一行。
然后我们考虑如何更新左右指针,可以发现,右指针是可以无脑一步一步向右移动的,所以这意味着我们可以用一个for循环,来更新右指针。而左指针更新也很简单,只要我们发现换行了,那么我们就把left指到当前righ的位置。伪代码就是:
m = 0;(m表示窗口里面有多少个字符)
n = 0;(n表示窗口里有多少个单词);
for(int right = 0; right < words.length; right++) {
if(n - 1 + m + words[i]> maxWidth) {
String row = helper(left, right - 1);//helper函数用于拼接出符合要求的一行。我们拼接[left, right-1]这几个单词
left = right;
m = words[right];
n = 1;
}else{
m = m + words[right]
n = n + 1;
}
}
这样,我们就可以找出每一行需要处理的单词了,然后用helper函数单独处理每一行。注意此时,当right走到终点结束之后,left并没有走到终点,这是因为我们需要特殊处理最后一行。因为最后一行我们不需要扩展空格了,直接拼接起来即可,所以我们把最后一行的处理加入刚才的代码中
m = 0;(m表示窗口里面有多少个字符)
n = 0;(n表示窗口里有多少个单词);
for(int right = 0; right < words.length; right++) {
if(n - 1 + m + words[i]> maxWidth) {
String row = helper(left, right - 1);//helper函数用于拼接出符合要求的一行。我们拼接[left, right-1]这几个单词
left = right;
m = words[right];
n = 1;
}else{
m = m + words[right]
n = n + 1;
}
}
//处理最后一行
StringBuilder sb = new StringBuilder();
for(int i = left; i < words.length; i++){
sb.append(words[i]);
if(i < words.length - 1) sb.append(" ");
}
while(sb.length() < maxWidth) sb.append(" ");
至此,我们的如何找出每一行的单词,并且处理最后一行,这个主逻辑就完成了。接下来,我们要处理如何正确地写出helper,也就是如何拼接单词,处理空格。
----------------------
这个题第二个难度就在于,怎么样去处理空格的问题,它给的说法非常复杂,说要尽量平均,若是无法平均,尽量左边多于右边。
这个描述,我认为是出题者为了增加难度,故意说得很模糊。很多答案都用平均数,然后再计算mod等等。
我后来仔细想了想,突然发现这个说辞简直就是坑爹,为了增加难度而增加难度而已。它的意思其实非常简单。就是数手指游戏。
首先,我们知道每一行的长度,一定是maxWidth, 并且我们知道一共有n个单词,这n个单词,一共有m个字符。
那么我们马上可以知道,这一行一共有 maxWidth - m 个空格。并且这些空格,要按照要求填在n - 1个坑里。
比如举个例子example of text
这个例子中,我们一共有n = 3个单词, 这3个单词,一共有13个字符,它的maxWidth = 16。也就是要求一行有16个字符,所以这一行一共有16 -13 = 3个空格,要填在3 - 1 = 2个坑里。
填坑的方法其实非常简单,我们初始化一个数组,长度为坑的长度。每一个数组,表示每一个坑我们要填多少空格,顺序就是左到右。比如这个例子中,我们就建一个数组[0,0]。
然后我们从左到右,每一位加1 ,并且记录我们加了多少次1了,若是到末尾了,但是加的1的次数没有到3个空格,我们就从头再来。直到我们填1的次数等于这一行需要的空格数:
[0, 0] -> [1,0] -> [1,1] -> [2, 1];
那么此时,这个数组,就表示我们每一个坑有多少个空格。就这么简单,无需计算什么余数,平均数之类的。非常无脑。
那么我们计算出每一个坑需要填的空格数之后,这个题所有的难点就都解决完了。
接下来就拼字符串好了,先填单词,再填坑,再填单词,再填坑……
注意这里有一个特殊情况,就是这一行只有一个单词的时候,此时坑的数量依然是1,我们在初始化填坑数组的时候,不能无脑定义成int[right - left]。因为right 可能等于left。也就是一行有2个单词,只有一个坑,一行有一个单词,也有一个坑。所以数组的长度len = right - left == 0? 1 : right - left;
这样,这道题就做完了,接下来只要拼字符就可以了,没有任何难度。那么我们的代码就是- class Solution {
- public List<String> fullJustify(String[] words, int maxWidth) {
- List<String> rs = new ArrayList<>();
-
- int rowLen = 0;
- int left = 0;
- for(int i = 0; i < words.length; i++) {
- if(rowLen + (i - left) + words[i].length() > maxWidth) {
- rs.add(helper(left, i - 1, words, maxWidth, rowLen));
- left = i;
- rowLen = words[i].length();
- }else{
- rowLen += words[i].length();
- }
- }
-
- //last row
- StringBuilder sb = new StringBuilder();
- for(int i = left; i < words.length; i++){
- sb.append(words[i]);
- if(i < words.length - 1) sb.append(" ");
- }
-
- while(sb.length() < maxWidth) sb.append(" ");
- rs.add(sb.toString());
- return rs;
- }
-
- private String helper(int left, int right, String[] words, int max, int w_len) {
- int spacelen = right - left == 0 ? 1 : right - left;
- int[] space = new int[spacelen];
- int totalspace = max - w_len;
- int idx = 0;
- while(totalspace > 0) {
- space[idx]++;
- idx = idx == space.length - 1 ? 0: idx + 1;
- totalspace--;
- }
-
- idx = 0;
- StringBuilder sb = new StringBuilder();
- for(int i = left; i <= right; i++) {
- sb.append(words[i]);
- int len = sb.length();
- if(idx < space.length){
- while(sb.length() < len + space[idx]) sb.append(" ");
- idx++;
- }
- }
-
- return sb.toString();
-
- }
- }
复制代码 |
|