【问题标题】:Maximum number of characters using keystrokes A, Ctrl+A, Ctrl+C and Ctrl+V使用击键 A、Ctrl+A、Ctrl+C 和 Ctrl+V 的最大字符数
【发布时间】:2011-06-04 04:00:26
【问题描述】:

这是一个来自谷歌的面试问题。我自己无法解决。有人能解释一下吗?

编写一个程序来打印击键序列,以便生成最大数量的字符“A”。您只能使用 4 个键:ACtrl+ACtrl+C 和 Ctrl+V。只允许 N 次击键。所有 Ctrl+ 字符都被视为一键,因此 Ctrl+A 是一键。

例如序列ACtrl+ACtrl+C kbd>, Ctrl+V 4 次击键生成两个 A。

  • Ctrl+A 是全选
  • Ctrl+C 是复制
  • Ctrl+V 是粘贴

我做了一些数学。对于任何 N,使用 x 个 A,一个 Ctrl+A,一个 Ctrl+C 和 y Ctrl+V,我们可以生成最大((N-1)/2)2个A。对于某些 N > M,最好使用尽可能多的 Ctrl+ACtrl+CCtrl+V 序列,因为它使 A 的数量加倍。

序列 Ctrl+A, Ctrl+V, Ctrl+ C 不会覆盖现有的选择。它会将复制的选择附加到选定的选择。

【问题讨论】:

  • 在许多文本编辑器中,^A 通常是“全选”,^C 是“复制”,^V 是“粘贴”。这给你一个想法吗?
  • 我的意思是'A'的数量。例如,对于 N=7,我们可以使用击键 A、A、A、CTRL+A、CTRL+C、CTRL+V、CTRL+V 打印 9 个 A
  • 呃,那是 7 次击键。
  • @John "所有 CTRL+ 字符都被视为一次按键,因此 CTRL+A 是一次按键。"
  • 我已经删除了 C++ 标签,这纯粹是一个算法问题,希望它能防止不满意的 C++ 追随者投反对票/投票关闭。

标签: algorithm


【解决方案1】:

如果允许 N 次击键,则结果为 N-3。

A -> N-3

CTRL+A -> 选择那 N 个字符:+1

CTRL+C -> 复制那 N 个字符 :+1

Ctrl+V -> 粘贴 N 个字符。 :+1 即,(因为我们使用 CTRL+A 选择了整个字符)用复制的 N-3 个字符替换这些现有的 N-3 个字符(覆盖相同的字符),结果是 N-3。

【讨论】:

  • 欢迎来到 StackOverflow!了解如何添加content formatting 并可能使用实际的箭头符号。这将提高您答案的可读性!
【解决方案2】:

这是我的动态编程解决方案,没有嵌套循环,它还打印您需要输入的实际字符:

N = 52

count = [0] * N
res = [[]] * N
clipboard = [0] * N

def maybe_update(i, new_count, new_res, new_clipboard):
  if new_count > count[i] or (
      new_count == count[i] and new_clipboard > clipboard[i]):
    count[i] = new_count
    res[i] = new_res
    clipboard[i] = new_clipboard

for i in range(1, N):
  # First option: type 'A'.
  # Using list concatenation for 'res' to avoid O(n^2) string concatenation.
  maybe_update(i, count[i - 1] + 1, res[i - 1] + ['A'], clipboard[i - 1])

  # Second option: type 'CTRL+V'.
  maybe_update(i, count[i - 1] + clipboard[i - 1],  res[i - 1] + ['v'],
               clipboard[i - 1])

  # Third option: type 'CTRL+A, CTRL+C, CTRL+V'.
  # Assumption: CTRL+V always appends.
  if i >= 3:
    maybe_update(i, 2 * count[i - 3],  res[i - 3] + ['acv'], count[i - 3])

for i in range(N):
  print '%2d %7d %6d %-52s' % (i, count[i], clipboard[i], ''.join(res[i]))

这是输出('a'表示'CTRL+A'等)

 0       0      0                                                     
 1       1      0 A                                                   
 2       2      0 AA                                                  
 3       3      0 AAA                                                 
 4       4      0 AAAA                                                
 5       5      0 AAAAA                                               
 6       6      3 AAAacv                                              
 7       9      3 AAAacvv                                             
 8      12      3 AAAacvvv                                            
 9      15      3 AAAacvvvv                                           
