28-文本左右对齐
给定一个单词数组 words 和一个长度 maxWidth ,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本。 你应该使用 “贪心算法” 来放置给定的单词;也就是说,尽可能多地往每行中放置单词。必要时可用空格 ' ' 填充,使得每行恰好有 maxWidth 个字符。 要求尽可能均匀分配单词间的空格数量。如果某一行单词间的空格不能均匀分配,则左侧放置的空格数要多于右侧的空格数。 文本的最后一行应为左对齐,且单词之间不插入额外的空格。 注意: 单词是指由非空格字符组成的字符序列。 每个单词的长度大于 0,小于等于 maxWidth。 输入单词数组 words 至少包含一个单词。
方法一:基本贪心算法实现
function fullJustify(words: string[], maxWidth: number): string[] {let result: string[] = [];let i = 0;while (i < words.length) {let lineWords: string[] = [];let currentLength = 0;// 尽可能多地往当前行添加单词while (i < words.length && currentLength + words[i].length + lineWords.length <= maxWidth) {lineWords.push(words[i]);currentLength += words[i].length;i++;}let spaces = maxWidth - currentLength;let line = "";if (i === words.length || lineWords.length === 1) {// 最后一行或只有一个单词的行,左对齐line = lineWords.join(" ");line += " ".repeat(maxWidth - line.length);} else {// 非最后一行,均匀分配空格let avgSpaces = Math.floor(spaces / (lineWords.length - 1));let extraSpaces = spaces % (lineWords.length - 1);for (let j = 0; j < lineWords.length - 1; j++) {line += lineWords[j];line += " ".repeat(avgSpaces + (j < extraSpaces? 1 : 0));}line += lineWords[lineWords.length - 1];}result.push(line);}return result;
}// 测试示例
let words = ["This", "is", "an", "example", "of", "text", "justification."];
let maxWidth = 16;
let result = fullJustify(words, maxWidth);
console.log(result);
方法二:拆分逻辑实现
function fullJustify(words: string[], maxWidth: number): string[] {let result: string[] = [];let start = 0;while (start < words.length) {let end = start;let lineLength = 0;// 确定当前行能容纳的单词范围while (end < words.length && lineLength + words[end].length + (end - start) <= maxWidth) {lineLength += words[end].length;end++;}let spaces = maxWidth - lineLength;let line = "";if (end === words.length || end - start === 1) {// 最后一行或只有一个单词的行,左对齐for (let i = start; i < end; i++) {if (i > start) {line += " ";spaces--;}line += words[i];}line += " ".repeat(spaces);} else {// 非最后一行,均匀分配空格let avgSpaces = Math.floor(spaces / (end - start - 1));let extraSpaces = spaces % (end - start - 1);for (let i = start; i < end - 1; i++) {line += words[i];line += " ".repeat(avgSpaces + (i - start < extraSpaces? 1 : 0));}line += words[end - 1];}result.push(line);start = end;}return result;
}// 测试示例
let words2 = ["What", "must", "be", "acknowledgment", "shall", "be"];
let maxWidth2 = 16;
let result2 = fullJustify(words2, maxWidth2);
console.log(result2);
方法三:利用辅助函数实现
function createLine(words: string[], start: number, end: number, maxWidth: number, isLastLine: boolean): string {let lineLength = 0;for (let i = start; i < end; i++) {lineLength += words[i].length;}let spaces = maxWidth - lineLength;let line = "";if (isLastLine || end - start === 1) {// 最后一行或只有一个单词的行,左对齐for (let i = start; i < end; i++) {if (i > start) {line += " ";spaces--;}line += words[i];}line += " ".repeat(spaces);} else {// 非最后一行,均匀分配空格let avgSpaces = Math.floor(spaces / (end - start - 1));let extraSpaces = spaces % (end - start - 1);for (let i = start; i < end - 1; i++) {line += words[i];line += " ".repeat(avgSpaces + (i - start < extraSpaces? 1 : 0));}line += words[end - 1];}return line;
}function fullJustify(words: string[], maxWidth: number): string[] {let result: string[] = [];let start = 0;while (start < words.length) {let end = start;let currentLength = 0;// 确定当前行能容纳的单词范围while (end < words.length && currentLength + words[end].length + (end - start) <= maxWidth) {currentLength += words[end].length;end++;}let isLastLine = end === words.length;let line = createLine(words, start, end, maxWidth, isLastLine);result.push(line);start = end;}return result;
}// 测试示例
let words3 = ["Science", "is", "what", "we", "understand", "well", "enough", "to", "explain", "to", "a", "computer.", "Art", "is", "everything", "else", "we", "do"];
let maxWidth3 = 20;
let result3 = fullJustify(words3, maxWidth3);
console.log(result3);
复杂度分析
- 时间复杂度:三种方法的时间复杂度均为 (O(n)),其中 n 是单词数组
words
中所有字符的总数。因为每个单词只会被处理一次。 - 空间复杂度:三种方法的空间复杂度均为 (O(m)),其中 m 是结果数组的长度,主要用于存储排版后的每行文本。
这些方法的核心思路都是贪心算法,尽可能多地往每行中放置单词,然后根据不同情况(最后一行或非最后一行)来分配空格。不同方法只是在代码结构和实现细节上有所差异。
方法四:动态规划
动态规划的核心在于将大问题拆解为小问题,通过保存子问题的解来避免重复计算。对于这个单词排版问题,我们可以定义状态并找出状态转移方程。
function fullJustify(words: string[], maxWidth: number): string[] {const n = words.length;// cost[i][j] 表示从第 i 个单词到第 j 个单词放在一行的代价const cost: number[][] = new Array(n).fill(0).map(() => new Array(n).fill(Number.MAX_SAFE_INTEGER));// 计算每行放置不同单词组合的代价for (let i = 0; i < n; i++) {let length = 0;for (let j = i; j < n; j++) {if (i === j) {length = words[j].length;} else {length += words[j].length + 1;}if (length <= maxWidth) {cost[i][j] = Math.pow(maxWidth - length, 2);}}}// dp[i] 表示从第 i 个单词开始排版的最小代价const dp: number[] = new Array(n + 1).fill(Number.MAX_SAFE_INTEGER);// 用于记录每个位置的最优分割点const path: number[] = new Array(n + 1).fill(0);dp[n] = 0;// 动态规划计算最小代价和最优分割点for (let i = n - 1; i >= 0; i--) {for (let j = i; j < n; j++) {if (cost[i][j]!== Number.MAX_SAFE_INTEGER) {if (dp[i] > cost[i][j] + dp[j + 1]) {dp[i] = cost[i][j] + dp[j + 1];path[i] = j + 1;}}}}const result: string[] = [];let start = 0;// 根据最优分割点构建排版结果while (start < n) {const end = path[start];const lineWords = words.slice(start, end);let line = "";if (end === n) {// 最后一行左对齐line = lineWords.join(" ");line += " ".repeat(maxWidth - line.length);} else {const spaces = maxWidth - lineWords.reduce((acc, word) => acc + word.length, 0);if (lineWords.length === 1) {line = lineWords[0] + " ".repeat(spaces);} else {const avgSpaces = Math.floor(spaces / (lineWords.length - 1));const extraSpaces = spaces % (lineWords.length - 1);for (let i = 0; i < lineWords.length - 1; i++) {line += lineWords[i];line += " ".repeat(avgSpaces + (i < extraSpaces? 1 : 0));}line += lineWords[lineWords.length - 1];}}result.push(line);start = end;}return result;
}// 测试示例
const words = ["This", "is", "an", "example", "of", "text", "justification."];
const maxWidth = 16;
console.log(fullJustify(words, maxWidth));
方法五:递归回溯
递归回溯是一种通过尝试所有可能的组合来找到最优解的方法。对于这个问题,我们可以递归地尝试将单词放入不同的行,直到找到满足条件的排版方式。
function fullJustify(words: string[], maxWidth: number): string[] {const result: string[] = [];function backtrack(index: number): void {if (index === words.length) {return;}let lineWords: string[] = [];let currentLength = 0;// 尽可能多地往当前行添加单词while (index < words.length && currentLength + words[index].length + lineWords.length <= maxWidth) {lineWords.push(words[index]);currentLength += words[index].length;index++;}let line = "";if (index === words.length) {// 最后一行左对齐line = lineWords.join(" ");line += " ".repeat(maxWidth - line.length);} else {const spaces = maxWidth - currentLength;if (lineWords.length === 1) {line = lineWords[0] + " ".repeat(spaces);} else {const avgSpaces = Math.floor(spaces / (lineWords.length - 1));const extraSpaces = spaces % (lineWords.length - 1);for (let i = 0; i < lineWords.length - 1; i++) {line += lineWords[i];line += " ".repeat(avgSpaces + (i < extraSpaces? 1 : 0));}line += lineWords[lineWords.length - 1];}}result.push(line);backtrack(index);}backtrack(0);return result;
}// 测试示例
const words2 = ["What", "must", "be", "acknowledgment", "shall", "be"];
const maxWidth2 = 16;
console.log(fullJustify(words2, maxWidth2));
复杂度分析
动态规划方法
- 时间复杂度:(O(n^2)),其中 n 是单词的数量。主要开销在于填充
cost
数组和进行动态规划计算。 - 空间复杂度:(O(n^2)),主要用于存储
cost
数组和dp
数组。
递归回溯方法
- 时间复杂度:(O(2^n)),因为在最坏情况下,每个单词都有两种选择:放入当前行或放入下一行。
- 空间复杂度:(O(n)),主要是递归栈的空间开销。
相关文章:

28-文本左右对齐
给定一个单词数组 words 和一个长度 maxWidth ,重新排版单词,使其成为每行恰好有 maxWidth 个字符,且左右两端对齐的文本。 你应该使用 “贪心算法” 来放置给定的单词;也就是说,尽可能多地往每行中放置单词。必要时可…...

建筑兔零基础自学python记录39|实战词云可视化项目——章节分布10(上)
这次我们来制作《红楼梦》各章节的分布情况: 源代码: import pandas as pd import numpy as np import matplotlib.pyplot as pltdf_hlm pd.read_csv("hlm.txt", names["hlm_texts"]).dropna()df_hlm df_hlm[~df_hlm.hlm_texts.s…...

Impacket工具中的横向渗透利器及其使用场景对比详解
在渗透测试中,横向移动(Lateral Movement)是指攻击者在获得一个系统的控制权限后,通过网络进一步渗透到其他系统的过程。Impacket 是一款强大的渗透测试工具集,提供了多种实现横向渗透的脚本,常见的工具包括…...

基于java,SpringBoot和Vue的医院药房药品管理系统设计
摘要 随着医疗行业信息化的快速发展,高效、精准的医院药房药品管理对于提升医疗服务质量和医院运营效率至关重要。本文基于 Java 语言,采用 SpringBoot 框架和 Vue 框架进行医院药房药品管理系统的设计与研究。该系统以 SpringBoot 作为后端开发框架&am…...

MQ保证消息的顺序性
在消息队列(MQ)中保证消息的顺序性是一个常见的需求,尤其是在需要严格按顺序处理业务逻辑的场景(例如:订单创建 → 支付 → 发货)。 一、消息顺序性被破坏的原因 生产者异步/并行发送:消息可能…...

cmake、CMakeLists.txt、make、ninja
文章目录 一、概念0.cmake官网1.什么是cmake2.为什么使用cmake3.CMakeLists.txt 二、CMakeLists.txt语法:如何编写CMakeLists.txt,语法详解(0)语法基本原则(1)project关键字(2)set关键字(3)message关键字(4)add_executable关键字(5)add_subdirectory关键…...

数据结构与算法 计算机组成 八股
文章目录 数据结构与算法数组与链表的区别堆的操作红黑树定义及其原理 计算机组成int和uint的表示原码反码补码移码的定义?为什么用补码? 数据结构与算法 数组与链表的区别 堆的操作 红黑树定义及其原理 计算机组成 int和uint的表示 原码反码补码移…...

RoboBrain:从抽象到具体的机器人操作统一大脑模型
25年2月来自北大、北京智源、中科院自动化所等的论文“RoboBrain: A Unified Brain Model for Robotic Manipulation from Abstract to Concrete”。 目前的多模态大语言模型(MLLM) 缺少三项必备的机器人大脑能力:规划能力,将复杂…...

算法 之 前缀和 与 滑动窗口 与 背包问题 的差异(子数组之和为k问题)
文章目录 使用前缀和哈希表560.和为K的子数组525.连续数组2588.统计美丽子数组数目 子数组的定义是原来的数组当中连续的非空的序列,而我们的背包问题的选与不选的情况,对应的是这个非连续的情况,那么这种情况就要注意当然啦,对于线性的时间内…...

微电网协调控制器ACCU-100 分布式光伏 光储充一本化
安科瑞 华楠 18706163979 应用范围: 分布式光伏、微型风力发电、工商业储能、光储充一体化电站、微电网等领域。 主要功能: 数据采集:支持串口、以太网等多通道实时运行,满足各类风电与光伏逆变器、储能等 设备接入ÿ…...

IDEA入门及常用快捷键
IDEA是java常用的IDE。当run一个.java文件时,其实是经历了先编译为.class,再运行的过程。 在project文件夹中,out文件夹存储编译的.class文件,src文件夹存储.java代码文件。 设置自动导包 快捷键: 格式化快捷键&…...

electron打包结构了解
Electron 应用打包后的文件结构和内容取决于你使用的打包工具(如 electron-builder、electron-packager 等)以及目标操作系统(Windows、macOS、Linux)。以下是典型 Electron 应用打包后的文件结构和关键组成部分: 1. 基…...

03.06 QT
一、使用QSlider设计一个进度条,并让其通过线程自己动起来 程序代码: <1> Widget.h: #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QThread> #include "mythread.h"QT_BEGIN_NAMESPACE namespace Ui {…...

Python中的常用库
一、collections collections是 Python 标准库中的一个模块,提供了一些专门的容器数据类型,能够帮助你更高效地处理常见的数据结构操作。 1、Counter Counter 是一个字典的子类,用于计数可哈希对象。它会统计对象的出现次数,并…...

马尔科夫不等式和切比雪夫不等式
前言 本文隶属于专栏《机器学习数学通关指南》,该专栏为笔者原创,引用请注明来源,不足和错误之处请在评论区帮忙指出,谢谢! 本专栏目录结构和参考文献请见《机器学习数学通关指南》 正文 统计概率的利剑:掌…...

护照阅读器在汽车客运站流程中的应用
在汽车客运站的日常运营里,如何高效服务旅客、保障出行安全是工作重点。护照阅读器作为精准身份识别的得力工具,在客运站的多个关键流程,如自助购票、柜台购票、安检以及行李托运中,发挥着不可小觑的作用,有力地提升了…...

CentOS 7 安装Nginx-1.26.3
无论安装啥工具、首先认准了就是官网。Nginx Nginx官网下载安装包 Windows下载: http://nginx.org/download/nginx-1.26.3.zipLinxu下载 wget http://nginx.org/download/nginx-1.26.3.tar.gzLinux安装Nginx-1.26.3 安装之前先安装Nginx依赖包、自行选择 yum -y i…...

Unity 使用NGUI制作无限滑动列表
原理: 复用几个子物体,通过子物体的循环移动实现,如下图 在第一个子物体滑动到超出一定数值时,使其放到最下方 --------------------------------------------------------------》 然后不停的循环往复,向下滑动也是这…...

linux中断调用流程(arm)
文章目录 ARM架构下Linux中断处理全流程解析:从硬件触发到驱动调用 ⚡**一、中断触发与硬件层响应** 🔌**1. 设备触发中断** 📡 **二、CPU阶段:异常入口与上下文处理** 🖥️**1. 异常模式切换** 🔄**2. 跳转…...

基于Matlab的多目标粒子群优化
在复杂系统的设计、决策与优化问题中,常常需要同时兼顾多个相互冲突的目标,多目标粒子群优化(MOPSO)算法应运而生,作为群体智能优化算法家族中的重要成员,它为解决此类棘手难题提供了高效且富有创新性的解决…...

【网络安全】——协议逆向与频繁序列提取:从流量中解码未知协议
目录 引言 一、为什么要结合频繁序列提取? 二、四步融合分析法 步骤1:原始流量采集与预处理 步骤2:多粒度序列模式挖掘 层1:单包内字节级频繁项 层2:跨数据包的行为序列 步骤3:关键字段定位与结构假…...

CSS 中等比例缩放的演变:从传统技巧到 aspect-ratio 属性
CSS 中等比例缩放的演变:从传统技巧到 aspect-ratio 属性 在响应式网页设计和多设备兼容成为主流的今天,如何实现元素的等比例缩放成为前端开发中一个重要的课题。无论是图片、视频还是其他容器,都常常需要保持固定的宽高比,以便…...

系统架构设计师—计算机基础篇—进度管理
文章目录 基本概念进程的特征进程的状态前趋图 进程的通信进程的互斥做题方法 进程的同步PV操作做题方法 基本概念 进程的特征 进程通常由程序、数据集合、进程控制块PCB组成。 PCB是一种数据结构,是进程存在的唯一标识。 组织方式说明线性方式把所有PCB组织在一…...

初始提示词(Prompting)
理解LLM架构 在自然语言处理领域,LLM(Large Memory Language Model,大型记忆语言模型)架构代表了最前沿的技术。它结合了存储和检索外部知识的能力以及大规模语言模型的强大实力。 LLM架构由外部记忆模块、注意力机制和语…...

Ollama+AnythingLLM安装
一、文件准备 1. 安装包获取 从联网设备下载: AnythingLLMDesktopInstaller.exe(官网离线安装包) deepseek-r1-1.5b.gguf(1.5B 参数模型文件) 2. 传输介质 使用 U 盘或移动硬盘拷贝以下文件至离线设…...

docker拉取失败
备份原始配置文件 sudo cp /etc/docker/daemon.json /etc/docker/daemon.json.bak 清理或修复 daemon.json 文件 sudo nano /etc/docker/daemon.json 删除 文件中的所有内容,确保文件为空。 cv下面这个文件内容 { "registry-mirrors": [ &…...

PHP之Cookie和Session
在你有别的编程语言的基础下,你想学习PHP,可能要了解的一些关于cookie和session的信息。 Cookie 参数信息 setcookie(name,value,expire, path, domain); name : Cookie的名称。 value : Cookie的值。 expire : Cookie的过期时间,可以是一…...

【万字长文】基于大模型的数据合成(增强)及标注
写在前面 由于合成数据目前是一个热门的研究方向,越来越多的研究者开始通过大模型合成数据来丰富训练集,为了能够从一个系统的角度去理解这个方向和目前的研究方法便写了这篇播客,希望能对这个领域感兴趣的同学有帮助! 欢迎点赞&…...

CES Asia 2025增设未来办公教育板块,科技变革再掀高潮
作为亚洲消费电子领域一年一度的行业盛会,CES Asia 2025(第七届亚洲消费电子技术贸易展)即将盛大启幕。今年展会规模再度升级,预计将吸引超过500家全球展商参展,专业观众人数有望突破10万。除了聚焦人工智能、物联网、…...

Python详细安装教程——Python及PyCharm超详细安装教程:新手小白也能轻松搞定!(最新版)
Python作为一门简单易学、功能强大的编程语言,近年来在数据分析、人工智能、Web开发等领域广受欢迎。而PyCharm作为一款专业的Python集成开发环境(IDE),提供了强大的代码编辑、调试和项目管理功能,是Python开发者的得力…...