判断一个整数经过重排后能否为2的整数次幂

1 题目描述

给一个整数N(N大于等于1, 且小于等于10的18次方)判断能否将N的各个数位重新排列,得到一个新的数(不能含前缀零)是2的整数次幂。能则返回true,不能则返回false。要求在普通的PC机上能1秒内算个结果。

 

2. 输入输出描述

例如,输入23,输出true,因为23可以重排为32,而32是2的5次幂

输入1025,输出false,因为1025的全排列都不能是2的整数次幂

 

3. 算法分析

最直接的想法:对于输入的一个长度为m的整数N,我们可以对它进行全排列,然后对每个新排列的数字判断它是否为的2整数次幂,这样做的一个最大问题就是需要判断的次数太多,计算过于复杂,很有可能达不到题目中1秒的要求。

新想法:找出10e18中所有是2的整数次幂的整数,2^64 ~= 10e18(在Python3.7中的运算结果,在Python3.7中10e18=1e19,所以使用numpy和math运算log2(10e18)的结果都为63.1166),将每个转化为字符串,并进行排序,然后放在一个集合中。例如整数32是2的5次幂,转化为字符串‘32’,排序后变为‘23’,然后放入一个集合中。把所有2的整数次幂的整数放如集合中后,对输入的整数也进行同样的操作,将其转化为字符串,进行从小到大的排序,最后判断这个字符串是否在集合中,如果在,则输入的字符串则可以通过重排,转化为2的整数次幂,否则不能。

 

4. 代码实现

C++实现:

#include 
#include 
#include 
#include 
using namespace std;

bool r2(int N) {
	if (N == 1) return true;

	set two;
	for (int i=0; i < 64; i++) {
		string s = to_string(1LL << i);
		sort(s.begin(), s.end());
		two.insert(s);
	}

	string s = to_string(N);
	sort(s.begin(), s.end());
	return two.find(s) != two.end();
}

int main() {
	int n;
	n = 512;
	bool result = r2(n);
	cout << result << endl;
	return 0;
}

 

Python实现:

number = input()
number = int(number)
base = 1
nin = 10e18
if number < base or number > nin:
    raise AttributeError('please input a integer that greater than 1 less than 10e18')

def PowderOf2(n):
    """
    1) 找出10e18内是2的整数次幂的整数
    2) 将这些整数转化为字符串,并且将每个字符串进行从小到大的排序,最后添加到集合two中
       例如32是2的5次幂,转化为字符串‘32’,排序后为‘23’,再将‘23’添加到集合two中
    3) 同样,将输入的整数,也转化为字符串,也进行同样的排序
    4) 判断这个字符串是否在集合two中,如果在,说明输入的数字可以通过重排转化为2的整数次幂
    """
    if (n == 1):
        return True
    two = set()
    for i in range(64):  # 64 ~= log2(10e18)
        s = str(1 << i)
        s="".join((lambda x:(x.sort(),x)[1])(list(s)))
        two.add(s)
    
    s = str(n)
    s="".join((lambda x:(x.sort(),x)[1])(list(s)))
    return s in two

result = PowderOf2(number)
print(result)

 

5. 测试

判断一个整数经过重排后能否为2的整数次幂_第1张图片

 

6. 改进

仔细思考,可以发现,PowderOf2算法有两个缺点,第一,每次输入一个整数,都要遍历64次(将2^64范围里面2的整数次幂转化为字符串,并排序);第二,需要一个内存空间去存储集合two

改进:先将输入的整数转化为字符串并进行排序,然后从小到大在2的整数次幂中去寻找是否存在一个整数的排序与输入整数的排序相同,如果有,返回True,如果搜寻的整数的长度已经大于了输入的整数的长度,那么说明,输入的整数不能经过重排转化为2的整数次幂,返回False

参考:https://blog.csdn.net/qq_41855420/article/details/91353302

number = input()
number = int(number)
base = 1
nin = 10e18
if number < base or number > nin:
    raise AttributeError('please input a integer that greater than 1 less than 10e18')

