这是一个有趣的问题,我相信下面的方法(find all trees which make the *from* string their preorder and the *to* string their inorder,下面有更多详细信息)如果实施得当会非常快:比暴力递归要好得多,因为它只是“知道”什么是可能的每个步骤都有子问题。
事实上,我做了一个小实验,并将其与给出的蛮力答案之一进行了比较。 (注意:C# 代码位于线程底部)。 preorder/inorder 的算法不是最优的,实际上可以改进。当然,蛮力算法具有简洁的优点,并且对于较小规模的问题可能已经足够好(并且性能更好)。
以下是结果。尤其是对于较长字符串的最后两个结果,对于这些字符串,前序/有序方法比蛮力要快得多。解决的子问题数量之间存在很大差异这一事实应该让您相信,这不仅是由于数据,而且随着字符串变长(可能有更多重复的字符),蛮力解决方案本质上必须处理更多不必要的子问题。使用蛮力解决方案进行任何记忆可能是可能的,但似乎非常困难。
------------------------
bahama(Length:6)
bahama(Length:6)
PreorderInorder
TimeTaken: 00:00:00.0230000
Number of recursive calls: 20
Number of results returned: 4
Brute Force
TimeTaken: 00:00:00.0030000
Number of recursive calls: 47
Number of results returned: 4
------------------------
madameric(Length:9)
adammcire(Length:9)
PreorderInorder
TimeTaken: 00:00:00
Number of recursive calls: 28
Number of results returned: 4
Brute Force
TimeTaken: 00:00:00
Number of recursive calls: 107
Number of results returned: 4
------------------------
trittrottotr(Length:12)
tirttorttort(Length:12)
PreorderInorder
TimeTaken: 00:00:00.0010000
Number of recursive calls: 103
Number of results returned: 63
Brute Force
TimeTaken: 00:00:00.0010000
Number of recursive calls: 1301
Number of results returned: 63
------------------------
bahamabhahambahamamadambahamabhahambahama(Length:41)
bahamabhahambahamamadambahamabhahammahaba(Length:41)
PreorderInorder
TimeTaken: 00:00:01.1710000
Number of recursive calls: 2059
Number of results returned: 97472
Brute Force
TimeTaken: 00:00:18.2610000
Number of recursive calls: 41784875
Number of results returned: 97472
------------------------
bahamabhahambahamamadambahamabhahambahama(Length:41)
bahamabhahambahamamadambahamabhahambahama(Length:41)
PreorderInorder
TimeTaken: 00:00:00.1790000
Number of recursive calls: 315
Number of results returned: 20736
Brute Force
TimeTaken: 00:00:17.1680000
Number of recursive calls: 41062923
Number of results returned: 20736
对于较短的字符串,蛮力胜出,因为开销较小,但是当字符串变长时,其他算法的速度确实会显示出来。在任何情况下,对于较短的字符串,您可以使用蛮力并切换到预排序/排序方法以获得较长的字符串,以获得两全其美。
现在,写一篇关于建议方法的文章:
考虑以下树:
m
/ \
a m
/ \ / \
. d . .
/ \
. a
/ \
. .
在哪里。是空节点。
考虑一个预购,其中您还输出空节点(a dot = '.')。
这给了我们预购:m a . d . a . . m . .
考虑有序,没有空节点,它是:a d a m m
现在考虑以下几点:
您接受预购:m a . d . a .. m ..,每次看到非空(或非点)时,您都会压入堆栈。每次看到空值(或点)时,都会弹出堆栈顶部。您可以忽略最后一个 .,因为这会导致弹出空堆栈。
我们可以手动运行:
m a . d . a . . m .. | Stack = | Output =
Push m
a . d . a . . m .. | Stack = m | Output =
Push a
. d . a . . m .. | Stack = a m | Output =
Pop
d . a . . m .. | Stack = m | Output = a
Push d
. a . . m .. | Stack = d m | Output = a
Pop
a . . m .. | Stack = m | Output = a d
Push a
. . m .. | Stack = a m | Output = a d
Pop, Pop (sorry getting a little lazy)
m .. | Stack = | Output = a d a m
Push m, Pop
. | Stack = | Output = a d a m m.
现在将 this 的输出与 in-order 进行比较。这是相同!
事实上,这对于一般二叉树是正确的,可以使用归纳法证明,并且是树的一个很好的属性,我们可以利用它来解决这个问题。
证明的简要草图:观察空节点的数量 = 1 + 非空节点的数量。这意味着当您完成弹出左侧树时,您会弹出根,因为左侧树的最后一个空值,因此所有这些推送和弹出都转换为(左)根(右)。
因此,给定一个字符串 A(如 madam),您需要仅使用 push 和 pop 将其转换为字符串 B(如 adamm),我们有以下解决问题的方法:
Find all trees which have A as the preorder and B as the inorder
该树的预排序,输出非空节点的推送和空节点的弹出(忽略最后一次推送)应该给出所需的序列。
在给定的前序/中序下查找树是一个标准问题,并且有许多快速解决方案。对于这个问题,您可能只需要稍微调整其中一个解决方案。
此外,这种方法的一些优点是
- 轻松知道要解决哪个子问题。
- 以上点有助于实现记忆化(参见下面的代码)。
- 此算法也可以轻松并行化。
我猜你确实想编写自己的 C/C++/Java 代码,所以我将提供我拥有的 C# 原型代码。
StackAnagram.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace SO
{
public class StackAnagram
{
public static int count = 0;
static Dictionary<string, Dictionary<string, List<string>>> memoized =
new Dictionary<string, Dictionary<string, List<string>>>();
public static List<string> Instructions(string from, string to)
{
count = 0;
if (from.Length != to.Length)
{
return new List<string>();
}
List<string> l = Instructions(from, 0, from.Length, to, 0, to.Length);
return l;
}
private static bool IsAnagram(string from, string to,
int startF, int endF, int startTo, int endTo)
{
Dictionary<char, int> d = new Dictionary<char, int>();
for (int i = startF; i < endF; i++)
{
if (d.ContainsKey(from[i]))
{
d[from[i]]++;
}
else
{
d[from[i]] = 1;
}
}
for (int i = startTo; i < endTo; i++)
{
if (d.ContainsKey(to[i]))
{
d[to[i]]--;
if (d[to[i]] == 0)
{
d.Remove(to[i]);
}
}
else
{
return false;
}
}
if (d.Count > 0)
{
return false;
}
return true;
}
private static List<string> Instructions(string from,
int startF, int endF, string to, int startTo, int endTo)
{
List<string> inst;
// Null tree.
if (startF >= endF || startTo >= endTo)
{
inst = new List<string>();
inst.Add("o ");
count++;
return inst;
}
string subFrom = from.Substring(startF, endF - startF);
string subTo = to.Substring(startTo, endTo - startTo);
Dictionary<string, List<string>> dict;
if (memoized.TryGetValue(subFrom, out dict))
{
if (dict.TryGetValue(subTo, out inst))
{
return inst;
}
}
else
{
memoized[subFrom] = new Dictionary<string, List<string>>();
}
count++;
inst = new List<string>();
if (!IsAnagram(from, to, startF, endF, startTo, endTo))
{
goto ret;
}
for (int j = 0; j < endTo - startTo; j++)
{
// Found possible root
if (from[startF] == to[startTo + j])
{
List<string> leftInst = Instructions(from, startF + 1,
startF + j + 1, to, startTo, startTo + j);
List<string> rightInst = Instructions(from, startF + j + 1,
endF, to, startTo + j + 1, endTo);
if (rightInst.Count <= 0)
{
continue;
}
foreach (string l in leftInst)
{
foreach (string r in rightInst)
{
inst.Add("i " + l + r);
}
}
}
}
ret:
memoized[subFrom][subTo] = inst;
return inst;
}
}
public class StackAnagramBrute
{
public static int count = 0;
static void anagram(String s1, String s2, String stack,
String instr, List<string> insts)
{
count++;
if (s2.Length == 0)
{
if (s1.Length == 0 && stack.Length == 0)
{
insts.Add(instr.Trim());
}
return;
}
if (!(s1.Length == 0))
{
anagram(s1.Substring(1), s2, s1[0] + stack, instr + "i ", insts);
}
if (!(stack.Length == 0) && stack[0] == s2[0])
{
anagram(s1, s2.Substring(1), stack.Substring(1), instr + "o ",
insts);
}
}
public static List<string> anagram(String s1, String s2)
{
count = 0;
if (s1.Length != s2.Length)
{
return new List<string>();
}
List<string> l = new List<string>();
anagram(s1, s2, "", "", l);
return l;
}
}
}
程序.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace SO
{
class Program
{
static void Main(string[] args)
{
string[] from = { "bahama", "madameric", "trittrottotr",
"bahamabhahambahamamadambahamabhahambahama", "bahamabhahambahamamadambahamabhahambahama" };
string[] to = { "bahama", "adammcire", "tirttorttort",
"bahamabhahambahamamadambahamabhahammahaba", "bahamabhahambahamamadambahamabhahambahama" };
for (int i = 0; i < from.Length; i++)
{
CompareAlgorithms(from[i], to[i]);
}
}
static void CompareAlgorithms(string from, string to)
{
Console.WriteLine("------------------------");
Console.WriteLine(from + "(Length:" + from.Length + ")");
Console.WriteLine(to + "(Length:" + to.Length + ")");
DateTime start = DateTime.Now;
List<string> inst = StackAnagram.Instructions(from, to);
DateTime end = DateTime.Now;
TimeSpan t = end - start;
Display("PreorderInorder", t, StackAnagram.count, inst.Count);
DateTime startBrute = DateTime.Now;
List<string> instBrute = StackAnagramBrute.anagram(from, to);
DateTime endBrute = DateTime.Now;
TimeSpan tBrute = endBrute - startBrute;
Display("Brute Force", tBrute, StackAnagramBrute.count, instBrute.Count);
}
static void Display(string method, TimeSpan t, int callCount, int resultCount)
{
Console.WriteLine(method);
Console.Write("\t");
Console.Write("TimeTaken: ");
Console.WriteLine(t.ToString());
Console.Write("\t");
Console.Write("Number of recursive calls: ");
Console.WriteLine(callCount);
Console.Write("\t");
Console.Write("Number of results returned: ");
Console.WriteLine(resultCount);
}
}
}