向字符串添加空格 图解题解
这道题到底在问什么
- 输入
- s="LeetcodeHelpsMeLearn", spaces=[8,13,15]
- 输出
- "Leetcode Helps Me Learn"
- 输入
- s="AlgoMoocOJ", spaces=[4,8]
- 输出
- "Algo Mooc OJ"
最优解:一步一步想明白
- 3记牢这一句:i 扫字符,j 追下一个要插空格的位置,i 撞上 spaces[j] 就先补一个空格再把字符追进去。整趟只扫一遍,又快又稳。下面从头开始拼。
- 4双指针待命布局摆好。上面这条是原串 AlgoMoocOJ,每个格子下面是它的下标。右边面板是 spaces 数组,现在 j 指着第 0 行、值是 4,意思是下一个要在它前面补空格的位置是下标 4。下面这行 ans 是我们要拼的答案,现在还空着。指针 i 从 0 出发。
- 5比较 i 与 spaces[j]指针 i 走到 0,和 spaces[0] 也就是 4 比一比,0 不等于 4,说明这个位置前面不用加空格。那就跳过补空格这步,直接去追加字符。
- 6追加当前字符不管刚才插没插空格,当前字符 A 都要放进答案。把它追加到 ans 末尾,现在答案变成了 「A」。这一格处理完,指针 i 往右挪一步,去看下一个字符。
- 7比较 i 与 spaces[j]指针 i 走到 1,和 spaces[0] 也就是 4 比一比,1 不等于 4,说明这个位置前面不用加空格。那就跳过补空格这步,直接去追加字符。
- 8追加当前字符不管刚才插没插空格,当前字符 l 都要放进答案。把它追加到 ans 末尾,现在答案变成了 「Al」。这一格处理完,指针 i 往右挪一步,去看下一个字符。
- 9比较 i 与 spaces[j]指针 i 走到 2,和 spaces[0] 也就是 4 比一比,2 不等于 4,说明这个位置前面不用加空格。那就跳过补空格这步,直接去追加字符。
- 10追加当前字符不管刚才插没插空格,当前字符 g 都要放进答案。把它追加到 ans 末尾,现在答案变成了 「Alg」。这一格处理完,指针 i 往右挪一步,去看下一个字符。
- 11比较 i 与 spaces[j]指针 i 走到 3,和 spaces[0] 也就是 4 比一比,3 不等于 4,说明这个位置前面不用加空格。那就跳过补空格这步,直接去追加字符。
- 12追加当前字符不管刚才插没插空格,当前字符 o 都要放进答案。把它追加到 ans 末尾,现在答案变成了 「Algo」。这一格处理完,指针 i 往右挪一步,去看下一个字符。
- 13比较 i 与 spaces[j]指针 i 走到 4,拿它和 spaces[0] 也就是 4 比一比,正好相等,命中了!这说明当前字符 M 之前该补一个空格。先别急着放字符,下一步先把空格补上。
- 14插空格 · j 前移命中了就先补空格。往 ans 末尾放一个空格,你看答案串在字符 M 之前多了一个空格。补完把 j 往后挪一格,从 0 变成 1,面板高亮跟着移到下一个待插入的位置(下标 8)。空格补好了,接着才轮到字符。
- 15追加当前字符不管刚才插没插空格,当前字符 M 都要放进答案。把它追加到 ans 末尾,现在答案变成了 「Algo M」。这一格处理完,指针 i 往右挪一步,去看下一个字符。
- 16比较 i 与 spaces[j]指针 i 走到 5,和 spaces[1] 也就是 8 比一比,5 不等于 8,说明这个位置前面不用加空格。那就跳过补空格这步,直接去追加字符。
- 17追加当前字符不管刚才插没插空格,当前字符 o 都要放进答案。把它追加到 ans 末尾,现在答案变成了 「Algo Mo」。这一格处理完,指针 i 往右挪一步,去看下一个字符。
- 18比较 i 与 spaces[j]指针 i 走到 6,和 spaces[1] 也就是 8 比一比,6 不等于 8,说明这个位置前面不用加空格。那就跳过补空格这步,直接去追加字符。
- 19追加当前字符不管刚才插没插空格,当前字符 o 都要放进答案。把它追加到 ans 末尾,现在答案变成了 「Algo Moo」。这一格处理完,指针 i 往右挪一步,去看下一个字符。
- 20比较 i 与 spaces[j]指针 i 走到 7,和 spaces[1] 也就是 8 比一比,7 不等于 8,说明这个位置前面不用加空格。那就跳过补空格这步,直接去追加字符。
- 21追加当前字符不管刚才插没插空格,当前字符 c 都要放进答案。把它追加到 ans 末尾,现在答案变成了 「Algo Mooc」。这一格处理完,指针 i 往右挪一步,去看下一个字符。
- 22比较 i 与 spaces[j]指针 i 走到 8,拿它和 spaces[1] 也就是 8 比一比,正好相等,命中了!这说明当前字符 O 之前该补一个空格。先别急着放字符,下一步先把空格补上。
- 23插空格 · j 前移命中了就先补空格。往 ans 末尾放一个空格,你看答案串在字符 O 之前多了一个空格。补完把 j 往后挪一格,从 1 变成 2,这下 spaces 里两个下标都用过了、j 已用完,后面不再有待插入的位置,面板高亮也随之撤掉。空格补好了,接着才轮到字符。
- 24追加当前字符不管刚才插没插空格,当前字符 O 都要放进答案。把它追加到 ans 末尾,现在答案变成了 「Algo Mooc O」。这一格处理完,指针 i 往右挪一步,去看下一个字符。
- 25比较 i 与 spaces[j]指针 i 走到 9,看右边面板,j 已经等于 2,spaces 两个下标都用完了。既然没有待插入的位置了,就跳过检查,直接去追加这个字符。这一步演示的就是 j 用尽后的收尾。
- 26追加当前字符不管刚才插没插空格,当前字符 J 都要放进答案。把它追加到 ans 末尾,现在答案变成了 「Algo Mooc OJ」。这一格是原串最后一个字符,处理完 i 就到达串尾之后的位置,下一步进入扫描结束、开始收束。
- 27大功告成整条串扫完了。回头看,我们只在两处补了空格:下标 4 的 M 前面、下标 8 的 O 前面,这两个触发字符标成了绿色。最后拼出来的答案就是 「Algo Mooc OJ」,和一开始说的 Algo Mooc OJ 分毫不差。全程只扫了一遍,双指针配合得干净利落。
⚠️ 容易写错的地方
✗ 错:取 spaces[j] 前不判 j 是否越界
✓ 对:先判 j 小于 spaces 长度,再比 i 等于 spaces[j]
spaces 用完后 j 会等于其长度,此时再取 spaces[j] 会越界,轻则报错重则读到脏值,守卫必须放在取值之前
✗ 错:把空格插到 spaces[j] 那个字符之后
✓ 对:空格插在该字符之前
题目要求在下标 spaces[j] 的字符之前加空格,插到后面会整体错位,输出全错
✗ 错:Java 里用 String 加号在循环里拼
✓ 对:用 StringBuilder,或 Python 用列表 join
不可变字符串每次加号都新建整串,循环里拼是 O(n 方);用可变容器攒好再一次成串才是 O(n)
✗ 错:每遇到一个 spaces 就用切片重建整串
✓ 对:一遍扫描顺序追加
反复切片拼接,每次都要复制整串,总代价是 O(n 乘 m);单遍追加只碰每个字符一次,才是线性
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class Solution:
def addSpaces(self, s: str, spaces: List[int]) -> str:
ans = []
j = 0
for i, c in enumerate(s):
if j < len(spaces) and i == spaces[j]:
ans.append(' ')
j += 1
ans.append(c)
return ''.join(ans)C++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
class Solution {
public:
string addSpaces(string s, vector<int>& spaces) {
string ans = "";
for (int i = 0, j = 0; i < s.size(); ++i) {
if (j < spaces.size() && i == spaces[j]) {
ans += ' ';
++j;
}
ans += s[i];
}
return ans;
}
};Java
import java.util.*;
class Solution {
public String addSpaces(String s, int[] spaces) {
StringBuilder ans = new StringBuilder();
for (int i = 0, j = 0; i < s.length(); ++i) {
if (j < spaces.length && i == spaces[j]) {
ans.append(' ');
++j;
}
ans.append(s.charAt(i));
}
return ans.toString();
}
}复杂度
时间
O(n)
n 是原串长度。i 从头扫到尾只走一遍,每个位置最多补一个空格加追加一个字符,都是常数操作;j 一路只增不减,总共前移的次数等于 spaces 的长度,不超过 n。合起来随 n 线性增长
空间
O(n)
按峰值算,主要花在攒出来的答案串上:它比原串多了 spaces 长度个空格,长度是 n 加 m 量级,也就是 O(n)。除答案外只用了 i、j 两个下标,额外辅助空间是 O(1)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 向字符串添加空格 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题的核心思路一句话怎么说?+
双指针单遍扫描。i 从头扫原串,j 指向 spaces 里下一个待插入的下标。每到一个 i,先判 j 没用完且 i 等于 spaces[j],成立就先补一个空格、j 前移;然后无条件把当前字符追加进答案。扫一遍就得到结果。
为什么要用可变容器攒答案,直接字符串相加不行吗?+
像 Java 的 String 是不可变的,循环里用加号每次都会新建一整条串,复制代价累加起来是 O(n 方),数据一大就超时。正确做法是用 StringBuilder 这类可变容器,或者 Python 先把片段放进列表最后 join,把总代价压回 O(n)。
时间和空间复杂度各是多少?+
时间 O(n),i 单遍扫串,j 只增不减,总前移次数不超过 spaces 长度。空间 O(n),主要是攒出来的答案串,长度是 n 加 m 量级;除此之外只用 i、j 两个下标,额外辅助空间 O(1)。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 向字符串添加空格 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。