LeetCode 942简单双指针 · 滑窗
增减字符串匹配 图解题解
这道题到底在问什么
字符串 s 长度为 n,只含 I 和 D。要返回一个由 0 到 n 这 n+1 个不同整数组成的排列 perm,使得 s 的第 i 位是 I 时 perm[i] 比 perm[i+1] 小,是 D 时 perm[i] 比 perm[i+1] 大。有多个合法答案时返回任意一个。
- 输入
- s = "IDID"
- 输出
- [0,4,1,3,2]
- 输入
- s = "III"
- 输出
- [0,1,2,3](一路递增)
- 输入
- s = "DDI"
- 输出
- [3,2,0,1]
最优解:一步一步想明白
- 3一句话口诀:I 给最小、D 给最大,贪心填。下面每一帧都在套它。
- 4开局:手里有 0 到 8 这 9 个数还没用,最小是 low=0,最大是 high=8。下面从左到右逐位决定放谁。
- 5轮到第 0 位(紫色空位),字符是 'I'。绿色是已经填好的位置。当前还没用的数是 0 到 8 这一段。
- 6'I' 表示后一个要比它大,所以放最小的 0 最稳,剩下的数全都比它大。放完 low 前进到 1。
- 7轮到第 1 位(紫色空位),字符是 'I'。绿色是已经填好的位置。当前还没用的数是 1 到 8 这一段。
- 8'I' 表示后一个要比它大,所以放最小的 1 最稳,剩下的数全都比它大。放完 low 前进到 2。
- 9轮到第 2 位(紫色空位),字符是 'D'。绿色是已经填好的位置。当前还没用的数是 2 到 8 这一段。
- 10'D' 表示后一个要比它小,所以放最大的 8 最稳,剩下的数全都比它小。放完 high 后退到 7。
- 11轮到第 3 位(紫色空位),字符是 'D'。绿色是已经填好的位置。当前还没用的数是 2 到 7 这一段。
- 12'D' 表示后一个要比它小,所以放最大的 7 最稳,剩下的数全都比它小。放完 high 后退到 6。
- 13轮到第 4 位(紫色空位),字符是 'I'。绿色是已经填好的位置。当前还没用的数是 2 到 6 这一段。
- 14'I' 表示后一个要比它大,所以放最小的 2 最稳,剩下的数全都比它大。放完 low 前进到 3。
- 15轮到第 5 位(紫色空位),字符是 'D'。绿色是已经填好的位置。当前还没用的数是 3 到 6 这一段。
- 16'D' 表示后一个要比它小,所以放最大的 6 最稳,剩下的数全都比它小。放完 high 后退到 5。
- 17轮到第 6 位(紫色空位),字符是 'I'。绿色是已经填好的位置。当前还没用的数是 3 到 5 这一段。
- 18'I' 表示后一个要比它大,所以放最小的 3 最稳,剩下的数全都比它大。放完 low 前进到 4。
- 19轮到第 7 位(紫色空位),字符是 'D'。绿色是已经填好的位置。当前还没用的数是 4 到 5 这一段。
- 20'D' 表示后一个要比它小,所以放最大的 5 最稳,剩下的数全都比它小。放完 high 后退到 4。
- 21前 8 位填完后,区间只剩一个数 4(low 和 high 撞在一起)。把它放到最后一格,排列就完整了。
- 22这就是构造出来的排列。整段绿色表示全部就位。下面随手验两位,确认增减关系都对得上。
- 23第 0 位是 'I',要求 perm[0] 比 perm[1] 小:0 确实小于 1,对。
- 24第 2 位是 'D',要求 perm[2] 比 perm[3] 大:8 确实大于 7,对。其余各位同理,全部满足。
⚠️ 容易写错的地方
✗ 错:想枚举全排列去试哪个合法
✓ 对:贪心一遍直接构造,O(n)
排列数是阶乘级,枚举会超时;贪心保证每步都不会卡住
✗ 错:循环完忘了补最后一个数
✓ 对:位置有 n+1 个,循环只填了 n 个,末位再放 low
此时 low 正好等于 high,是区间剩下的唯一一个数
✗ 错:把 I 和 D 放反
✓ 对:I 放最小、D 放最大
I 要后面更大,先放最小才给后面留足空间;D 反过来
完整代码(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 diStringMatch(self, s: str) -> List[int]:
low, high = 0, len(s)
ans = []
for c in s:
if c == "I":
ans.append(low)
low += 1
else:
ans.append(high)
high -= 1
ans.append(low)
return ansC++
#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:
vector<int> diStringMatch(string s) {
int n = s.size();
int low = 0, high = n;
vector<int> ans(n + 1);
for (int i = 0; i < n; ++i) {
if (s[i] == 'I') {
ans[i] = low++;
} else {
ans[i] = high--;
}
}
ans[n] = low;
return ans;
}
};Java
import java.util.*;
class Solution {
public int[] diStringMatch(String s) {
int n = s.length();
int low = 0, high = n;
int[] ans = new int[n + 1];
for (int i = 0; i < n; i++) {
if (s.charAt(i) == 'I') {
ans[i] = low++;
} else {
ans[i] = high--;
}
}
ans[n] = low;
return ans;
}
}复杂度
时间
O(n)
从左到右扫一遍字符串
空间
O(1)
只用 low、high 两个指针,不计输出数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 增减字符串匹配 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么这套贪心一定能构造出合法解,不会中途卡死?+
每一步放的数都取自「当前还没用的连续区间 low 到 high」。放 low,剩下的数全比它大,能满足后面任何 I;放 high,剩下的全比它小,能满足后面任何 D。区间每步缩一格,刚好用 n+1 个数填 n+1 个位置,所以一路顺到底,不会冲突。
合法答案是唯一的吗?+
不唯一。题目允许返回任意一个合法排列,这个贪心给出的是其中最简洁的一种构造。比如 s = "IDID" 它给 [0,4,1,3,2],但别的排列也可能合法。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 增减字符串匹配 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。