描述
牛客最近来了一个新员工Fish,每天早晨总是会拿着一本英文杂志,写些句子在本子上。同事Cat对Fish写的内容颇感兴趣,有一天他向Fish借来翻看,但却读不懂它的意思。例如,“nowcoder. a am I”。后来才意识到,这家伙原来把句子单词的顺序翻转了,正确的句子应该是“I am a nowcoder.”。Cat对一一的翻转这些单词顺序可不在行,你能帮助他么?
数据范围:1≤n≤100 1≤n≤100
进阶:空间复杂度 O(n) O(n) ,时间复杂度 O(n) O(n) ,保证没有只包含空格的字符串
示例
输入:"nowcoder. a am I"
返回值:"I am a nowcoder."
题目解析
题目要求是解决一个字符串单词顺序颠倒的问题。例如,给定一个字符串 "nowcoder. a am I"
,需要将其反转为 "I am a nowcoder."
。这里的关键是理解字符串中的单词顺序被完全颠倒了,但单词内部的字母顺序没有变化。
解题思路
首先,我们需要将字符串按空格分割成单词数组,然后反转这个单词数组,使单词顺序恢复正常。如果单词后面紧跟着标点符号,需要将标点符号与单词一起反转,最后,将反转后的单词数组重新拼接成一个字符串。
根据这个需求,我们可以第一时间想到利用栈后进先出的特性来反转单词顺序,使用栈来存储单词。
使用栈
object Solution {
fun ReverseSentence(ReverseSentence: String): String {
val words = ReverseSentence.split(" ") // 分割字符串为单词列表
val stack = arrayListOf<String>() // 创建一个模拟栈的列表
// 将单词压入栈中(列表开头)
words.forEach { word ->
stack.add(0, word) // 在列表开头添加元素
}
// 从栈中弹出单词并拼接结果
val builder = StringBuilder()
while (stack.isNotEmpty()) {
builder.append(stack.removeAt(0)) // 移除并获取列表第一个元素
if (stack.isNotEmpty()) {
builder.append(" ") // 添加空格,如果还有单词需要拼接
}
}
return builder.toString()
}
}
这种方法首先将单词压入栈中(使用列表实现),然后依次弹出单词并拼接成字符串。压栈和出栈操作都是线性时间的,但因为每个单词都要单独处理,所以总体时间复杂度是O(n),符合题目要求。方法需要额外的存储空间来模拟栈结构,空间复杂度为O(n),符合题目要求。
如果我们希望在空间效率上更进一步,应考虑使用不需要额外的存储空间,所有的操作都在原始字符数组上进行的双指针方法。
双指针方法
使用两个索引变量(指针)遍历字符串,一个从左到右,另一个从右到左,用于反转字符串中的字符或单词。在这个方法中,我们首先反转整个字符串,然后反转每个单词内部的字符。这需要两次遍历:第一次是反转整个字符串,第二次是反转每个单词。
object Solution {
fun ReverseSentence(ReverseSentence: String): String {
val chars = ReverseSentence.toCharArray()
var i = 0 // 左指针
var j = chars.size - 1 // 右指针
// 首先反转整个字符串
while (i < j) {
swap(chars, i++, j--)
}
// 然后反转每个单词
i = 0
while (i < chars.size) {
if (!chars[i].isWhitespace()) {
var start = i
while (i < chars.size && !chars[i].isWhitespace()) {
i++
}
var end = i - 1
while (start < end) {
swap(chars, start++, end--)
}
}
i++
}
return String(chars)
}
private fun swap(chars: CharArray, a: Int, b: Int) {
val temp = chars[a]
chars[a] = chars[b]
chars[b] = temp
}
}
空间复杂度:O(1),因为不需要额外的存储空间,所有的操作都在原始字符数组上进行。时间复杂度:O(n),其中n是字符串的长度。需要两次遍历整个字符串:一次用于反转整个字符串,另一次用于反转每个单词,符合题目要求。