def PowderOf2_modified(n):
    """
    在PowderOf2中,对于每次输入的整数,都会转化所有10e18内是2的整数次幂的整数为字符串,并排序
    这是不必要的,而且需要一定的内存来保存这些字符串
    可以根据输入的整数的大小,自动确定需要转化的整数,而不是全部,这样不仅会减少一部分时间,也会减少内存消耗
    特别是当输入的整数较小时,特别有用
    """
    if (n == 1):
        return True
    
    s = str(n)
    s = "".join((lambda x:(x.sort(),x)[1])(list(s)))

    i = 0
    while True:
        s2 = str(1 << i)
        if (len(s2) > len(s)):
            return False
        s2 = "".join((lambda x:(x.sort(),x)[1])(list(s2)))
        if (s == s2):
            return True 
        i += 1

result = PowderOf2_modified(number)
print(result)

 

7. 时间比较

import time 
number = input()
number = int(number)
base = 1
nin = 10e18
if number < base or number > nin:
    raise AttributeError('please input a integer that greater than 1 less than 10e18')

def PowderOf2(n):
    """
    1) 找出10e18内是2的整数次幂的整数
    2) 将这些整数转化为字符串,并且将每个字符串进行从小到大的排序,最后添加到集合two中
       例如32是2的5次幂,转化为字符串‘32’,排序后为‘23’,再将‘23’添加到集合two中
    3) 同样,将输入的整数,也转化为字符串,也进行同样的排序
    4) 判断这个字符串是否在集合two中,如果在,说明输入的数字可以通过重排转化为2的整数次幂
    """
    if (n == 1):
        return True
    two = set()
    for i in range(64):  # 64 ~= log2(10e18)
        s = str(1 << i)
        s = "".join((lambda x:(x.sort(),x)[1])(list(s)))
        two.add(s)
    
    s = str(n)
    s = "".join((lambda x:(x.sort(),x)[1])(list(s)))
    return s in two

def PowderOf2_modified(n):
    """
    在PowderOf2中,对于每次输入的整数,都会转化所有10e18内是2的整数次幂的整数为字符串,并排序
    这是不必要的,而且需要一定的内存来保存这些字符串
    可以根据输入的整数的大小,自动确定需要转化的整数,而不是全部,这样不仅会减少一部分时间,也会减少内存消耗
    特别是当输入的整数较小时,特别有用
    """
    if (n == 1):
        return True
    
    s = str(n)
    s = "".join((lambda x:(x.sort(),x)[1])(list(s)))

    i = 0
    while True:
        s2 = str(1 << i)
        if (len(s2) > len(s)):
            return False
        s2 = "".join((lambda x:(x.sort(),x)[1])(list(s2)))
        if (s == s2):
            return True 
        i += 1

def Swap(string, i, j):
    """
    交换字符串string中的第i个元素和第j个元素
    """
    string = list(string)
    temp = string[i]
    string[i] = string[j]
    string[j] = temp 
    string = "".join(string)
    return string 

def Perm(string, k, m, all_number):
    """
    依次循环交换
    最外层:第一位数与其后面的各位数进行交换
    次外层:第二位数与其后面的各位数进行交换
    一直循环...
    最后一层:倒数第二位与倒数第一位进行交换
    """
    if(k == m-1):
        all_number.add(int(string))
    else:
        for i in range(k, m):
            string = Swap(string, k, i)
            Perm(string, k+1, m, all_number)
            string = Swap(string, k, i)

def PowderOf2_perm(n):
    all_number = set()
    n = str(n)
    Perm(n, 0, len(n), all_number)
    # print(all_number)
    for i, num in enumerate(all_number):
        if ((num & num -1) == 0):
            return True
        if i == (len(all_number) - 1) and ((num & num -1) != 0):
            return False

time1 = time.time()
result = PowderOf2(number)
time2 = time.time()
print(result)
print('PowderOf2 COST TIME:', (time2 - time1))

time3 = time.time()
result = PowderOf2_modified(number)
time4 = time.time()
print(result)
print('PowderOf2_modified COST TIME:', (time4 - time3))

time5 = time.time()
result = PowderOf2_perm(number)
time6 = time.time()
print(result)
print('PowderOf2_perm COST TIME:', (time6 - time5))

 测试结果:

判断一个整数经过重排后能否为2的整数次幂_第2张图片

从测试结果中可以发现,如果用遍历全排列的方法是不可行的,当输入数字的长度较小时还可以接受,当超过十位数时,时间就超过了17秒,这是不可忍受的(指数级增长),也是不满足题目要求的。改进的方法几乎不耗时,也省去了集合two的内存消耗。

你可能感兴趣的:(算法分析设计)