#include <iostream>
#include <stdio.h>
#include <hash_map>
#include <string>
#include <vector>
#include <fstream>
#include <algorithm>
#include <sstream>
using namespace std;
/*
问题:给定一份几百万个单词的清单,设计一个算法,创建由字母组成的最大矩形,其中每一行组成一个单词(自左向右),每一列也组成一个单词(自上而下)。
不要求这些单词在清单里连续出现,但要求所有行等长,所有列等高。
分析:等于每一行和每一列都要是一个单词,这个只能通过递归去做了。
书上做法:
步骤1:先从字典中选择长度最长的单词记为L,那么单词矩阵最大面积为s=L*L,初始以s,行长度i从L到1,列高度j为s/L,构建长度i高度为j的单词矩阵
以该长度i摆放在单词矩阵第一行,然后从等长度的单词数组中选择下一个尝试进行 摆放
步骤2:摆放后,如果摆放完成(单词矩阵的高度等于所求高度):判断单词矩阵每一行列的字符串是否在字典中出现,如果都出现,说明是的 ; 否则令单词矩阵面积s减1,转步骤1
如果没有摆放完成,遍历现有单词矩阵每一列,判断每一列组成的字符串是否在等长度的所有单词生成的trie树中为前缀,
如果都是,继续摆放下一个单词,转步骤2;
否则,说明当前摆放的单词不对,尝试用其他等长度单词继续摆放该位置,如果所有等长度单词都尝试过,且都不行,那么说明该单词矩阵无法生成,令
单词矩阵面积s减1,转步骤1
*/
const int MAXSIZE = 1024;
//前缀树结点包含:孩子结点指针数组,从根节点到当前结点是否是一个字符串的标记【结点的字符,体现在孩子结点指针数组的下标上,下标index = 字符 - 'a'】
const int BRANCH_NUM = 26;//26个英文字符
class TrieNode
{
public:
TrieNode()
{
_isString = false;
memset(_childs , NULL , sizeof(_childs) );
}
bool _isString;
TrieNode* _childs[BRANCH_NUM];
};
//前缀树包含了:根节点,查找,删除,销毁等成员和函数
class TrieTree
{
public:
TrieTree():_root(NULL){}//初始化根节点为空,否则后续析构会有问题
~TrieTree()
{
deleteTrie(_root);
}
void insert(vector<string>& strs)
{
if(strs.empty())
{
return;
}
int size = strs.size();
string word;
for(int i = 0 ; i < size ; i++)
{
if(i == 27)
{
int a = 1;
}
word = strs.at(i);
insert((char*)word.c_str());
}
}
//判断字符串中是否含有除'a'~'z'之外的其他字符
bool containInvalidChar(char* str)
{
if(NULL == str)
{
return true;
}
char* tempStr = str;
bool isInvalid = false;
while( (*str) != '\0')
{
int index = (*str) - 'a';
if(index < 0 || index >= BRANCH_NUM)
{
isInvalid = true;
break;
}
str++;
}
str = tempStr;
return isInvalid;
}
//插入一个字符串:遍历每个字符,插入到对应子树上,当所有字符处理玩,这只最后一个字符对应结点的字符串标记为真
void insert(char* str)
{
if(NULL == str || NULL == _root)
{
return;
}
//包含无效字符,则不建立
if(containInvalidChar(str))
{
return;
}
TrieNode* curNode = _root;
while( (*str) != '\0')
{
//找到对应子树
int index = (*str) - 'a';
//"self-satisfaction",里面不能出现其他字符;过滤的时候,str必须累加。 这里最终决定出现其他字符,就不建立“前缀”
if(index < 0 || index >= BRANCH_NUM)
{
str++;
continue;
}
//寻找对应子树,如果为空,就新建子树对应结点
TrieNode* childNode = curNode->_childs[index];
if(NULL == childNode)
{
TrieNode* node = new TrieNode();
curNode->_childs[index] = node;
}
str++;
//设置当前结点继续往下走
curNode = curNode->_childs[index];
}
//处理完成后,设置最后结点对应的字符串标记为真
curNode->_isString = true;
}
//查找字符串是否为前缀树中的某个前缀
bool searchPrefix(char* str)
{
if(NULL == str || NULL == _root )
{
return false;
}
TrieNode* curNode = _root;
while( (*str) != '\0')
{
int index = (*str) - 'a';
if(index < 0 || index >= BRANCH_NUM)
{
return false;
}
//寻找对应子树,如果为空,就说明没有找到
TrieNode* childNode = curNode->_childs[index];
if(NULL == childNode)
{
return false;
}
str++;
curNode = childNode;
}
//如果字符串最后为空了,说明必定是查找到整个前缀了,返回为真
if((*str) == '\0')
{
return true;
}
return false;
}
//查找某个字符串:对每个字符继续向下遍历对应的结点,直到字符串为空时,此时如果最后一个字符对应的结点的字符串标记如果为真,就说明查找到
//这个是查找某个字符串是否存在的
bool search(char* str)
{
if(NULL == str || NULL == _root )
{
return false;
}
TrieNode* curNode = _root;
while( (*str) != '\0')
{
int index = (*str) - 'a';
//一旦出现不属于'a'~'z'的字符,将不进行查询,直接返回错误
if(index < 0 || index >= BRANCH_NUM)
{
return false;
}
//寻找对应子树,如果为空,就说明没有找到
TrieNode* childNode = curNode->_childs[index];
if(NULL == childNode)
{
return false;
}
str++;
curNode = childNode;
}
return curNode->_isString;
}
//删除树,递归删除:先递归,最后删除结点
void deleteTrie(TrieNode* root)
{
if(NULL == root)
{
return;
}
for(int i = 0 ; i < BRANCH_NUM ; i++ )
{
if( root->_childs[i] != NULL )
{
deleteTrie(root->_childs[i]);
}
}
delete root;
root = NULL;
}
TrieNode* _root;
};
//单词数组:包含了:最大单词的长度,以长度进行分组的单词数组
class WordGroup
{
public:
WordGroup(int maxLength):_maxLength(maxLength){}
//读取所有单词,按长度分组,分别存储至对应长度的单词数组中
void getWordGroup(hash_map<string , int>& words , vector< vector<string> >& wordGroups)
{
if(_maxLength <= 0 || words.empty())
{
_wordGroups = wordGroups;
return ;
}
//先创建一个长度为 _maxLength的数组
for(int i = 0 ; i < _maxLength ; i++)
{
vector<string> strings;
wordGroups.push_back(strings);
}
hash_map<string , int>::iterator it = words.begin();
string word;
int length = 0;
for( ; it != words.end() ; it++)
{
word = it->first;
length = word.length();
//存入到对应长度的单词数组中
wordGroups.at( length - 1 ).push_back(word);
}
_wordGroups = wordGroups;
}
vector<string> getWords(int length)
{
vector<string> words;
if(_wordGroups.empty() || length > _maxLength)
{
return words;
}
words = _wordGroups.at(length - 1);
return words;
}
public:
int _maxLength;
vector< vector<string> > _wordGroups;
};
//读取字典中每一行,截取出英文单词,存放到哈希表中; 寻找出长度最长的单词,并将单词按照长度分组
int readDictionary(string& fileName , hash_map<string , int>& words)
{
ifstream infile(fileName , ios::in);
if(!infile)
{
cout << fileName << " can not open" << endl;
return -1;
}
char line[MAXSIZE];
int index = -1;
string word;
int maxLength = INT_MIN;
while(!infile.eof())
{
infile.getline(line , MAXSIZE);
//找到"|"截取出前面的部分作为单词
string sLine(line);
if(sLine.empty())
{
continue;
}
index = sLine.find("|");
word = sLine.substr(0 , index);
//将单词转换为小写
transform(word.begin() , word.end() , word.begin() , tolower);
if(words.find(word) == words.end())
{
words.insert(pair<string , int>(word , 1));
}
int length = word.length();
if(length > maxLength)
{
maxLength = word.length();
}
}
infile.close();
return maxLength;
}
//单词形成的矩形:包含矩阵的长度和高度,长度由传入的单词长度确定,需要一个二维的字符数组
const int CHAR_MAXSIZE = 100;
class Rectangle
{
public:
Rectangle(int length):_length(length),_height(0),_isValid(true){}
bool isRepeated(string& word)
{
//如果能找到,说明重复
if(_wordToTimes.find(word) != _wordToTimes.end())
{
return true;
}
else
{
return false;
}
}
//向已有单词矩形中添加一个单词,返回添加后的单词矩阵
Rectangle append(string& word)
{
if(word.empty() || word.length() != _length)
{
return *this;
}
if(isRepeated(word))
{
return *this;
}
_wordToTimes[word] = 1;
_words.push_back(word);
/*
for(int i = 0 ; i < length ; i++)
{
char value = word.at(i);
_charMatrix[_height][i] = value;
}
*/
_height++;
return *this;
}
//删除单词矩形中最后一行,用于回溯;不是实际删除,只是设置高度减少
void remove()
{
if(_height <= 0)
{
return;
}
_words.pop_back();
_height--;
}
//获取一列的值:行号一直变化,列号不懂
string getColumn(int columnNum)
{
stringstream stream;
for(int i = 0 ; i < _height ; i++)
{
//char ch = _charMatrix[i][columnNum];
char ch = _words.at(i).at(columnNum);
stream << ch ;
}
string word = stream.str();
return word;
}
//判断已有单词矩阵是否摆放完成:需要对每一列,检查该列的字符串是否在字典中,每一行无需检查,因为之前摆放的每一行都是字典中的单词
bool isCompleted(int length , int height , hash_map<string ,int>& dictionary)
{
string word;
for(int i = 0 ; i < length ; i++)
{
word = getColumn(i);
if( dictionary.find(word) == dictionary.end() )
{
return false;
}
}
return true;
}
//检查当前字符串矩阵部分是不是OK,仍然是对每一列,检查每一列的字符串是否在前缀树trie中,如果不在,说明无法构建
bool isPartialOK(int length , TrieTree* trieTree)
{
//如果高度为0,说明初始建立单词矩阵,返回为真
if(_height == 0)
{
return true;
}
if(NULL == trieTree || length <= 0)
{
return false;
}
string word;
for(int i = 0 ; i < length ; i++)
{
word = getColumn(i);
//这里是查找前缀是否存在
if(!trieTree->searchPrefix((char*)word.c_str()))
{
return false;
}
}
return true;
}
public:
int _length;
int _height;//记录当前又有的字符矩形高度
bool _isValid;//是否是有效的矩形
//char _charMatrix[][CHAR_MAXSIZE];//这个应该可以用字符串替换的
vector<string> _words;
hash_map<string ,int> _wordToTimes;//用于判断添加的字符串是否重复
};
//判断是否能够形成长度为length ,高度为height的单词矩阵
Rectangle formRectangle(int length , int height , WordGroup& wordGroup , Rectangle& rectangle , hash_map<string ,int>& dictionary , TrieTree* trieTree)
{
Rectangle nullRect(length);
nullRect._isValid = false;
if(length <= 0 || height <= 0)
{
return nullRect;
}
//判断是否完成,这里应该是摆放完成的,如果失败需要回溯吗?应该需要
if(rectangle._height == height)
{
//如果摆放完成,直接返回该矩形
if(rectangle.isCompleted(length , height , dictionary))
{
return rectangle;
}
//摆放失败,则需要回溯
else
{
return nullRect;
}
}
//判断部分的单词矩阵是否ok
else
{
if(!rectangle.isPartialOK(length , trieTree))
{
return nullRect;
}
}
//递归处理,获取等长的单词集合
vector<string> words = wordGroup.getWords(length);
int size = words.size();
string word;
for(int i = 0 ; i < size; i++ )
{
//加入一个单词,添加的单词不能重复
word = words.at(i);
//如果该单词已经存在,则过滤
if(rectangle.isRepeated(word))
{
continue;
}
//Rectangle newRect = rectangle.append(word);
Rectangle newRect(rectangle);
//确保原来的矩形不受影响,等同于起到回溯的作用
newRect.append(word);
Rectangle rect = formRectangle(length , height , wordGroup , newRect,dictionary , trieTree);
if(rect._isValid)
{
return rect;
}
}
return nullRect;
}
//生成单词矩阵
Rectangle formWordMatrix(int maxLength , WordGroup& wordGroup , hash_map<string ,int>& dictionary)
{
TrieTree* trieArray = new TrieTree[maxLength];//设定一个trie树,默认
//vector<TrieTree> trieArray;
for(int i = 0 ; i < maxLength ; i++)
{
TrieTree trie;
trieArray[i] = trie;
//trieArray.push_back(trie);
}
int area = maxLength * maxLength;
for(int s = area ; s > 0 ; s--)
{
//i表示单词的宽度
for(int i = maxLength ; i >= 1 ; i--)
{
int j = s / i;//j表示单词的高度
cout << "面积:" << s << ",长度:" << i << ",高度:" << j << endl;
if( i == 13 && j == 30 && s == 400)
{
int kk = 1;
}
//尝试形成:长度为i,高度为j的单词矩阵
vector<string> words = wordGroup.getWords(i);
//如果同等长度的单词数组个数 < 单词矩阵的高度,则肯定无法建立
if( words.size() < j )
{
continue;
}
//如果此时的对应长度的trie树还没有建立,就先建立。易错,这里对应的是 i-1的trie树
if(trieArray[i-1]._root == NULL)
{
TrieNode* root = new TrieNode();
trieArray[i-1]._root = root;
trieArray[i-1].insert(words);
}
//下面就开始建立单词矩阵
Rectangle initRect(i);
Rectangle rect = formRectangle(i , j , wordGroup ,initRect , dictionary , &trieArray[i-1]);
if(rect._isValid)
{
delete[] trieArray;
return rect;
}
}
}
delete[] trieArray;
Rectangle nullRect(maxLength);
nullRect._isValid = false;
return nullRect;
}
void print(Rectangle& rect)
{
if(rect._isValid)
{
int size = rect._words.size();
for(int i = 0 ; i < size ; i++)
{
cout << rect._words.at(i) << endl;
}
}
else
{
cout << "No result" << endl;
}
}
void process()
{
hash_map<string ,int> words;
string fileName("./dictionary.txt");
int maxLength = readDictionary(fileName , words);
if(maxLength <= 0)
{
cout << "Can't form word matrix" << endl;
return ;
}
WordGroup wordGroup(maxLength);
vector< vector<string> > wordMatrix;
wordGroup.getWordGroup(words , wordMatrix);
//单词按长度分组后,下面就开始递归处理了
Rectangle rect = formWordMatrix(maxLength , wordGroup , words);
print(rect);
}
int main(int argc , char* argv[])
{
process();
getchar();
return 0;
}
程序员面试金典——解题总结: 9.18高难度题 18.13给定一份几百万个单词的清单,设计一个算法,创建由字母组成的最大矩形
最新推荐文章于 2024-03-22 14:07:35 发布