想象一下,您有一个特殊的键盘,它具有以下按键:
键1:(A):在屏幕上打印一个“ A”。
键2:(Ctrl-A):选择整个屏幕。
键3:(Ctrl-C):将所选内容复制到缓冲区。
键4:(Ctrl-V):在屏幕上打印缓冲区,将缓冲区追加到 已被打印。
现在,您只能按键盘N次(对于上述四次 键),找出可以在屏幕上打印的最大A数。
示例1:
输入:N = 3
输出:3
说明:按以下键序列,我们最多可以在屏幕上获得3 A的字样:A, A, A
示例2:
输入:N = 7
输出:9
说明:按以下键序列,我们最多可以在屏幕上获得9 A:A, A, A, Ctrl A, Ctrl C, Ctrl V, Ctrl V
(免责声明:我不想听到其他解决方案。我只是想了解我所缺少的内容以及解决方法。)
这是我当前的(错误的)解决方案(下面的解释):
class Solution:
def maxA(self, N):
screen = [0] * N
screen[0] = 1
applied_clipboard = clipboard = select = 0
for i in range(1, N):
if i < 3:
screen[i] = screen[i-1] + 1
else:
screen[i] = max(screen[i-3] + clipboard, screen[i-1] + 1, screen[i-1] + applied_clipboard)
if screen[i] == screen[i-3] + clipboard:
applied_clipboard = clipboard
select, clipboard = max(select, screen[i-1]), max(clipboard, select)
return screen[-1]
我正在跟踪一些状态,如上面的代码所示。状态是:
有了这些状态,我相信我可以在每个步骤中做出最佳决策。
但是,我的代码不正确。对于N = 11
的输入,我的代码返回27
,正确的值为27
。但是,对于N=9
和N=10
,正确的值分别是16
和20
,但是我分别得到15
和18
)。
有人以我更新状态的方式看到该错误吗?
编辑。回应现有答案: 我了解我的状态更新可能不完整,但问题是在哪里。我的想法是使每个状态都最大化,因为有一系列单独的决策来使每个状态最大化。然后,在接下来的迭代中使用所有这些信息来更新状态。
重写我的代码以保留每个片段的状态。
class Solution:
def maxA(self, N):
screen = [0] * N
screen[0] = 1
applied_clipboard, clipboard, select = [[0] * N for _ in range(3)]
for i in range(1, N):
if i < 3:
screen[i] = screen[i-1] + 1
else:
screen[i] = max(screen[i-3] + clipboard[i-1], screen[i-1] + 1, screen[i-1] + applied_clipboard[i-1])
if screen[i] == screen[i-3] + clipboard[i-1]:
applied_clipboard[i] = clipboard[i-1]
else:
applied_clipboard[i] = applied_clipboard[i-1]
select[i] = max(select[i-1], screen[i-1])
clipboard[i] = max(clipboard[i-1], select[i-1])
print('screen', screen)
print('clipboard', clipboard)
print('applied', applied_clipboard)
return screen[-1]
我想让我感到困惑的是,如何保留已应用的剪贴板值(applied_clipboard
)与剪贴板的最大状态。但是我需要区分两者,因为这会影响递归关系。
感谢user3386109
使我看到了这一点:
N=9
采取的一系列正确步骤是AAAASCVVV
(总共16个A)N=9
输出AAASCVVVV
(总共15个A)。其中s =选择,c =复制,v =粘贴。
答案 0 :(得分:2)
错误很简单,就是代码在每个步骤都最大化了屏幕上'A'的数量。如果print screen
为9时在函数的末尾N
,您将看到以下信息:
[1, 2, 3, 4, 5, 6, 9, 12, 15]
请注意,当N
为6时,屏幕上“ A”的最大数量为6。这可以通过三种不同的方式实现:
AAAAAA
AAscvv
AAAscv
其中s =选择,c =复制,v =粘贴。
但是,当N
为9时,正确答案为
AAAAscvvv
如您所见,按下6键后,屏幕上的“ A”数仅为4,而不是6。因此,在每一步中最大化屏幕上的“ A”数并不能给出正确的答案。
答案 1 :(得分:2)
一种思考的方法是,我们的解决方案必须以paste
结尾,因为显然我们不想以select
或copy
结尾来浪费印刷机。然后,我们可以假设我们要选择粘贴的屏幕状态是最佳的(否则,为什么要从中粘贴?)。
让f(n)
代表可以输出的A
的最大数量。然后,我们可以选择在3次或更多次按后重复应用任何屏幕状态。例如,在第七次按时,我们可以回头说:要重复屏幕状态4(仅为4),我们需要按3次,因此从4重复将提供我们的记录,即7、4 + 4 = 8 {{1} } s。如果我们尝试从3开始重复,则需要3次按压才能重复1次,使我们达到6次,我们仍然可以再按1次,所以3 + 2 * 3 =9。因此,重复应用屏幕状态3将提供记录连续7九个A
,这是最好的。对于任何A
,我们可以获得第0 < j < i-3
个屏幕状态的1 + i - (j + 3) + 1
或i - j - 1
倍。
JavaScript代码:
j
检查数据,我发现要复制的最佳屏幕状态似乎始终非常接近function f(n) {
const m = [0,1,2,3,4,5,6].concat(
new Array(Math.max(n-6,0)).fill(0));
for (let i=7; i<=n; i++)
for (let j=1; j<=i-3; j++)
m[i] = Math.max(m[i], m[j]*(i-j-1));
console.log(JSON.stringify(m));
return m[n];
}
console.log(4, f(4));
console.log(7, f(7));
console.log(9, f(9));
console.log(10, f(10));
console.log(11, f(11));
(向后6个状态),并且向后遍历时似乎始终是凸面的,这意味着我们可以通过向后遍历来进行优化并寻找向下的变化。
如果这种行为是一致的,则意味着该算法具有n
复杂性。
O(n)
答案 2 :(得分:1)
据我所知(现在,我已经更改了代码以使其可运行),您正在进行更新。我怀疑问题在于状态更新不完整。不幸的是,我认为您的实现是正确的,但是您的算法是错误的。
我认为,您的问题是每次迭代screen
都会使您的长期目标短路。在每次迭代中,您都将处理选择,就好像此按键必须是您的最后一次。结果,您有时会放弃潜力更大的剪贴板,而只用最能使您A
来完成此步骤的更新。
您需要为每个步骤维护多个有用的状态:不仅是最大的屏幕显示,还包括丰富的剪贴板的潜力。
更新 我认为我们现在在一起思考。您需要为每种击键次数存储多个状态(部分解决方案)。这不是简单的递归关系:它更多是量子状态。
例如,在7次击键时,您需要表示至少两个状态。
A
,剪贴板中有4个。 (8,4)A
,剪贴板中有3个。 (9,3)您当前的算法在每次击键次数中分别只为screen
和clipboard
提供一个值。它仅保留具有最佳短期结果的解决方案。它错过了(8,4)是9和10次击键的更好垫脚石。
您需要保留每个步骤中不占优势的所有对。当G(screen) >= H(screen)
和 G(clipboard) >= H(clipboard)
时,状态G“支配”状态H。
还要注意,您需要在状态中使用较少的元素:当前选择的元素。例如,考虑上述两种状态的情况(为便于说明),进一步沿中风(粘贴),以及“提前计划”的另一种状态:
scr clip sel
12 4 0 Nothing selected
12 3 0 Nothing selected
9 3 9 Stopped at 9 A's and did a select-copy
第三个状态将立即超过前两个状态,又走了两个步骤。