程序员面试金典——解题总结: 9.17中等难题 17.14句子分割

#include <iostream>
#include <stdio.h>
#include <hash_map>
#include <string>
#include <vector>
#include <fstream>
#include <algorithm>
#include <sstream>

using namespace std;

/*
问题:你写了一篇长文,却误用“查找/替换”,不慎删除了文档中所有空格、
      标点,大写变成小写。比如,句子"I reset the computer, It still didn't boot!"变成了"ireset the computertstilldidntboot"。
	  你发现,只要能正确分离各个单词,加标点和调整大小写都不成问题。大部分单词在字典里都找得到,有些字符串如专有名词则找不到。
	  给定一个字典(一组单词),设计一个算法,找出拆分一连串单词的最佳方式。这里“最佳”的定义是,解析后无法辨识的字符序列越少
	  越好。
	  举个例子,字符串"jesslookedjustliketimherbrother"的最佳解析结果为"JESS looked just like TIM her brother",总共有7个字符无法
	  辨别,全部显示为大写,以示区别。
分析:本题的关键:将字符串拆分为单词,使得解析后剩下的字符越少越好。
      可以递归来做,对每个字符后面存在两种情况:
	  1】该字符后面插入空格,
	  2】不插入空格
	  可以用动态规划来做,设dp[i]表示在以字符串str[i]结尾的字符串无效字符个数
	  这个动态规划方程应该这样写:
	  设p(str)表示字符串str得到的最小无效字符串个数
	    c(str)表示字符串str是否在字典中能否找到,能够找到返回0,否则,返回该字符串str的长度作为无效字符的个数
	  假设字符串为"thit",那么显然有
	  p(thit)= min{ c(t)+p(hit) , c(th) + p(it) , c(thi) + p(t) , c(thit) }
	  而p(hit)=min{ c(h)+p(it)  , c(hi) + p(t)  , c(hit) }
	  ....
	  所以
	  p(str) = min{ c(str[ 0~i ]) + p( str[ i+1~ str.length() - 1 ] ) | i属于 0到 str.length() - 1  }

输入:
thit
jesslookedjustliketimherbrother
输出:
1(无效字符个数)
T hit
7
JESS looked just like TIM her brother

关键:
1 
      可以递归来做,对每个字符后面存在两种情况:
	  1】该字符后面插入空格,
	  2】不插入空格
	  可以用动态规划来做,设dp[i]表示在以字符串str[i]结尾的字符串无效字符个数
	  这个动态规划方程应该这样写:
	  设p(str)表示字符串str得到的最小无效字符串个数
	    c(str)表示字符串str是否在字典中能否找到,能够找到返回0,否则,返回该字符串str的长度作为无效字符的个数
	  假设字符串为"thit",那么显然有
	  p(thit)= min{ c(t)+p(hit) , c(th) + p(it) , c(thi) + p(t) , c(thit) }
	  而p(hit)=min{ c(h)+p(it)  , c(hi) + p(t)  , c(hit) }
	  ....
	  所以
	  p(str) = min{ c(str[ 0~i ]) + p( str[ i+1~ str.length() - 1 ] ) | i属于 0到 str.length() - 1  }
*/
const int MAXSIZE = 1024;

//读取字典中每一行,截取出英文单词,存放到哈希表中
void readDictionary(string& fileName , hash_map<string , int>& words)
{
	ifstream infile(fileName , ios::in);
	if(!infile)
	{
		cout << fileName << " can not open" << endl;
		return;
	}
	char line[MAXSIZE];
	int index = -1;
	string word;
	while(!infile.eof())
	{
		infile.getline(line , MAXSIZE);
		//找到"|"截取出前面的部分作为单词
		string sLine(line);
		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));
		}
	}
	infile.close();
}

//记录:字符串及其对应的最少无效字符个数,以及前半段字符串
typedef struct Result
{
	string sentence;//原始字符串
	int invalidCharNumbers;//最少无效字符个数
	string frontStr;//前半段字符串
	string backStr;//用于递归处理的后半段字符串
	bool isFrontStrValid;//前面的字符串是否是有效的
}Result;

//p(str) = min{ c(str[ 0~i ]) + p( str[ i+1~ str.length() - 1 ] ) | i属于 0到 str.length() - 1  }
int getSplitResult(string sentence , hash_map<string , int>& words , hash_map<string ,Result>& wordToResult)
{
	if(sentence.empty() || words.empty())
	{
		return 0; //传入空字符串,则无效字符个数为0
	}
	//将字符串同一转换成小写的
	transform(sentence.begin() , sentence.end() , sentence.begin() , tolower);

	//如果结果已经计算出来,则直接返回
	hash_map<string ,Result>::iterator itFind = wordToResult.find(sentence);
	if(itFind != wordToResult.end())
	{
		return itFind->second.invalidCharNumbers;
	}
	int size = sentence.length() ;
	string frontStr;
	string backStr;
	int min = INT_MAX;
	Result result;
	for( int i = 0; i < size ; i++)
	{
		//截取前面的部分字符串用于在字典中查找,0到i+1的长度,对应str[0 ~ i],后半部分为str[i+1 , str.length() - i]
		frontStr = sentence.substr( 0 , i + 1);
		backStr = sentence.substr(i + 1 , sentence.length() - i);
		hash_map<string ,int>::iterator it = words.find(frontStr);
		int invalidNumbers = 0;
		//如果字符串在字典中无法找到,无效字符累加该字符串的长度
		if(it == words.end())
		{
			invalidNumbers += frontStr.length();
		}
		//递归对后半部分字符串进行分割
		int temp = getSplitResult(backStr , words , wordToResult);
		invalidNumbers += temp;

		//找到最小无效次数,需要记录,并记录当前划分的结果
		if(invalidNumbers < min)
		{
			min = invalidNumbers;
			result.frontStr = frontStr;
			result.backStr = backStr;
			//如果前面字符串无效
			if(it == words.end())
			{
				result.isFrontStrValid = false;
			}
			else
			{
				result.isFrontStrValid = true;
			}
		}
	}
	//将结果记录到结果集中
	result.invalidCharNumbers = min;
	result.sentence = sentence;
	wordToResult.insert(pair<string ,Result>( sentence , result ));
	return min;
}

