[博客迁移][算法] 二幂拆分问题

312 阅读3分钟

from 二幂拆分问题


问题描述:

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

如:

7 = 2^0 + 2^1 + 2^2 7 = 2^3 - 2^0

\therefore 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

x - floor2(x) = (0010110)_2 ceil2(x) - x = (10000000)_2 - (1010110)_2 = (0101010)_2 =~x + 1

我们会发现,x - floor2(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

以此类推

参考文章:

blog.csdn.net/howardemily… blog.csdn.net/howardemily…