Leetcode:No.651 4 Keys Keyboard

Imagine you have a special keyboard with the following keys:
Key 1: (A): Prints one 'A' on screen.
Key 2: (Ctrl-A): Select the whole screen.
Key 3: (Ctrl-C): Copy selection to buffer.
Key 4: (Ctrl-V): Print buffer on screen appending it after what has already been printed.
Now, you can only press the keyboard for N times (with the above four keys), find out the maximum numbers of 'A' you can print on screen.
Example 1:
Input: N = 3
Output: 3
Explanation:
We can at most get 3 A's on screen by pressing following key sequence:
A, A, A
Example 2:
Input: N = 7
Output: 9
Explanation:
We can at most get 9 A's on screen by pressing following key sequence:
A, A, A, Ctrl A, Ctrl C, Ctrl V, Ctrl V
Note:

  1. 1 <= N <= 50
  2. Answers will be in the range of 32-bit signed integer.

看起来和2 keys keyboard有点像,但是并不一样。这里全选(以下简称a),复制(以下简称c),粘贴(以下简称v)比之前多了一步,而且问题是给步数n求能得到最大A的数量。(平A=直接按A)

注意这个一开始没有A,一个也没有。
可以先自己手算前几个(r代表结果result):
n=1,r=1
n=2,r=2
n=3,r=3
这几个非常明显。这时候应该稍微有点意识了,acv三步走实现加倍,如果本身比3小,那是不如平A的。
n=4,r=4
这时候n比3大,但还是不能用acv,因为acv必须要求至少3步,少了什么也没有,如果这里硬要用的话只能对1个A使用,太不划算。
这时候应该能想到,要想让acv三步至少不亏,那么之前至少要留有3个A,也就是说n=6的时候不亏。即:
n=6,r=6
前面的5自然还是一个个A拼出来:n=5,r=5

n=7的时候,又是一个需要思考的地方。按前面说的,6个A可以通过平A得到,也可以3个A再acv,acv的方法肯定更优,因为给后面留下了继续v的可能。
所以如果从n=6加v,结果是9.
当然还需要考虑使用acv的不同情况。可能性已经有不少了,比如只使用一轮acv+(+指1个或多个),或者极限的话可以在A之后使用连续两轮acv。这些结果都不如9大,但是可以提供一些思路。

到这里其实没太多必要继续推下去了。从最优角度来看,除了前面平A,后面应该都要用acv+及其组合。因为这里只给了步数,并不存在要套数字或者溢出的问题,所以尽量争取A的数量最多。
也就是说最优解应该是这种形状:
初始若干平A + (acv+)+(acv+)...
关于acv的效率,3步相当于x2,4步(acvv)相当于x3,5步相当于x4,或者设acv+步数为k,那么效果是x(k-1)。
说了这么多,有什么用呢?
其实这些就是问题的本质。相当于动态规划。因为总步数一定,目的就是让这些数乘起来尽可能大。
当然这里面还掺杂了初始平A这种东西。至少得3次平A之后再使用acv。

其实有鉴于上一道题,我本来一开始想尝试数学解法,结果还是不行。看来面试的时候还是直接奔DP比较靠谱。当然有大神掏出了O(1)的数学解法,我表示牛到不行,只能佩服。后面有讲,总之数学解法难度是ACM级别的(臆测)。

DP就不同了,只要知道关系式就行。
这里的话,很明显有子问题。拿上面的形状来说:
初始若干平A + (acv+)+(acv+)...
我们可以单独把最后一个acv+拿出来,拿它的长度做文章。
设dp[i]是i步能获得的最大A数量,因为它总是以acv+结尾(假设i>=6),其长度在3~(i-3)之间。我们可以遍历这个长度,结合之前剩下的dp,来取一个最大值。也就是说

for j in xrange(3, i - 2):
    dp[i] = max(dp[i], dp[i-j]*(j-1))

更详细的解释:i指目前我们考虑的下标,j是最后acv+的长度,按照之前说的acv+长度为j,那么效果就是乘以(j-1),剩下的部分长度为i-j,之前已经算过了直接拿出来就好。要做的就是遍历长度j可能的值计算dp[i],然后取一个最大值。

