繁体   English   中英

计算 Python 中的最小二元运算

[英]Count Min Binary Operations in Python

问题:给定一个正数 integer n ,在一次操作中,选择任何i >= 0并将n转换为n + 2^in - 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM