给定一份单词的清单,设计一个算法,创建由字母组成的面积最大的矩形,其中每一行组成一个单词(自左向右),每一列也组成一个单词(自上而下)。不要求这些单词在清单里连续出现,但要求所有行等长,所有列等高。
如果有多个面积最大的矩形,输出任意一个均可。一个单词可以重复使用。
示例 1:
输入: [“this”, “real”, “hard”, “trh”, “hea”, “iar”, “sld”]
输出:
[
“this”,
“real”,
“hard”
]
比较复杂,所以还是看注释吧.主要就是构造前缀树,然后DFS+回溯,同时利用前缀树来判断是否符合要求
public class Main17_25 {
public static void main(String[] args) {
}
static Trie root;
static int maxArea,maxLength;
static List<String> ans;
public static String[] maxRectangle(String[] words) {
root = new Trie();
Map<Integer, Set<String>> map = new HashMap<>();//用map来存放长度len,以及长度为len的所有单词的集合set
maxArea = 0;
maxLength = 0;
ans = new ArrayList<>();
//构造前缀树
for (String str : words) {
Trie node = root;
for (int i = 0; i < str.length(); i++) {
if (node.chrilren[str.charAt(i) - 'a'] == null) {
node.chrilren[str.charAt(i) - 'a'] = new Trie();
}
node = node.chrilren[str.charAt(i) - 'a'];
}
node.isLeaf = true;//将单词结尾处的isleaf置true 表示一个单词的结尾
}
//将每个单词放到map中
for (String str : words) {
maxLength = Math.max(str.length(), maxLength);
Set<String> set = map.getOrDefault(str.length(), new HashSet<>());
set.add(str);
map.put(str.length(), set);
}
//进行DFS
List<String> path = new ArrayList<>();
for (int len:map.keySet()){
path.clear();//将前一次的结果清空
//回溯需要的参数是:相同长度单词的集合,存放路径的列表,当前单词的长度
DFS(map.get(len),path,len);
}
return ans.toArray(new String[ans.size()]);
}
public static void DFS(Set<String> set,List<String> path,int wordLen){
//剪枝:set里的情况不可能得到最优解,提前过滤掉不考虑
if (wordLen*maxLength<=maxArea) return;
//剪枝:如果path矩阵的高度已经超过清单中最长单词长度,结束
if (path.size()>maxLength) return;
for (String str:set){
path.add(str);
//valid[0]表示是否有字母不在字典树中
//valid[1]表示是否所有的列都构成了清单里的单词
boolean[] valid = isValid(path);
//都在清单
if (valid[0]){
int area = path.size()*path.get(0).length();
//都是末尾的字符了 可以结束本次
if (valid[1]&&(area>maxArea)){
maxArea =area;
ans = new ArrayList<>(path);
}
DFS(set,path,wordLen);
}
//回溯
path.remove(path.size()-1);
}
}
/**
*
*/
/* 判断一个矩阵是否每一列形成的单词都在清单里
* 存在两种情况:1.有的列中的字母不在字典树中,即这一列不可能构成单词,整个矩阵不合要求
* 2.每列的所有字母都在字典树中但有的结尾不是leaf,也就是有的列目前还不是个单词
* 所以需要一个boolean数组res[]来存放结果:
* res[0]表示是否有字母不在字典树中,true:都在,false:有不在的
* res[1]表示是否所有的列都构成了清单里的单词
*/
public static boolean[] isValid(List<String> path){
boolean allLeaf = true;
for (int i=0;i<path.get(0).length();i++){
//按列来看单词是否在字典树
Trie node =root;
for (int j=0;j<path.size();j++){
int ch = path.get(j).charAt(i)-'a';
if (node.chrilren[ch]==null) return new boolean[]{false,false};
node = node.chrilren[ch];
}
if (!node.isLeaf) allLeaf=false;
}
return new boolean[]{true,allLeaf};
}
}
//构造前缀树
class Trie {
Trie[] chrilren;
boolean isLeaf;//是否是叶子结点 单词的结尾
public Trie() {
chrilren = new Trie[26];
}
}