【问题标题】:C++/C/Java: Anagrams - from original string to target;C++/C/Java: Anagrams - 从原始字符串到目标;
【发布时间】:2011-01-23 13:16:06
【问题描述】:

我正在尝试解决这个问题:http://uva.onlinejudge.org/external/7/732.html。 对于给定的示例,它们为我们提供了原始单词,例如 TRIT 和目标“anagramed”字符串 TIRT

目标:我们必须输出所有有效的“i”和“o”序列(分别是push和pop),它们从源字符串产生目标字符串。

所以,我正在考虑计算 "i" 和 "o" 的所有排列,但要减少这种情况:

1) 如果当前排列以 'o' 开头,则停止检查,因为所有下一个排列都将以该弹出命令开头,并且从空堆栈中弹出某些内容是无效命令。

2)如果在检查过程中发现 'o' 命令并且堆栈中没有任何内容,则跳过该情况。

3)如果找到“i”命令并且输入字符串中没有任何内容,则跳过该情况。

4)如果找到 'o' 命令并且当前预期的字符不是刚刚弹出的字符,则跳过这种情况,因为这永远不会到达目标字符串。

5) 如果输入字符串和目标字符串的长度不同,则不要搜索。

但我认为无论如何它可能会让我得到 TLE...

我知道这个理论:可能是排列和一路回溯。 我只是在实现它时遇到了太多困难。

有人可以和我分享一些代码和/或想法吗?

P.S.:当然,欢迎任何可能减少执行时间的建议。

【问题讨论】:

    标签: java c++ c algorithm stack


    【解决方案1】:

    这是一个有趣的问题,我相信下面的方法(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);
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      这个第一次迭代解决方案很有指导意义。它不是最有效的,因为它到处使用String,但它是一个很好的起点。

      import java.util.*;
      
      public class StackAnagram {
      
          static void anagram(String s1, String s2, String stack, String instr) {
              if (s2.isEmpty()) {
                  if (s1.isEmpty() && stack.isEmpty()) {
                      System.out.println(instr.trim());
                  }
                  return;
              }
              if (!s1.isEmpty()) {
                  anagram(s1.substring(1), s2, s1.charAt(0) + stack, instr + "i ");
              }
              if (!stack.isEmpty() && stack.charAt(0) == s2.charAt(0)) {
                  anagram(s1, s2.substring(1), stack.substring(1), instr + "o ");
              }
          }
      
          static void anagram(String s1, String s2) {
              System.out.println("[");
              anagram(s1, s2, "", "");
              System.out.println("]");
          }
      
          public static void main(String args[]) {
              anagram("madam", "adamm");
              anagram("bahama", "bahama");
              anagram("long", "short");
              anagram("eric", "rice");
              anagram("ericc", "rice");
          }
      }
      

      【讨论】:

      • 请注意,此解决方案仅检查 i 和 o 的所有排列,它仅检查堆栈上的顶部字符匹配的特定分支字谜中的下一个字符。
      • 我认为这很好,因为消除了计算所有排列的额外计算。该问题为我们提供了一些可以用来跳过案例的提示,因此我认为不检查所有排列的解决方案是一种很好的方式。我错了吗?
      • 如果您进行大量修改和连接,字符串只会很糟糕(性能方面)。由于您主要使用子字符串和字符,因此提供的代码应该没问题。
      • 该解决方案通过了测试,但 T.E.
      猜你喜欢
      • 2017-11-12
      • 1970-01-01
      • 1970-01-01
      • 2017-07-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-26
      • 1970-01-01
      相关资源
      最近更新 更多