我已经找到了问题的O(n平方)解决方案。我想知道更好的解决方案。 (这不是一个家庭作业/面试问题,而是我出于自己的兴趣做的事情,因此在这里分享):
If a=1, b=2, c=3,….z=26. Given a string, find all possible codes that string
can generate. example: "1123" shall give:
aabc //a = 1, a = 1, b = 2, c = 3
kbc // since k is 11, b = 2, c= 3
alc // a = 1, l = 12, c = 3
aaw // a= 1, a =1, w= 23
kw // k = 11, w = 23
以下是我的问题代码:
void alpha(int* a, int sz, vector<vector<int>>& strings) {
for (int i = sz - 1; i >= 0; i--) {
if (i == sz - 1) {
vector<int> t;
t.push_back(a[i]);
strings.push_back(t);
} else {
int k = strings.size();
for (int j = 0; j < k; j++) {
vector<int> t = strings[j];
strings[j].insert(strings[j].begin(), a[i]);
if (t[0] < 10) {
int n = a[i] * 10 + t[0];
if (n <= 26) {
t[0] = n;
strings.push_back(t);
}
}
}
}
}
}
基本上,矢量字符串将包含数字集。 这将在n平方中运行。我正在尝试至少一个nlogn解决方案。
直观的树应该在这里提供帮助,但不能在任何地方发布。
答案 0 :(得分:4)
通常,您的问题复杂性更像是2^n
,而不是n^2
,因为您的k
可以随着每次迭代而增加。
这是另一种递归解决方案(注意:对于很长的代码,递归是不好的)。我没有专注于优化,因为我不熟悉C ++ X,但我认为递归解决方案可以使用一些move
进行优化。
// Add the front element to each trailing code sequence. Create a new sequence if none exists
void update_helper(int front, std::vector<std::deque<int>>& intermediate)
{
if (intermediate.empty())
{
intermediate.push_back(std::deque<int>());
}
for (size_t i = 0; i < intermediate.size(); i++)
{
intermediate[i].push_front(front);
}
}
std::vector<std::deque<int>> decode(int digits[], int count)
{
if (count <= 0)
{
return std::vector<std::deque<int>>();
}
std::vector<std::deque<int>> result1 = decode(digits + 1, count - 1);
update_helper(*digits, result1);
if (count > 1 && (digits[0] * 10 + digits[1]) <= 26)
{
std::vector<std::deque<int>> result2 = decode(digits + 2, count - 2);
update_helper(digits[0] * 10 + digits[1], result2);
result1.insert(result1.end(), result2.begin(), result2.end());
}
return result1;
}
呼叫:
std::vector<std::deque<int>> strings = decode(codes, size);
修改强>
关于原始代码的复杂性,我将尝试展示在最坏情况下会发生什么,其中代码序列仅包含1
和2
值。
void alpha(int* a, int sz, vector<vector<int>>& strings)
{
for (int i = sz - 1;
i >= 0;
i--)
{
if (i == sz - 1)
{
vector<int> t;
t.push_back(a[i]);
strings.push_back(t); // strings.size+1
} // if summary: O(1), ignoring capacity change, strings.size+1
else
{
int k = strings.size();
for (int j = 0; j < k; j++)
{
vector<int> t = strings[j]; // O(strings[j].size) vector copy operation
strings[j].insert(strings[j].begin(), a[i]); // strings[j].size+1
// note: strings[j].insert treated as O(1) because other containers could do better than vector
if (t[0] < 10)
{
int n = a[i] * 10 + t[0];
if (n <= 26)
{
t[0] = n;
strings.push_back(t); // strings.size+1
// O(1), ignoring capacity change and copy operation
} // if summary: O(1), strings.size+1
} // if summary: O(1), ignoring capacity change, strings.size+1
} // for summary: O(k * strings[j].size), strings.size+k, strings[j].size+1
} // else summary: O(k * strings[j].size), strings.size+k, strings[j].size+1
} // for summary: O(sum[i from 1 to sz] of (k * strings[j].size))
// k (same as string.size) doubles each iteration => k ends near 2^sz
// string[j].size increases by 1 each iteration
// k * strings[j].size increases by ?? each iteration (its getting huge)
}
也许我在某个地方犯了一个错误,如果我们想玩得好,我们可以将矢量副本视为O(1)
而不是O(n)
以降低复杂性,但事实仍然是,最糟糕的是case是在每次迭代中加倍外部矢量大小(至少每第二次迭代,考虑到if
条件的确切结构),内循环和内循环取决于增长的矢量大小,这使得整个故事在至少O(2 ^ n)。
<强> EDIT2:强>
我找出了结果的复杂性(最好的假设算法仍然需要创建结果的每个元素,因此结果复杂性就像任何算法可以达到的下限一样)
它实际上遵循斐波那契数字:
对于您1
的最差情况输入(仅限size N+2
s):
size N
有k(N)
个元素size N+1
有k(N+1)
个元素size N+2
是以a
开头的代码组合,后跟size N+1
的组合(a
获取源的一个元素)和以{{开头的代码1}},然后是k
的组合(size N
需要两个元素来源)k
有size N+2
个元素从k(N) + k(N+1)
和size 1 => 1 (a)
结果:仍然呈指数增长;)
<强> EDIT3:强>
根据Edit2中解释的属性,制作了一个动态编程解决方案,有点类似于在代码数组上进行反向迭代并在向量使用中进行优化的方法。
内循环(size 2 => 2 (aa or k)
)仍然由结果计数(最坏情况下的Fibonacci)支配,并且一些外循环迭代将具有相当数量的子结果,但至少子结果是缩减为指向某个中间节点的指针,因此复制应该非常有效。作为一点奖励,我将结果从数字转换为字符。
另一个修改:范围update_helper
的更新代码为0 - 25
,修复了导致错误结果的一些错误。
'a' - 'z'
动态重用结构的一个缺点是清理,因为你要小心只删除每个节点一次。