10      18      9 AAAacvvacv                                          
11      27      9 AAAacvvacvv                                         
12      36      9 AAAacvvacvvv                                        
13      45      9 AAAacvvacvvvv                                       
14      54     27 AAAacvvacvvacv                                      
15      81     27 AAAacvvacvvacvv                                     
16     108     27 AAAacvvacvvacvvv                                    
17     135     27 AAAacvvacvvacvvvv                                   
18     162     81 AAAacvvacvvacvvacv                                  
19     243     81 AAAacvvacvvacvvacvv                                 
20     324     81 AAAacvvacvvacvvacvvv                                
21     405     81 AAAacvvacvvacvvacvvvv                               
22     486    243 AAAacvvacvvacvvacvvacv                              
23     729    243 AAAacvvacvvacvvacvvacvv                             
24     972    243 AAAacvvacvvacvvacvvacvvv                            
25    1215    243 AAAacvvacvvacvvacvvacvvvv                           
26    1458    729 AAAacvvacvvacvvacvvacvvacv                          
27    2187    729 AAAacvvacvvacvvacvvacvvacvv                         
28    2916    729 AAAacvvacvvacvvacvvacvvacvvv                        
29    3645    729 AAAacvvacvvacvvacvvacvvacvvvv                       
30    4374   2187 AAAacvvacvvacvvacvvacvvacvvacv                      
31    6561   2187 AAAacvvacvvacvvacvvacvvacvvacvv                     
32    8748   2187 AAAacvvacvvacvvacvvacvvacvvacvvv                    
33   10935   2187 AAAacvvacvvacvvacvvacvvacvvacvvvv                   
34   13122   6561 AAAacvvacvvacvvacvvacvvacvvacvvacv                  
35   19683   6561 AAAacvvacvvacvvacvvacvvacvvacvvacvv                 
36   26244   6561 AAAacvvacvvacvvacvvacvvacvvacvvacvvv                
37   32805   6561 AAAacvvacvvacvvacvvacvvacvvacvvacvvvv               
38   39366  19683 AAAacvvacvvacvvacvvacvvacvvacvvacvvacv              
39   59049  19683 AAAacvvacvvacvvacvvacvvacvvacvvacvvacvv             
40   78732  19683 AAAacvvacvvacvvacvvacvvacvvacvvacvvacvvv            
41   98415  19683 AAAacvvacvvacvvacvvacvvacvvacvvacvvacvvvv           
42  118098  59049 AAAacvvacvvacvvacvvacvvacvvacvvacvvacvvacv          
43  177147  59049 AAAacvvacvvacvvacvvacvvacvvacvvacvvacvvacvv         
44  236196  59049 AAAacvvacvvacvvacvvacvvacvvacvvacvvacvvacvvv        
45  295245  59049 AAAacvvacvvacvvacvvacvvacvvacvvacvvacvvacvvvv       
46  354294 177147 AAAacvvacvvacvvacvvacvvacvvacvvacvvacvvacvvacv      
47  531441 177147 AAAacvvacvvacvvacvvacvvacvvacvvacvvacvvacvvacvv     
48  708588 177147 AAAacvvacvvacvvacvvacvvacvvacvvacvvacvvacvvacvvv    
49  885735 177147 AAAacvvacvvacvvacvvacvvacvvacvvacvvacvvacvvacvvvv   
50 1062882 531441 AAAacvvacvvacvvacvvacvvacvvacvvacvvacvvacvvacvvacv  
51 1594323 531441 AAAacvvacvvacvvacvvacvvacvvacvvacvvacvvacvvacvvacvv 