//判断一个字符串是不是全大写
bool isUpperString(string str)
{
	if(str.empty())
	{
		return false;
	}
	string tempStr = str;
	transform(str.begin() , str.end() , str.begin() , toupper);
	if(tempStr == str)
	{
		return true;
	}
	else
	{
		return false;
	}
}

//拼接,将相邻大写的无效字符串拼接在一起
string concat(vector<string>& words)
{
	//如果两个相邻的字符串都是大写的,连接在一起,指导找到一个单词的首字母是小写的
	int size = words.size();
	int i = 0;
	string word;
	int upperBeginIndex = -1;
	int upperEndIndex = -1;
	bool isFirst = true;
	vector<string> results;
	while(i < size)
	{
		word = words.at(i);
		if(word.empty())
		{
			i++;
			continue;
		}
		
		//如果字符串大写,则说明这个是无效字符串,
		if( isUpperString(word) )
		{
			if(isFirst)
			{
				upperBeginIndex = i;
				isFirst = false;
			}
			upperEndIndex = i;
		}
		//如果此时找到小写字符串,说明之前累积的大写字符串需要进行拼接了
		else
		{
			//如果之前的大写字符串还没有处理
			if(isFirst == false)
			{
				//如果只有一个大写字符串
				if(upperEndIndex > upperBeginIndex)
				{
					stringstream resultStream;
					for( int j = upperBeginIndex ; j <= upperEndIndex ;j++)
					{
						resultStream << words.at(j) ;
					}
					results.push_back(resultStream.str());
				}
				else
				{
					results.push_back(words.at(upperBeginIndex));
				}
			}
			//存放本次直接小写字符串
			results.push_back(word);
			isFirst = true;
		}
		i++;
	}
	stringstream stream;
	int length = results.size();
	for(int i = 0 ; i < length ; i++)
	{
		if(i)
		{
			stream << " " << results.at(i);
		}
		else
		{
			stream << results.at(i);
		}
	}
	return stream.str();
}

//打印最终结果,需要把相邻的无效字符串拼接,如何判断两个无效字符是否相邻
string getResult(string& sentence,hash_map<string ,Result>& wordToResult)
{
	if(sentence.empty() || wordToResult.empty())
	{
		cout << "No Result" << endl;
		return "";
	}
	transform(sentence.begin() , sentence.end() , sentence.begin() , tolower);
	hash_map<string ,Result>::iterator it = wordToResult.find(sentence);
	if(it == wordToResult.end())
	{
		cout << "No Result" << endl;
		return "";
	}
	stringstream resultStream;
	vector<string> resultVector;
	while(it != wordToResult.end())
	{
		//取出前半段字符,如果是无效的变成大写
		Result result = it->second;
		if(result.isFrontStrValid)
		{
			//resultStream << result.frontStr << " ";
			resultVector.push_back(result.frontStr);
		}
		else
		{
			string frontStr(result.frontStr);
			transform(frontStr.begin() , frontStr.end() , frontStr.begin() , toupper);
			//resultStream << frontStr << " ";
			resultVector.push_back(frontStr);
		}
		//寻找下一个分割的结果
		it = wordToResult.find(result.backStr);
	}
	string realResult = concat(resultVector);
	return realResult;
}

void process()
{
	hash_map<string ,int> words;
	string fileName("./dictionary.txt");
	readDictionary(fileName , words);
	string sentence;
	string result;
	while(cin >> sentence)
	{
		int wordStart = 0;
		int wordEnd = 0;
		hash_map<string , Result> wordToResult;
		int invalidNum = getSplitResult(sentence , words  , wordToResult);
		result = getResult(sentence , wordToResult);
		cout << invalidNum << endl << result << endl;
	}
}

int main(int argc , char* argv[])
{
	process();
	getchar();
	return 0;
}

/*
//对单词进行分割,返回无效字符个数
int getSplitResult(string& sentence  , int wordStart , int wordEnd , hash_map<string , int>& words)
{
	//?
	if(wordEnd >= sentence.length())
	{
		return wordEnd - wordStart;
	}
	//? java中substring(int beginIndex ,int endIndex),C++ substr(int beginIndex, int size)
	string word = sentence.substr(wordStart , wordEnd + 1 - wordStart + 1);
	//切断当前的单词
	int bestExact = getSplitResult(sentence , wordEnd + 1, wordEnd + 1 , words);
	//如果找不到单词,就累加无效字符个数
	if(words.find(word) == words.end())
	{
		bestExact += word.length();
	}

	//扩展当前的单词
	int bestExtend = getSplitResult(sentence , wordStart , wordEnd+1 , words);
	//找出最佳单词
	int result = min(bestExact , bestExtend);
	return result;
}

*/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值