金字塔转换矩阵
难度:
标签:
题目描述
你正在把积木堆成金字塔。每个块都有一个颜色,用一个字母表示。每一行的块比它下面的行 少一个块 ,并且居中。
为了使金字塔美观,只有特定的 三角形图案 是允许的。一个三角形的图案由 两个块 和叠在上面的 单个块 组成。模式是以三个字母字符串的列表形式 allowed
给出的,其中模式的前两个字符分别表示左右底部块,第三个字符表示顶部块。
- 例如,
"ABC"
表示一个三角形图案,其中一个“C”
块堆叠在一个'A'
块(左)和一个'B'
块(右)之上。请注意,这与"BAC"
不同,"B"
在左下角,"A"
在右下角。
你从底部的一排积木 bottom
开始,作为一个单一的字符串,你 必须 使用作为金字塔的底部。
在给定 bottom
和 allowed
的情况下,如果你能一直构建到金字塔顶部,使金字塔中的 每个三角形图案 都是允许的,则返回 true
,否则返回 false
。
示例 1:
输入:bottom = "BCD", allowed = ["BCC","CDE","CEA","FFF"] 输出:true 解释:允许的三角形模式显示在右边。 从最底层(第3层)开始,我们可以在第2层构建“CE”,然后在第1层构建“E”。 金字塔中有三种三角形图案,分别是“BCC”、“CDE”和“CEA”。都是允许的。
示例 2:
输入:bottom = "AAAA", allowed = ["AAB","AAC","BCD","BBE","DEF"] 输出:false 解释:允许的三角形模式显示在右边。 从最底层(游戏邦注:即第4个关卡)开始,创造第3个关卡有多种方法,但如果尝试所有可能性,你便会在创造第1个关卡前陷入困境。
提示:
2 <= bottom.length <= 6
0 <= allowed.length <= 216
allowed[i].length == 3
- 所有输入字符串中的字母来自集合
{'A', 'B', 'C', 'D', 'E', 'F', 'G'}
。 -
allowed
中所有值都是 唯一的
代码结果
运行时间: 48 ms, 内存: 17.5 MB
/*
思路:
1. 使用流API创建一个递归函数来尝试构建金字塔的每一层。
2. 在每一层中,检查当前层的所有可能组合,并查看它们是否在允许的模式中。
3. 如果我们能够通过所有层的检查并到达顶部,则返回true。
*/
import java.util.*;
import java.util.stream.*;
public class PyramidTransitionStream {
public boolean pyramidTransition(String bottom, List<String> allowed) {
Map<String, List<Character>> allowedMap = allowed.stream().collect(Collectors.groupingBy(s -> s.substring(0, 2), Collectors.mapping(s -> s.charAt(2), Collectors.toList())));
return canBuildPyramid(bottom, allowedMap);
}
private boolean canBuildPyramid(String bottom, Map<String, List<Character>> allowedMap) {
if (bottom.length() == 1) return true;
if (IntStream.range(0, bottom.length() - 1).anyMatch(i -> !allowedMap.containsKey(bottom.substring(i, i + 2)))) return false;
List<String> nextLevel = new ArrayList<>();
generateNextLevel(bottom, 0, new StringBuilder(), nextLevel, allowedMap);
return nextLevel.stream().anyMatch(next -> canBuildPyramid(next, allowedMap));
}
private void generateNextLevel(String bottom, int index, StringBuilder current, List<String> nextLevel, Map<String, List<Character>> allowedMap) {
if (index == bottom.length() - 1) {
nextLevel.add(current.toString());
return;
}
String base = bottom.substring(index, index + 2);
allowedMap.get(base).forEach(c -> {
current.append(c);
generateNextLevel(bottom, index + 1, current, nextLevel, allowedMap);
current.deleteCharAt(current.length() - 1);
});
}
}
解释
方法:
此题解使用深度优先搜索(DFS)配合记忆化来判断是否能从底层的积木字符串构建到金字塔的顶端。首先,使用哈希表 `trans` 来存储每种可能的底部积木组合和对应能放在其上的顶部积木的列表。然后,定义一个递归函数 `search(a, b)`,其中 `a` 代表当前层正在处理的积木字符串,`b` 代表下一层已经部分构建的字符串。如果 `a` 的长度为2且 `b` 非空,函数尝试将 `b` 作为新的底层来继续构建金字塔。如果 `b` 为空,函数检查 `a` 是否存在于 `trans` 中。如果 `a` 的长度大于2,递归地对 `a` 的子串和 `b` 扩展后的字符串进行搜索。整个递归过程都用 `cache` 装饰器进行记忆化,以避免重复计算。最终,从 `bottom` 字符串开始调用 `search` 函数,并判断是否可以成功构建到金字塔顶部。
时间复杂度:
O(T^(N-1)),其中 T 是每个积木对可能的顶部积木选择数,N 是输入字符串 `bottom` 的长度。
空间复杂度:
O(L + N + M),其中 L 是 `allowed` 的长度,N 是 `bottom` 的长度,M 是记忆化存储的大小。
代码细节讲解
🦆
在`search`函数中,当`b`的长度大于1时,为什么需要调用`search(b, '')`?这里的逻辑是什么?
▷🦆
递归函数`search`中,当`len(a) == 2`且`b`为空时,为什么直接返回`a in trans`?这在算法中起到什么作用?
▷🦆
递归函数`search`在处理`len(a) > 2`的情况时,为什么选择`any(search(a[1:], b + t) for t in trans[a[:2]])`作为递归策略?这样的递归展开方式有什么优势?
▷🦆
在使用`defaultdict(list)`来构建`trans`字典时,具体是如何从`allowed`列表映射到`trans`的?请解释这种数据结构选择的好处。
▷