【讨论】:

    【解决方案3】:

    在手动打印 mA 和使用 Ctrl+ACtrl+C 和 Nm-2 Ctrl+V。最好的解决方案是在中间。如果最大击键次数 = 10,则最好的解决方案是键入 5 个 A 或 4 个 A。

    尝试使用这个看看这个http://www.geeksforgeeks.org/how-to-print-maximum-number-of-a-using-given-four-keys/,也许会优化一点,寻找中点附近的结果。

    【讨论】:

      【解决方案4】:

      使用上面答案中提到的技巧,在数学上,解决方案可以用一个方程式解释为,

      4 + 4^[(N-4)/5] + ((N-4)%5)*4^[(N-4)/5]。 其中 [] 是最大整数因子

      【讨论】:

        【解决方案5】:

        这是我的方法和解决方案,代码如下。

        方法:

        可以执行三种不同的操作。

        1. Keystroke A - 输出一个字符'A'
        2. Keystroke (Ctrl-A) + (Ctrl-C) - 基本上什么都不输出。这两个击键可以组合成一个操作,因为这些击键中的每一个单独地都没有意义。此外,此按键设置了下一次粘贴操作的输出。
        3. 击键 (Ctrl-V) - 此击键的输出实际上取决于前一个(第二个)操作,因此我们需要在代码中考虑这一点。

        现在给定三个不同的操作及其各自的输出,我们必须遍历这些操作的所有排列。


        假设:

        现在,此问题的某些版本表明击键顺序 Ctrl+A -> Ctrl+C -> Ctrl+V 会覆盖突出显示的选择。为了考虑这个假设,只需将一行代码添加到下面的解决方案中,其中案例 2 中的打印变量设置为 0

                case 2:
                //Ctrl-A and then Ctrl-C
                    if((count+2) < maxKeys)
                    {
                        pOutput = printed;
        
                        //comment the below statement to NOT factor 
                        //in the assumption described above
                        printed = 0;    
                    }
        

        对于这个解决方案

        下面的代码将打印几个序列,最后一个序列是任何给定 N 的正确答案。例如对于 N=11,这将是正确的顺序

        假设

        A,A,A,A,A,C,S,V,V,V,V,:20:

        没有假设

        A,A,A,C,S,V,V,C,S,V,V,:27:

        我决定保留此解决方案的假设。


        击键图例:

        'A' - A

        'C' - Ctrl+A

        'S' - Ctrl+C

        'V' - Ctrl+V


        代码:

        #include <stdio.h>
        #include <string.h>
        #include <stdlib.h>
        
        void maxAprinted(int count, int maxKeys, int op, int printed, int pOutput, int *maxPrinted, char *seqArray)
        {
            if(count > maxKeys)
                return;
        
            if(count == maxKeys)
            {
                if((*maxPrinted) < printed)
                {
                    //new sequence found which is an improvement over last sequence
                    (*maxPrinted) = printed;
        
                    printf("\n");
                    int i;
                    for(i=0; i<maxKeys; i++)
                        printf(" %c,",seqArray[i]);
                }
        
                return;
            }
        
            switch(op)
            {
                case 1:
                //A keystroke
                    printed++;
        
                    seqArray[count] = 'A';
                    count++;
                    break;
        
                case 2:
                //Ctrl-A and then Ctrl-C
                    if((count+2) < maxKeys)
                    {
                        pOutput = printed;
        
                        //comment the below statement to NOT factor 
                        //in the assumption described above
                        printed = 0;    
                    }
        
                    seqArray[count] = 'C';
                    count++;
                    seqArray[count] = 'S';
                    count++;
                    break;
        
                case 3:
                //Ctrl-V
                    printed = printed + pOutput;
        
                    seqArray[count] = 'V';
                    count++;
                    break;
            }
        
            maxAprinted(count, maxKeys, 1, printed, pOutput, maxPrinted, seqArray);
            maxAprinted(count, maxKeys, 2, printed, pOutput, maxPrinted, seqArray);
            maxAprinted(count, maxKeys, 3, printed, pOutput, maxPrinted, seqArray);    
        }
        
        int main()
        {
            const int keyStrokes = 11;
        
            //this array stores the sequence of keystrokes
            char *sequence;
            sequence = (char*)malloc(sizeof(char)*(keyStrokes + 1));
        
            //stores the max count for As printed for a sqeuence
            //updated in the recursive call.
            int printedAs = 0;
        
            maxAprinted(0, keyStrokes,  1, 0, 0, &printedAs, sequence);
        
            printf(" :%d:", printedAs);
        
            return 0;
        }    
        

        【讨论】:

          【解决方案6】:

          有一个动态编程解决方案。我们一开始就知道 0 个密钥可以使我们成为 0 个 A。然后我们遍历i直到n,做两件事:按一次A,然后按全选+复制,然后粘贴j次(实际上是j-i-1;注意这里的技巧:内容仍然在剪贴板中,所以我们可以多次粘贴而不用每次都复制)。我们只需要考虑最多 4 次连续粘贴,因为 select、copy、paste x 5 等价于 select、copy、paste、select、copy、paste,后者更好,因为它在剪贴板中留下了更多内容。一旦我们到达n,我们就会得到想要的结果。

          复杂度可能看起来是 O(N),但由于数字以指数速度增长,由于乘以大数字的复杂性,它实际上是 O(N2)。下面是一个 Python 实现。计算 N=50,000 大约需要 0.5 秒。

          def max_chars(n):
            dp = [0] * (n+1)
            for i in xrange(n):
              dp[i+1] = max(dp[i+1], dp[i]+1) # press a
              for j in xrange(i+3, min(i+7, n+1)):
                dp[j] = max(dp[j], dp[i]*(j-i-1)) # press select all, copy, paste x (j-i-1)
            return dp[n]
          

          在代码中,j 表示在我们的新按键序列之后按下的按键总数。在这个阶段,我们已经有了i 按键,并且有 2 个新按键进入全选和复制。因此,我们点击粘贴j-i-2 次。由于粘贴添加到dp[i]A 的现有序列中,我们需要添加1 使其成为j-i-1。这解释了倒数第二行中的j-i-1

          以下是一些结果(n => A 的数量):

          • 7 => 9
          • 8 => 12
          • 9 => 16
          • 10 => 20
          • 100 => 1391569403904
          • 1,000 => 3268160001953743683783272702066311903448533894049486008426303248121757146615064636953144900245 174442911064952028008546304
          • 50,000 => a very large number!

          我同意@SB 的观点,即您应该始终陈述您的假设:我的观点是您不需要粘贴两次即可将字符数加倍。这得到了 7 的答案,所以除非我的解决方案是错误的,否则假设一定是正确的。

          如果有人想知道为什么我不检查 Ctrl+ACtrl+C 形式的序列kbd>, A, Ctrl+V: 最终结果总是和A, Ctrl+A, Ctrl+C, Ctrl+V确实考虑过。

          【讨论】:

          • n =&gt; result 还是result =&gt; n?不管怎样,我认为这是错误的。我们可以用 7 次击键输入 9 As。如果是n =&gt; result,那肯定是错的。可以输入的 As 数量不能低于n
          • @IVlad 这是n =&gt; result。您说“我们可以键入 9 与 7 次击键一样”,这就是我得到的。阅读我刚刚编辑的“技巧”。
          • 这看起来很棒,除了问题是找到给定击键次数的最大 As 数,而不是获得给定 As 数的最小击键数。
          • @marcog - 你的符号至少是令人困惑的,最多是错误的。 n 是您可以使用的按键。您必须计算可以使用n 击键键入多少个 As。所以7 =&gt; 7 没有意义。
          • 看起来不错,+1。现在让我们看看是否有人可以将其降为O(n) 甚至O(1) :)。
          【解决方案7】:

          需要 3 次击键才能使您的 As 数量翻倍。只有当您已经打印了 3 个或更多 As 时,才开始加倍。您希望最后一次允许的击键是 CtrlV 以确保将最大数字加倍,因此为了对齐它,我们将填写任何额外的击键在前三个 As 之后,以更多 As 开头。

          for (i = 3 + n%3; i>0 && n>0; n--, i--) {
              print("a");
          }
          
          for (; n>0; n = n-3) {
              print("ctrl-a");
              print("ctrl-c");
              print("ctrl-v");
          }
          

          编辑:

          这太糟糕了,我完全超前了,没有考虑为每个副本多次粘贴。

          编辑 2:

          我相信粘贴 3 次是最佳选择,前提是您有足够的击键次数。在 5 次击键中,您将 As 的数量乘以 4。这比使用 4 次击键乘以 3 更好,也比使用 6 次击键乘以 5 更好。我通过给每种方法相同的击键次数来比较这一点,足以让它们每个都同时完成一个循环(60),让 3 乘法器执行 15 个循环,4 乘法器执行 12 个循环,而 5-乘法器做 10 个循环。 3^15 = 14,348,907,4^12=16,777,216,5^10=9,765,625。如果只剩下 4 次击键,做一个 3 乘法器比再粘贴 4 次要好,本质上使之前的 4 乘法器变成了一个 8 乘法器。如果只剩下 3 次击键,最好使用 2 倍数。

          【讨论】:

            【解决方案8】:

            使用 CtrlA + CtrlC + CtrlV 只有在 4 个 A 之后才有优势。

            所以我会做这样的事情(在伪 BASIC 代码中,因为您没有指定任何适当的语言):

            // We should not use the clipboard for the first four A's:
            FOR I IN 1 TO MIN(N, 4)
                PRINT 'CLICK A'
            NEXT
            LET N1 = N - 4
            
            // Generates the maximum number of pastes allowed:
            FOR I IN 1 TO (N1 DIV 3) DO
                PRINT 'CTRL-A'
                PRINT 'CTRL-C'
                PRINT 'CTRL-V'
                LET N1 = N1 - 3
            NEXT
            
            // If we still have same keystrokes left, let's use them with simple CTRL-Vs
            FOR I IN N1 TO N
                PRINT 'CTRL-V'
            NEXT
            

            编辑

            1. 回到在主循环中使用单个 CtrlV
            2. 添加了一些 cmets 来解释我在这里要做什么。
            3. 修复了“前四个 A”块的问题。

            【讨论】:

            • @SB:我正在为 LAST 粘贴执行 CTRL-V。顺便说一句,这正是你在回答中所说的。这意味着我们的想法相似,所以我不知道你为什么要批评我——或者我可能遗漏了什么?
            • google 从不指定合适的写作语言,随你所愿。
            【解决方案9】:

            以下是我的处理方式:

            • 假设 CtrlA = 全选
            • 假设 CtrlC = 复制选择
            • 假设 CtrlV = 粘贴复制的选择

            给定一些文本,复制它需要 4 次击键:

            • CtrlA全选
            • CtrlC复制它
            • CtrlV 粘贴(这将粘贴在选择上 - 说明您的假设)
            • CtrlV 再次粘贴,将其翻倍。

            从那里,您可以考虑做 4 或 5 个 A,然后循环执行上述操作。请注意,执行ctrl + a, c, v, v 将使您的文本在循环时呈指数增长。如果剩余笔划 CtrlV

            在像 Google 这样的地方进行面试的关键是陈述你的假设,并传达你的想法。他们想知道你是如何解决问题的。

            【讨论】:

            • 面试技巧的好点,得到正确答案不如最终清楚地沟通重要!
            • 好答案。对于该算法,一个贪心的二乘错误:ACVV-VVVVV 乘以 7,ACVV-ACVV-V 乘以 6。所以 Ctrl-V 用于剩余笔划
            【解决方案10】:
            public int dp(int n) 
            {
                int arr[] = new int[n];
                for (int i = 0; i < n; i++)
                    arr[i] = i + 1;
                for (int i = 2; i < n - 3; i++) 
                {
                    int numchars = arr[i] * 2;
                    int j = i + 3;
                    arr[j] = Math.max(arr[j], numchars);
                    while (j < n - 1) 
                    {
                        numchars = numchars + arr[i];
                        arr[++j] = Math.max(arr[j], numchars);
                    }
                }
                return arr[n - 1];
            }
            

            【讨论】:

              【解决方案11】:

              它可以在 O(1) 中求解:与斐波那契数一样,有一个公式可以计算打印的 As 的数量(以及击键的顺序):


              1)我们可以简化问题描述:

              • 只有 [A]、[C-a]+[C-c]、[C-v] 和一个空的复制粘贴缓冲区

              等于

              • 复制粘贴缓冲区中只有 [C-a]+[C-c]、[C-v] 和“A”。

              2) 我们可以将击键序列描述为 {'*','V','v'} 中的 N 个字符组成的字符串,其中 'v' 表示 [Cv],'*' 表示 [Ca] 'V' 表示 [Cc]。示例:“vvvv*Vvvvv*Vvvv”

              那个字符串的长度仍然等于 N。

              该字符串中 Vv-words 长度的乘积等于产生的 As 的数量。


              3) 给定字符串的固定长度 N 和固定数量的单词 K,如果所有单词的长度几乎相等,则结果将是最大的。它们的成对差异不超过±1。

              现在,如果给定 N,最佳数字 K 是多少?


              4) 假设,我们想通过添加一个长度为 L 的单词来增加单词的数量,那么我们必须将 L+1 乘以任何前一个单词减少一个 'v'。示例:“…*Vvvv*Vvvv*Vvvv*Vvvv”->“…*Vvv*Vvv*Vvv*Vvv*Vvv”

              现在,最佳字长 L 是多少?

              (5*5*5*5*5) (3*3*3*3)*3

              => 最佳值是 L=4。


              5) 假设,我们有足够大的 N 来生成一个包含许多长度为 4 的单词的字符串,但还剩下几个击键;我们应该如何使用它们?

              • 如果剩下 5 个或更多:附加另一个长度为 4 的单词。

              • 如果还剩 0 个:完成。

              • 如果还剩 4 个:我们也可以

                a) 追加一个长度为 3 的单词:4*4*4*4*3=768。

                b) 或增加 4 个单词到长度 5:5*5*5*5=625。 => 附加一个词更好。

              • 如果还剩 3 个:我们也可以

                a) 或通过将前一个单词的长度从 4 调整为 3 来追加一个长度为 3 的单词:4*4*4*2=128

                b) 增加 3 个单词到长度 5:5*5*5=125。 => 附加一个词更好。

              • 如果还剩 2 个:我们也可以

                a) 或通过将前面的两个单词从长度 4 调整为 3 来追加一个长度为 3 的单词:4*4*1=16

                b) 增加 2 个单词到 5 的长度:5*5=25。 => 附加一个词更好。

              • 如果还剩 1 个:我们也可以

                a) 或通过将前面的三个单词从长度 4 调整为 3 来追加一个长度为 3 的单词:4*4*4*0=0

                b) 增加一个词到长度 5:4*4*5=80。 => 附加一个词更好。


              6) 现在,如果我们没有“足够大的 N”来使用 5) 中的规则怎么办?如果可能,我们必须坚持计划 b)! 小 N 的字符串是:

              1:"v", 2:"vv", 3:"vvv", 4:"vvvv"

              5:"vvvvv" → 5 (plan b)

              6:"vvvvvv" → 6 (方案 b)

              7:"vvv*Vvv" → 9(计划a)

              8:"vvvv*Vvv" → 12(方案a)

              9:"vvvv*Vvvv" → 16

              10:"vvvv*Vvvvv" → 20 (方案b)

              11:"vvv*Vvv*Vvv" → 29(方案a)

              12:"vvvv*Vvv*Vvv" → 36(方案a)

              13:"vvvv*Vvvv*Vvv" → 48(方案a)

              14:"vvvv*Vvvv*Vvvv" → 64

              15:"vvv*Vvv*Vvv*Vvv" → 81(方案a)


              7) 现在,一个长度为 N 的字符串中的最佳单词数 K 是多少?

              如果 N K=ceil((N+1)/5)

              用 C/C++/Java 编写:int K = (N&lt;7)?(1) : (N&lt;11)?(2) : ((N+5)/5);

              如果 N > 10,那么长度为 3 的单词数将为:K*5-1-N。有了这个,我们就可以计算出打印出来的As个数了:

              如果 N > 10,则 As 的数量为:4^{N+1-4K}·3^{5K-N-1}

              【讨论】:

              • 似乎是对的,适用于@Andrew 的答案给出的示例,但你的答案也是 O(log N) 而不是 O(1),对吧?
              • 怎么可能是 O(log N)?计算 As 数量的数学公式在 O(1) 中计算。打印击键的算法是 O(N),因为有 O(N) 击键要打印,或者是 O(1),如果您允许将其打印为正则表达式。
              • 计算幂是 O(log N),因为 4 上的指数随着 N 的增加而增加。如果你打印出因数形式的 As 的数量,它是 O(1)。
              • 啊,好的。从来没有想过用整数算术实际计算数字。我只对公式或浮点近似感兴趣。但当然,为了能够将其与其他数字进行比较,它必须精确计算。
              【解决方案12】:

              通过使用marcog 的解决方案,我发现了一个以n=16 开头的模式。为了说明这一点,这里是 n=24n=29 的击键,为了便于阅读,我将 ^A 替换为 S(选择),将 ^C 替换为 C(复制),将 ^V 替换为 P(粘贴):

              24: A,A,A,A,S,C,P,P,P,S,C,P,P,P,S,C,P,P,P,S,C,P,P,P
                     4   *    4    *    4    *    4    *    4     = 1024
              25: A,A,A,A,S,C,P,P,P,S,C,P,P,S,C,P,P,S,C,P,P,S,C,P,P
                     4   *    4    *   3   *   3   *   3   *   3    = 1296
              26: A,A,A,A,S,C,P,P,P,S,C,P,P,P,S,C,P,P,S,C,P,P,S,C,P,P
                     4   *    4    *    4    *   3   *   3   *   3    = 1728
              27: A,A,A,A,S,C,P,P,P,S,C,P,P,P,S,C,P,P,P,S,C,P,P,S,C,P,P
                     4   *    4    *    4    *    4    *   3   *   3    = 2304
              28: A,A,A,A,S,C,P,P,P,S,C,P,P,P,S,C,P,P,P,S,C,P,P,P,S,C,P,P
                     4   *    4    *    4    *    4    *    4    *   3    = 3072
              29: A,A,A,A,S,C,P,P,P,S,C,P,P,P,S,C,P,P,P,S,C,P,P,P,S,C,P,P,P
                     4   *    4    *    4    *    4    *    4    *    4     = 4096
              

              在最初的 4 个 As 之后,理想的模式是选择、复制、粘贴、粘贴、粘贴和重复。这将每 5 次击键将 As 的数量乘以 4。如果这 5 次击键模式不能自行消耗剩余的击键,则某些数量的 4 击键模式 (SCPP) 会消耗最后的击键,并根据需要替换 SCPPP(或删除其中一种粘贴)。 4 种击键模式每 4 次击键将总数乘以 3。

              这里使用这个模式是一些 Python 代码,它得到与 marcog 的解决方案相同的结果,但是是 O(1) edit:由于求幂,这实际上是 O(log n),感谢 IVlad指出这一点。

              def max_chars(n):
                if n <= 15:
                  return (0, 1, 2, 3, 4, 5, 6, 9, 12, 16, 20, 27, 36, 48, 64, 81)[n]
                e3 = (4 - n) % 5
                e4 = n // 5 - e3
                return 4 * (4 ** e4) * (3 ** e3)
              

              计算 e3: 击键列表末尾总是有 0 到 4 个 SCPP 模式,n % 5 == 4 有 4 个,n % 5 == 1 有 3 个,n % 5 == 2 有 2 个,n % 5 == 3 有 1 个,n % 5 == 4有0个。这可以简化为(4 - n) % 5

              计算 e4: 每当n % 5 == 0 时,模式的总数就会增加 1,因为事实证明这个数字正好增加到n / 5。使用地板除法我们可以得到模式总数,e4 的总数是模式总数减去e3。对于不熟悉 Python 的人,// 是面向未来的楼层除法符号。

              【讨论】:

              • 不错的一个!经过测试,它适用于n=3000,所以它可能是正确的。 (可惜我今天没票了:/)
              • +1,非常好。次要的挑剔:它不是真正的O(1),因为幂运算不能在恒定时间内完成。这是O(log n)
              • 实际上,'SCPPP' 序列只会将字符数乘以 3:第一次粘贴只是覆盖所选文本。
              • @Nick 问题的最后一行:“序列 Ctrl+A、Ctrl+V、Ctrl+C 不会覆盖现有选择。它会将复制的选择附加到选定的选择。”跨度>
              • @marcog 是的,我没有注意到这一点。不过,我不知道有任何操作系统以这种方式运行。
              【解决方案13】:

              接下来使用 OP 的第二次编辑,即粘贴替换现有文本。

              注意几点:

              • ^A 和 ^C 可以被认为是需要两次击键的单个操作,因为单独执行它们从来没有意义。事实上,我们可以用 ^K^V 替换 ^A^C 的所有实例,其中 ^K 是 一键“剪切”操作(我们将其缩写为 X)。我们将看到处理 ^K 比处理两个成本的 ^A^C 要好得多。
              • 假设在剪贴板中以“A”开头。那么^V(我们缩写为Y)严格优于A,我们可以不考虑后者。 (在实际问题中,如果剪贴板一开始是空的,接下来我们将把 Y 替换为 A 而不是 ^V 直到第一个 X。)

              因此,每个合理的击键序列都可以解释为一组由 X 分隔的 Y,例如 YYYXYXYYXY。用 V(s) 表示序列 s 产生的 'A' 的数量。那么 V(nXm) = V(n)*V(m),因为 X 本质上用 V(n) 'A's 替换了 m 中的每个 Y。

              因此,复制粘贴问题与以下问题同构:“使用总和为 N-m 的 m+1 个数,最大化它们的乘积。”例如,当 N=6 时,答案是 m=1 和数字 (2,3)。 6 = 2*3 = V(YYXYYY) = V(AA^A^C^V^V)(或V(YYXYY) = V(AAA^A^C^V)。)

              我们可以做一些观察:

              对于m固定 值,要选择的数字是ceil( (N-m)/(m+1) )floor( (N-m)/(m+1) )(无论组合如何计算总和;更具体地说,您需要(N-m) % (m+1) @ 987654325@ 和其余的 floors)。这是因为,对于a &lt; b(a+1)*(b-1) &gt;= a*b

              不幸的是,我没有找到找到m 值的简单方法。如果这是我的面试,我会在这一点上提出两个解决方案:

              选项 1. 遍历所有可能的 m。一个 O(n log n) 解决方案。

              C++ 代码:

              long long ipow(int a, int b)
              {
                long long val=1;
                long long mul=a;
              
                while(b>0)
                  {
                    if(b%2)
                  val *= mul;
                    mul *= mul;
                    b/=2;
                  }
                return val;
              }
              
              long long trym(int N, int m)
              {
                int floor = (N-m)/(m+1);
                int ceil = 1+floor;
                int numceils = (N-m)%(m+1);
                return ipow(floor, m+1-numceils) * ipow(ceil, numceils);
              }
              
              long long maxAs(int N)
              {
                long long maxval=0;
                for(int m=0; m<N; m++)
                  {
                    maxval = std::max(maxval, trym(N,m));
                  }
                return maxval;
              }
              

              选项 2. 允许 m 获得非整数值,并通过取 [(N-m)/(m+1)]^mm 的导数并求解其根来找到其最优值。没有解析解,但可以使用例如找到根。牛顿法。然后使用该根的地板和天花板作为m 的值,并选择最佳的。

              【讨论】:

                【解决方案14】:

                假设剪贴板中有 x 个字符,文本区域中有 x 个字符;我们称之为“状态 x”。

                让我们按几次“粘贴”(为方便起见,我用m-1 表示),然后按“全选”和“复制”;在这个序列之后,我们到达“状态 m*x”。 在这里,我们总共浪费了 m+1 次击键。 所以渐近增长(至少)类似于f^n,其中 f = m^(1/(m+1))。 我相信这是最大可能的渐近增长,尽管我还不能证明这一点。

                尝试不同的 m 值表明,对于 m=4,获得了 f 的最大值。

                让我们使用以下算法:

                Press A a few times
                Press Select-all
                Press Copy
                Repeat a few times:
                    Press Paste
                    Press Paste
                    Press Paste
                    Press Select-all
                    Press Copy
                While any keystrokes left:
                    Press Paste
                

                (不确定它是否是最佳的)。

                一开始按A的次数是3次:如果按4次,你就错过了再按3次A的次数翻倍的机会。

                最后按Paste的次数不超过5次:如果还剩6次或以上,可以使用Paste、Paste、Paste、Select-all、Copy、Paste代替。

                所以,我们得到以下算法:

                If (less than 6 keystrokes - special case)
                    While (any keystrokes left)
                        A
                Else
                    First 5 keystrokes: A, A, A, Select-all, Copy
                    While (more than 5 keystrokes left)
                        Paste, Paste, Paste, Select-all, Copy
                    While (any keystrokes left)
                        Paste
                

                (不确定它是否是最佳的)。执行此操作后的字符数类似于

                3 * pow(4, floor((n - 6) / 5)) * (2 + (n - 1) % 5).

                样本值:1,2,3,4,5,6,9,12,15,18,24,36,48,60,72,96,144,192,240,288,...

                【讨论】:

                  猜你喜欢
                  • 2015-01-10
                  • 2016-01-19
                  • 2013-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-10-11
                  • 2014-01-29
                  • 1970-01-01
                  • 2010-12-03
                  相关资源
                  最近更新 更多