完整代码:

# Time:  O(n^2)
# Space: O(n)
class Solution(object):
    def maxA(self, n):
        dp = [_ for _ in xrange(n + 1)]
        for i in xrange(7, n + 1):
            for j in xrange(3, i - 2):
                dp[i] = max(dp[i], dp[i - j] * (j - 1))
        return dp[-1]

时间复杂度不能说很好,但是怎么说也算是解答了问题。

然后是数学解法,参加一亩三分地的帖子
还是回到上面的动态规划那里,转化为如下动态规划问题:

已知N,求x0 * x1 * ... * xk最大值,有:
x0 + x1 + ... + xk = N - k
k >= 0
x0 >= 1
x1, ..., xk >= 2

这里的x0就是初始平A,x1~xk就是acv+,其长度>=3,乘起来效果就是>=2。
那么给定N,如何求k呢?
4 * (k + 1) - delta = N - k, where 0 <= delta <= 4
k = (N + delta - 4) / 5 = N / 5
这里需要解释一下。结论就是优先使用4作为乘数,其次用3.
为什么?这里有一个“贡献因子”的理论,也就是说长度为x贡献x-1的乘数,贡献因子是(x-1)^(1/x),
x=2因子为1,x=3因子为1.26,x=4因子为1.316,x=5因子为1.320,x=6因子为1.308.
也就是说acv+的长度尽可能取5,其次是4,反映到乘数里面就是优先用4,其次用3了。

具体代码如下:

# Time:  O(1)
# Space: O(1)
    def maxA(self, N):
        """
        :type N: int
        :rtype: int
        """
        if N < 7: return N
        if N == 10: return 20  # the following rule doesn't hold when N = 10

        n = N // 5 + 1  # n3 + n4 increases one every 5 keys
        # (1) n     =     n3 +     n4
        # (2) N + 1 = 4 * n3 + 5 * n4
        #     5 x (1) - (2) => 5*n - N - 1 = n3
        n3 = 5 * n - N - 1
        n4 = n - n3
        return 3 ** n3 * 4 ** n4

n就是上面的k+1,理想情况下由x3的因子n3和x4的因子n4组成。
然后呢?就看不懂了……

又想了一下,这个意思应该是把平A也假设成为3或者4,否则结果里面没有说不过去。
那为什么只是N+1= 4 * n3 + 5 * n4呢?
因为作者把平A也归纳到n里面去了!换言之,作者把x0转化当成了一个acv+!
想想无论是平A=3还是4,就乘积效果而言相比acv+,都是省1步的!
所以这么写更好理解:N = 4 * n3 + 5 * n4 - 1
之后就一切自然而然了,然而还有一个特例就是10不遵守这个条件……
作者原话是:

10 is special here because it's the only > 6 number where there is no enough factors to share cuts from decrement of the number of 3's which means a 5 has to be introduced.

按照之前的算法n=3,然而即使是最小的组合3+4+4也超了,所以不成立。反映到计算就是n4=-1.
总而言之,这个算法充满神奇,多个大胆的假设,然后最后还是对的。临场还是别抱数学解法的想法吧……

彩蛋:一个基于数学解法的DP……

# Time:  O(n)
# Space: O(1)
class Solution2(object):
    def maxA(self, N):
        """
        :type N: int
        :rtype: int
        """
        if N < 7: return N
        dp = range(N + 1)
        for i in xrange(7, N + 1):
            dp[i % 6] = max(dp[(i - 4) % 6] * 3, dp[(i - 5) % 6] * 4)
        return dp[N % 6]

我一开始不太明白i%6是否有特别含义,对我而言这是滚动DP,按道理3个格子够用了?
想一想,i=7的时候需要dp[2]和dp[3]的值,为了不冲突,所以把值放到dp[1],很合理的解释。
试了一下,把所有%6改成%7,结果不变,说明我的猜测是正确的。事实上可以单独拿3个格子出来装,不过牵涉到初始值的问题麻烦一点。
另外dp = range(N + 1)改成dp = range(7)也不会有任何问题。

总结

数学解法超乎想象。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容