中级农民
- 积分
- 106
- 大米
- 颗
- 鳄梨
- 个
- 水井
- 尺
- 蓝莓
- 颗
- 萝卜
- 根
- 小米
- 粒
- 学分
- 个
- 注册时间
- 2015-9-3
- 最后登录
- 1970-1-1
|
Line Breaking
地里面经
详见Google电面。。好难估计跪了第二题
假设:
1. input string肯定valid,每个单词由一个space分开。
2. input中最长的单词<=k。
先看一下brutal force的解法:
- int minimumRaggedness(const string& in, int k) {
- if (in.empty()) {
- return 0;
- }
- return minSquaredSumDFS(in, k, 0);
- }
- int minimumRaggednessDFS(const string& in, int k, size_t start_pos) {
- int space_left = k;
- int res = INT_MAX;
- while (space_left > 0) {
- auto space_pos = in.find(' ', start_pos);
- space_pos = space_pos == string::npos ? in.size() : space_pos;
- space_left = space_left - (space_pos - start_pos);
- if (space_left < 0) {
- break;
- }
- if (space_pos == in.size()) {
- res = min(res, static_cast<int>(pow(space_left, 2)));
- break;
- }
- int child_res = minimumRaggednessDFS(in, k, ++space_pos);
- res = min(res, static_cast<int>(pow(space_left, 2)) + child_res);
- start_pos = space_pos;
- --space_left;
- }
- return res;
- }
复制代码 还可以用strok或者istringstream来分词,这里使用find是因为可以直接得到单词长度。
这里可以看到用DFS的话,会有很多重复计算。所以很容易想到记录已经计算过的结果。
- int minimumRaggedness(const string& in, int k) {
- if (in.empty()) {
- return 0;
- }
- //return minSquaredSumDFS(in, k, 0);
- unordered_map<size_t, int> record;
- return minSquaredSumDFSWithMem(in, k, 0, record);
- }
- int minimumRaggednessDFSWithMem(const string& in, int k, size_t start_pos, unordered_map<size_t, int>& record) {
- auto it = record.find(start_pos);
- if (it != record.end()) {
- return it->second;
- }
- auto pos = start_pos;
- int space_left = k;
- int res = INT_MAX;
- while (space_left > 0) {
- auto space_pos = in.find(' ', pos);
- space_pos = space_pos == string::npos ? in.size() : space_pos;
- space_left = space_left - (space_pos - pos);
- if (space_left < 0) {
- break;
- }
- if (space_pos == in.size()) {
- res = min(res, static_cast<int>(pow(space_left, 2)));
- break;
- }
- int child_res = minimumRaggednessDFSWithMem(in, k, ++space_pos, record);
- res = min(res, static_cast<int>(pow(space_left, 2)) + child_res);
- pos = space_pos;
- --space_left;
- }
- record[start_pos] = res;
- return res;
- }
复制代码 可以看到record相当于记录了以某个pos开始的word作为某行开头,余下的substring的minimum sum of squared space left over。
写到这里,DP的解法已经呼之欲出。
DP的解法:
- int minimumRaggedness(const string& in, int k) {
- if (in.empty()) {
- return 0;
- }
- //return minSquaredSumDFS(in, k, 0);
- //unordered_map<size_t, int> record;
- //return minSquaredSumDFSWithMem(in, k, 0, record);
- return minimumRaggednessDP(in, k);
- }
- int minimumRaggednessDP(const string& in, int k) {
- unordered_map<int, size_t> words;
- int i = 0;
- for (size_t start = 0; start < in.size(); ++i) {
- size_t space = in.find(' ', start);
- space = space == string::npos ? in.size() : space;
- words[i] = space - start;
- start = ++space;
- }
- int n = words.size();
- vector<int> DP(n + 1, INT_MAX);
- DP[0] = 0;
- for (int i = 1; i <= n; ++i) {
- int space_left = k - words[i - 1];
- DP[i] = min(DP[i], DP[i - 1] + static_cast<int>(pow(space_left, 2)));
- --space_left;
- for (int j = i - 1; j > 0; --j) {
- space_left -= words[j - 1];
- if (space_left < 0) {
- break;
- }
- DP[i] = min(DP[i], DP[j - 1] + static_cast<int>(pow(space_left, 2)));
- --space_left;
- }
- }
- return DP[n - 1];
- }
复制代码 这里我们预处理了一下input string。用一个unordered_map(其实直接使用vector就可以)来记录每个单词的长度。
因为计算顺序跟DFS相反,这里的状态转移方程等于以某个word作为某行结尾,其之前的substring的minimum sum of squared space left over。
可以看到虽然用了double loop,但是第二个loop是跟k的大小有关的,所以runtime performance更近似于O(n * k)。
我觉得这道题作为一道电面题目确实挺难的,因为在考虑怎么写DP的时候,还要考虑对input string的处理。
不知道给出recusive DFS+memorialization的解法,并口头指出可以将其转化为iterative DP的话可不可以过。
这道题其实DP并不是最优解,有兴趣的可以看一个这个链接:http://xxyxyz.org/line-breaking/
里面给出了O(n*logn)及O(n)的解法。 |
|