[英]Count Min Binary Operations in Python
问题:给定一个正数 integer n
,在一次操作中,选择任何i >= 0
并将n
转换为n + 2^i
或n - 2^i
。 找出将n
转换为 0 所需的最少操作次数。例如, n = 5
。 n
可以通过两个操作减少为 0:5 - 2^0 - 2^2 = 0。
我的解决方案适用于我手动测试过的每个案例:
def get_min_operations(n: int) -> int:
binary_representation = "{0:b}".format(n)
# reverse the string so i = 0 is the least significant bit
binary_representation = binary_representation[::-1]
for i, bit in enumerate(binary_representation):
if bit == "0":
continue
if (i == len(binary_representation) - 1) or binary_representation[i + 1] == "0": # isolated 1
return 1 + get_min_operations(n - 2 ** i)
else: # neighboring 1s
return 1 + get_min_operations(n + 2 ** i)
return 0
这迭代地应用操作来翻转数字的二进制表示中的 1,直到它全部为 0。 这没有必要递归。 我们从最低位到最高位进行迭代。 如果 1 是单独的,例如01
,那么我们应用n - 2^i
将其变为 0。如果我们有相邻的 1,例如011
,那么我们应用n + 2^i
将 1 推到 a更重要的位: 100
。 重复此操作,直到所有位都为 0。
这是我的测试用例:
assert(get_min_operations(1) == 1)
assert(get_min_operations(2) == 1)
assert(get_min_operations(3) == 2)
# 4 -2^2 == 0
assert(get_min_operations(4) == 1)
# 5 - 2^0 + 2^2 == 0
assert(get_min_operations(5) == 2)
# 6 + 2^1 - 2^3 == 0
assert(get_min_operations(6) == 2)
# 7 + 2^0 - 2^3 == 0
assert(get_min_operations(7) == 2)
assert(get_min_operations(8) == 1)
# 9 - 2^0 - 2^3 == 0
assert(get_min_operations(9) == 2)
# 10 - 2^1 - 2^3 == 0
assert(get_min_operations(10) == 2)
# 11 - 2^0 - 2^1 - 2^3 == 0
assert(get_min_operations(11) == 3)
# 12 - 2^2 - 2^3 == 0
assert(get_min_operations(12) == 2)
# 13 - 2^0 - 2^2 - 2^3 == 0
assert(get_min_operations(13) == 3)
# 14 + 2^2 - 2^4 == 0
assert(get_min_operations(14) == 2)
assert(get_min_operations(16) == 1)
# 18 - 2 - 16 == 0
assert(get_min_operations(18) == 2)
assert(get_min_operations(21) == 3)
# 24 + 8 - 32 == 0
assert(get_min_operations(24) == 2)
# 26 + 2 + 4 - 32 == 0
assert(get_min_operations(26) == 3)
assert(get_min_operations(27) == 3)
# Add 2^2 == 4, subtract 2^5 == 32
assert(get_min_operations(28) == 2)
许多程序员在第一次检查这个问题时,认为它可以使用仅 -2^i 操作的动态规划来解决,或者,甚至更简单,他们认为解决方案是汉明权重,但看看这些方法是如何失败的:
基数 10 | 基地 2 | 示例操作 | 最小操作 |
---|---|---|---|
1个 | 1个 | 1 - 2^0 | 1个 |
2个 | 10 | 2 - 2^1 | 1个 |
3个 | 11 | 3 - 2^1 - 2^0 | 2个 |
4个 | 100 | 4 - 2^2 | 1个 |
5个 | 101 | 5 - 2^2 - 2^0 | 2个 |
6个 | 110 | 6 - 2^2 - 2^1 | 2个 |
7 | 111 | 7 + 2^1 - 2^3 | 2个 |
请注意,7 只能在两次操作中减少到 0,而不是 3。加 1 使其成为 2 的幂,然后可以在一个额外的操作中减少到 0,总共 2。使用汉明权重的想法导致但是,我上面的工作解决方案。 我对此没有直觉,也不知道这种位操作将广泛应用于哪些类型的问题。 如果我能想出一些动态规划或数论解决方案来解决这个问题,我会非常愿意。
我相信这可以通过对数字的位表示进行字符串操作来实现。
删除一个独立的“1”可以在一次操作中完成。 任何连续的 1 都可以在两次操作中删除,无论连续的长度如何(即在最低功率时加 1,然后在下一个更高的功率时删除 1)。
唯一的例外是 4 位或更多位(即 ..1011.. 或 ..1101..)中 1 之间的单独 0,其中翻转 0(在一次操作中)允许删除 3 或更多 1 位恰好 3 次移动等于或优于 3-4 次操作以删除两个独立的 1 条纹。
多个零的任何连串都足以将 1 隔离开来作为单独的问题进行处理。
from itertools import groupby
def get_min_operations(n):
result = 0
bits = f"{n:b}"
for shortcut in ("1011","1101","1011"):
result += bits.count(shortcut)
bits = bits.replace(shortcut,"1111")
result += sum( 1+(len(g)>1) for b,(*g,) in groupby(bits) if b=="1")
return result
[编辑]
再考虑一下,我提出了一种不使用字符串表示的递归方法,方法是使用上层递归的进位处理最后 2 位:
def get_min_operations(n,carry=0):
if not n:
return carry
# 1s streaks take max 2 operations
if n&1 == 1:
return get_min_operations(n//2,min(2,carry+1))
# 10 with carry extend streaks at cost of 1 oper.
if n&3 == 2 and carry:
return 1 + get_min_operations(n//4,carry)
# 00 ends streaks of 1s costing current carry
return carry + get_min_operations(n//2)
您想要转换为“带符号”的二进制表示形式,其中不仅包括 0 和 1,还包括 -1(我表示为“-”)
from math import log, ceil, floor
def signed_binary(num, current_digit = None):
if num == 0:
return '0' if current_digit is None else '0'*current_digit + '0'
elif num == 1:
return '1' if current_digit is None else '0'*current_digit + '1'
elif num == -1:
return '-' if current_digit is None else '0'*current_digit + '-'
else:
if num > 0:
# Got log base 2
power = log(num)/log(2)
# Look to represent as either 2^ceil(power) - remainder
# or 2^floor(power) - remainder
# and use representations with lower number of '1' and '-'
# Check if we need to prepend zeros
if current_digit is not None:
ceil_start = '0'*max(0, current_digit - ceil(power)) + '1'
floor_start = '0'*max(0, current_digit - floor(power)) + '1'
else:
ceil_start = '1'
floor_start = '1'
return shorter_rep(ceil_start + signed_binary(num - 2**ceil(power), ceil(power)-1), floor_start + signed_binary(num - 2**floor(power), floor(power)-1))
else:
return flip_signed_binary(signed_binary(-num))
def shorter_rep(sbin1, sbin2):
if sbin1.count('1') + sbin1.count('-') < sbin2.count('1') + sbin2.count('-'):
return sbin1
else:
return sbin2
def flip_signed_binary(sbin):
return ''.join(['-' if char == '1' else '1' if char == '-' else char for char in sbin])
for i in range(30):
rep = signed_binary(i)
ops = rep.count('1') + rep.count('-')
print(f'{i}: \'{rep}\', {ops} operations')
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.