二幂拆分问题

转载请注明出处,http://blog.csdn.net/Bule_Zst/article/details/77111983


问题描述:

任意一个整数x都可以写成一系列2的幂的组合,这里的组合是指用加减法把它们接连起来。令f(x)为x需要的最少的2的幂的项数,求f(x)。

如:

7=20+21+22
7=2320

f(7)=2

声明:

因为 f(x)=f(x) , 所以后文一致假设x非负。另外令 f(0)=0


下面介绍三种算法,由易到难,且算法之间存在联系。

算法1:

令floor2(x)表示不大于x的最大2幂, ceil2(x)表示不小于x的最小2幂,最优的组合要么是floor2(x)加上其它一些项,或者是ceil2(x)减去其它一些项。
于是可以得到求f(x)的一个递归过程如下:

def f(x):
    if x == 0: return 0
    l = floor2(x)
    u = ceil2(x)
    if l == u: return 1
    return 1 + min(f(x - l), f(u - x))


算法2:

观察算法1会发现,有很多的计算都是重复的
如对于对于求 f(23)
这里写图片描述

测试代码:


def floor2( x ):
    a = 1
    while a < x:
        a = a * 2
    if a == x:
        return a
    else:
        return a / 2

def ceil2( x ):
    a = 1
    while a < x:
        a = a * 2
    return a

def f(x):
    print x
    if x == 0:
        return 0
    l = floor2(x)
    u = ceil2(x)
    if l == u:
        return 1
    return 1 + min(f(x - l), f(u - x))

print f( 23 )

因此可以使用一个数组存储已经计算过的结果:

def f(x):
    if x == 0: return 0
    if x in table: return table[x]
    l = floor2(x)
    u = ceil2(x)
    if l == u: return 1
    r = 1 + min(f(x - l), f(u - x))
    table[x] = r
    return r


算法3:

算法2是一个从本身串层层向下递推的过程,因此需要额外内存存储结果,因此想到,可以改进算法2为由下向上的过程。

分析算法2的递推过程:

x=(1010110)2

xfloor2(x)=(0010110)2
ceil2(x)x=(10000000)2(1010110)2=(0101010)2= ~ x+1

我们会发现, xfloor2(x) 表示的是自身串从左往右数,第二个1之后的子串, ceil2(x)x 表示的是自身串从左往右数,第一个0之后的子串取反加1

因此,算法3的思路就是,从右往左遍历自身串,用u表示以1开头的串的 f(x) 值,用d表示以0开头的串取反加一的 f(x)

从右往左扫,扫到1则更新u,扫到0则更新d

自身串最右边刚开始的那些0是没有用的,一直扫到1开始更新,u、d赋初值1

代码中的>>1不要当成除以2,而是看做二进制字符串的右移。&1 == 1表示最后一位是1.

def f(x):
    if x == 0: return 0
    while x & 1 == 0: x = x >> 1
    u = 1
    d = 1
    x = x >> 1
    while x > 0:     
        if x & 1 == 1:
            u = min(u, d) + 1
        else:
            d = min(u, d) + 1
        x = x >> 1
    return u



2017年9月6日更新

今天重新看了这篇文章,发现自己竟然看不懂了,what?现在终于又懂了,再把思路重新梳理一下。

前两个方法没什么好说的,关键是第三个方法,里面的u与v。

u指的是当前二进制字符串(以1开头)从左往右第二个1开头的新串的 f(x) 值,v指的是当前二进制字符串从左往右第一个0开头的新串取反加一得到的新新串的 f(x) 值( f(x) 中的x指的是新串与新新串)

举个例子就懂了,这个方法确实很神奇

1010110
初始,
u: 10: 1
v: 10: 1
PS:10表示代表的串,1表示需要二进制数的个数

计算110,因为1开头,所以更新u
u=u+1 : 100 + u
u=v+1 : 1000 - v
结果:u: 110: 2

计算1010(原串:0110),更新v
v=u+1 : 10000 - u
v=v+1 : 1000 + v
结果:v: 1010: 2

计算10110,更新u
u=u+1 : 10000 + u
u=v+1 : 100000 - v
结果:u: 10110: 3

计算101010(原串:010110),更新v
v=u+1 : 1000000 - u
v=v+1 : 100000 + v

以此类推

参考文章:

http://blog.csdn.net/howardemily/article/details/74938030
http://blog.csdn.net/howardemily/article/details/74938033

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值