核心内容摘要
微语开源智能客服系统前端源码解析:从零搭建到生产环境部署
题目分析本题要求编写一个程序从给定的字典中找出所有能够由指定短语中的字母重组而成的单词组合。
输入分为两部分首先是字典部分每行一个单词按字母顺序排序然后是需要检查的短语列表每行一个短语。
两个部分均以单独一行的#结束。
字典最多包含2000 20002000个单词每个单词或短语的长度不超过20 2020个字符。
所有输入均为大写字母且不限于英语单词。
输出要求对于每个短语输出所有可能的字典单词组合按字母顺序排列这些组合中的单词全部来自字典且恰好用尽短语中的所有字母忽略空格且不能与原短语完全相同即不能只是原单词的顺序不同。
如果没有找到任何组合则不输出任何内容包括空行。
解题思路这个问题本质上是一个搜索与剪枝问题。
由于字典和短语的规模不大2000 20002000个单词每个短语最多20 2020个字母我们可以使用回溯法backtracking \texttt{backtracking}backtracking结合字母计数法来高效求解。
核心思路字母计数表示每个单词和短语都可以用一个长度为26 2626的数组表示记录每个字母出现的次数。
这种表示方式便于快速判断单词是否可由给定字母组成以及是否已用尽所有字母。
预处理字典读取字典时为每个单词存储其原始字符串和对应的字母计数数组。
处理每个短语计算短语的字母计数忽略空格作为目标数组goalCount。
将原短语按空格分割成单词列表single用于后续排除与原短语完全相同的组合。
使用回溯法从字典中选取单词组合使得组合的字母计数等于goalCount。
在回溯过程中利用字母计数进行剪枝如果当前选取的单词会导致某个字母的数量超过目标值则跳过该单词。
回溯搜索状态当前已选取的单词列表、当前字母计数数组currentCount。
终止条件已选取单词的总长度等于短语字母总数即所有字母已用尽。
剪枝条件单词长度超过剩余可用字母数。
单词的某个字母数量加上当前计数超过目标值。
去重确保输出组合的单词按字母顺序排列并排除与原短语单词列表完全相同只是顺序不同的组合。
输出格式输出时先输出原短语后跟再依次输出按字母顺序排序的单词组合。
算法复杂度分析字典大小M ≤ 2000 M \leq 2000M≤2000短语字母数L ≤ 20 L \leq 20L≤20回溯的深度取决于单词长度但剪枝能显著减少搜索空间。
最坏情况下搜索空间是指数级的但实际由于字母限制和剪枝运行效率较高。
实现细节使用结构体item存储单词及其字母计数。
使用全局数组used标记字典单词是否已被使用。
回溯函数backtrack从起始索引开始避免重复组合。
输出前对单词组合按字母顺序排序并比较与原短语单词列表是否相同。
代码实现// Anagram Checker// UVa ID: 148// Verdict: Accepted// Submission Date:
// UVa Run Time:
059s//// 版权所有C2016邱秋。
metaphysis # yeah dot net#includebits/stdc.husingnamespacestd;structitem{string word;intcount[26];};item words[2001];intgoalCount[26],currentCount[26];vectorstringsingle;boolused[2001];intwordGet[2001],indexGet,total;string targetLine;voidisGood(){boolgoodtrue;for(inti0;i26;i)if(currentCount[i]!goalCount[i]){goodfalse;break;}if(!good)return;if(indexGetsingle.size()){vectorstringt1(single),t2;for(inti0;iindexGet;i)t
push_back(words[wordGet[i]].word);sort(t
begin(),t
end());sort(t
begin(),t
end());boolgoodfalse;for(inti0;it
size();i)if(t1[i]!t2[i]){goodtrue;break;}if(!good)return;}couttargetLine ;for(inti0;iindexGet;i)cout words[wordGet[i]].word;coutendl;}voidbacktrack(intstart,intlength,inttarget){if(lengthtarget){isGood();return;}for(intistart;itotal;i){if(used[i])continue;if((lengthwords[i].word.length())target)continue;boolgoodtrue;for(intj0;j26;j)if((words[i].count[j]currentCount[j])goalCount[j]){goodfalse;break;}if(!good)continue;wordGet[indexGet]i;used[i]true;for(intj0;j26;j)currentCount[j]words[i].count[j];backtrack(i1,lengthwords[i].word.length(),target);for(intj0;j26;j)currentCount[j]-words[i].count[j];used[i]false;indexGet--;}}intmain(){string line;total0;while(getline(cin,line),line!#){item aItem;aItem.wordline;memset(aItem.count,0,sizeof(aItem.count));for(inti0;iline.length();i)aItem.count[line[i]-A];words[total]aItem;}while(getline(cin,line),line!#){intalphaCount0;targetLine.assign(line);memset(goalCount,0,sizeof(goalCount));for(inti0;iline.length();i)if(isalpha(line[i])){goalCount[line[i]-A];alphaCount;}single.clear();istringstreamiss(line);string word;while(issword)single.push_back(word);memset(currentCount,0,sizeof(currentCount));memset(used,0,sizeof(used));indexGet0;backtrack(0,0,alphaCount);}return0;}
总结本题通过字母计数法和回溯剪枝高效解决了多单词字母重组问题。
关键在于使用计数数组代替字符串直接比较提升效率。
在回溯过程中尽早剪枝减少无效搜索。
输出时注意排序和去重符合题目要求。