题目:(68)、文本左右对齐来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/text-justification
给定一个单词数组和一个长度 maxWidth,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本。
你应该使用“贪心算法”来放置给定的单词;也就是说,尽可能多地往每行中放置单词。必要时可用空格 ' ' 填充,使得每行恰好有 maxWidth 个字符。
要求尽可能均匀分配单词间的空格数量。如果某一行单词间的空格不能均匀分配,则左侧放置的空格数要多于右侧的空格数。文本的最后一行应为左对齐,且单词之间不插入额外的空格。
说明:单词是指由非空格字符组成的字符序列。每个单词的长度大于 0,小于等于 maxWidth。
输入单词数组 words 至少包含一个单词。
示例:
输入:
words = ["This", "is", "an", "example", "of", "text", "justification."]
maxWidth = 16
输出:
[
"This is an",
"example of text",
"justification. "
]
示例 2:
输入:
words = ["What","must","be","acknowledgment","shall","be"]
maxWidth = 16
输出:
[
"What must be",
"acknowledgment ",
"shall be "
]
解释: 注意最后一行的格式应为 "shall be " 而不是 "shall be",
因为最后一行应为左对齐,而不是左右两端对齐。
第二行同样为左对齐,这是因为这行只包含一个单词
代码实现
class Solution {
public List<String> fullJustify(String[] words, int maxWidth) {
int[] len = new int[words.length];
//用数组len记录单词长度集合,方便后面做判断是否录入
for(int i = 0;i < words.length;i++){
len[i] = words[i].length();
}
List<String> res = new ArrayList<>();
int l = 0;
int r = 0; //左右两个指针,用来判断即将录入的单词索引集
while(r < len.length){
//定义一个leng作为长度集合,l为开始的索引,r结束(不包括r)
int leng = len[l];
while(leng <= maxWidth && r < len.length - 1){
r++;
leng = len[l];
for(int i = l + 1;i <= r;i++){
leng += (len[i] + 1);
}
}
if(r == len.length - 1 && l == len.length - 1){
leng = len[r];
r++;
}
else if(r == len.length - 1 && leng < maxWidth + 1){
r++;
}
//索引未到结束时,也就是正常情况下
if(r != len.length){
int emptylen = maxWidth + len[r] - leng + 1;
//建立一个temp作为下一个加入res的单词集
StringBuffer temp = new StringBuffer();
//首先如果没有多余的空格,就不用多做考虑,向temp里加单词即可
if(emptylen == 0){
for(int i = l;i < r - 1;i++){
temp.append(words[i]);
temp.append(" ");
}
temp.append(words[r - 1]);
res.add(temp.toString());
}
//因为一个单词和两个单词时具有特殊性,我把他们单独列出来
//首先是大于两个单词时,我们先得到一个多余空格除以(r - l - 1)的除数num,再得到余数,由于题目要求前面的空格数大于后面的空格数,我们再每个间隔中间都加num个空格,再在前l -> l+remainder 之间的单词之后,将多余的空格一个间隔分一个即可
else if(r - l > 2){
if(emptylen%(r - l - 1) == 0){
int nums = emptylen/(r - l - 1);
StringBuffer empty = new StringBuffer();
for(int i = 0;i < nums;i++){
empty.append(" ");
}
for(int i = l;i < r - 1;i++){
temp.append(words[i]);
temp.append(" ");
temp.append(empty);
}
temp.append(words[r - 1]);
res.add(temp.toString());
}
else{
int nums = emptylen/(r - l - 1);
StringBuffer empty = new StringBuffer();
for(int i = 0;i < nums;i++){
empty.append(" ");
}
int remainder = emptylen%(r - l - 1);
for(int i = l;i < l + remainder;i++){
temp.append(words[i]);
temp.append(empty);
temp.append(" ");
temp.append(" ");
}
for(int i = l + remainder;i < r - 1;i++){
temp.append(words[i]);
temp.append(" ");
temp.append(empty);
}
temp.append(words[r - 1]);
res.add(temp.toString());
}
}
else if(r - l == 1){
StringBuffer empty = new StringBuffer();
for(int i = 0;i < emptylen;i++){
empty.append(" ");
}
temp.append(words[l]);
temp.append(empty);
res.add(temp.toString());
}
else if(r - l == 2){
StringBuffer empty = new StringBuffer();
for(int i = 0;i < emptylen;i++){
empty.append(" ");
}
temp.append(words[l]);
temp.append(" ");
temp.append(empty);
temp.append(words[l + 1]);
res.add(temp.toString());
}
l = r;
}
//这是到了最后的情况
else{
int emptylen = maxWidth - leng;
StringBuffer temp = new StringBuffer();
StringBuffer empty = new StringBuffer();
for(int i = l;i < len.length - 1;i++){
temp.append(words[i]);
temp.append(" ");
}
temp.append(words[len.length - 1]);
for(int i = 0;i < emptylen;i++){
empty.append(" ");
}
temp.append(empty);
res.add(temp.toString());
r++;
}
}
return res;
}
}
思路整理
有一点思路很简单,但是难的是把所有有思路都整理出来。首先说说我踩过的几个坑。
首先第一点,默认每行的单词中间都默认至少有一个空格的,这是排版。
第二点,要求如果某一行单词间的空格不能均匀分配,则左侧放置的空格数要多于右侧的空格数。我首先理解的就是多出的空格数除以单词间隔数后均匀分派到每个间隔,多出来的余数放到第一个间隔就好了,但是事实上他还强调了一点,要求尽可能均匀分配单词间的空格数量。所以实际上余数的空格要一个个的放到左边的间隔,直至放完。
然后说思路。
首先题目跟长度有关,我首先用了一个len数组来记录单词的长度,方便计算(其实不用数组的话也可以,直接用words[i].length()),然后遍历这个数组。
对于这个数组的遍历,大致思路是从len[l]开始一直到len[r],加和大于maxWidth时退出,根据贪心算法,l到r - 1是可以填入得最多的单词了。(当然数组遍历到最后时会有不同,我们到最后在讨论)。
我们按照程序的顺序来梳理吧,首先是单词数大于二时,我已经补过了注释。首先是emptylen,大家看遍历数组时leng最后实际上多了一个len[r]和一个空格的长度,所以empty = maxWidith - leng + len[r] + 1;然后间隔数为r - l -1;我们先把emptylen/(r - l - 1),平均分配给每个间隔,然后是余数remainder,由于要求尽量均匀分配,所以我们在前remainder个间隔里加一个空格,把空格的余数做一个分配。
这是大于两个单词时,至于两个和一个时是非常好解决的,分别把emptylen个空格放到中间和最后就好了。
至于数组遍历到最后时。只能说一句,败笔!!!还是做得少,由于我把r++放到了最前面,问题复杂了不是一点半点,事实上只要把i++放到后面,这种冗余代码完全没必要,败笔不解释了。