近期听到群友笔试题:
给定一个长度为n的int数组,请问可否随机组成2的n次方。如果可以输出组成的数,如果不可以输出-1
首先看到上面这个题,有4个想法:
- 反向思维:2的n次方的n=1,2,3…… n,循环暴力搜索
- 正向思维:按题目要求随机排列组合,检查是否是2的n次方,更暴力;
- 二进制:由于是2的n次方,可以考虑微操作,2的n次方的规律就是:二进制中只有一个1。具体想法还没有想通(后续待补充)
解法在第四部分
文章目录
1.快速幂
不管是正向搜索还是反向搜索,很有可能涉及到的知识就是计算2的n次幂
将最近学习到的快速幂记下来: 快速幂的实现方法已经定型了,理解的话移步文章。
int poww(int a, int b) {
int ans = 1, base = a;
while (b != 0) {
if (b & 1 != 0)
ans *= base;
base *= base;
b >>= 1;
}
return ans;
}
2.大数幂
学会了快速幂,就可以算基本的幂运算了;可是当a或者b很大时,上述快速幂算法就会越界,这就涉及到另一个问题:大数幂
大数幂的解决方法很多,但其根源只有一个:用数组/链表存储每位;
链表存储是反向存储;数组正向就行,反向也可;
——————————————————————————————————————————————————————————
2.1 大数幂(基于乘法)
用的是vector数据结构;可以用数组,可以用链表都行
下面是vector版本:
//全局变量 ans
vector<int> ans;
// 大数幂 的基础 大数乘法
void mul(int num){
int temp_add = 0;
int temp_mul = 0;
for(int i = 0; i < ans.size(); i++){
temp_mul = num * ans[i];
ans[i] = (temp_mul + temp_add) % 10;
temp_add = (temp_mul + temp_add) / 10;
}
while(temp_add){
ans.push_back( temp_add % 10);
temp_add /= 10;
}
}
void init(int b) //初始化 如果b大于等于10,就需要分配一下数组
{
while(b>0)
{
ans.push_back(b % 10); //倒序
b /= 10;
}
}
// 大数幂
vector<int> powd(int b, int e){
init(b);
for(int i = 1; i < e; i++)
mul(b);
return ans;
}
int main()
{
vector<int> a = powd(4444, 3);
for (int i = a.size()-1; i >= 0 ;i--) //反向打印输出
cout << a[i] << "";
cout << endl;
return 0;
}
2.2 大数幂(基于快速幂)
我初步想法是,快速幂可以和大数幂两者结合的。但是还没开始做。上面几个方法应付leetcode和找工作已经足够了。后面有需要再补充;
3.大数幂模运算
学会了大数幂运算,还可能会遇到大数幂的模运算(如:%65536)。此时按照大数幂的方法求解出幂结果,再进行取模运算也是可以的。
不过那样的话,就很麻烦了,需要数组每一位对65536取模;
———————————————————————————————
取模的性质
基本性质
(1)若p|(a-b),则a≡b (% p) 即 a%p = b % p。例如 11 ≡ 4 (% 7), 18 ≡ 4(% 7) // | 表示整除
(2)(a % p)=(b % p)意味a≡b (% p)
(3)对称性:a≡b (% p)等价于b≡a (% p)
(4)传递性:若a≡b (% p)且b≡c (% p) ,则a≡c (% p)
运算规则
模运算与基本四则运算有些相似,但是除法例外。其规则如下:
(a + b) % p = (a % p + b % p) % p (1)
(a - b) % p = (a % p - b % p) % p (2)
(a * b) % p = (a % p * b % p) % p (3)
ab % p = ((a % p)b) % p (4)
结合率:
((a+b) % p + c) % p = (a + (b+c) % p) % p (5)
((ab) % p * c)% p = (a * (bc) % p) % p (6)
交换率: (a + b) % p = (b+a) % p (7)
(a * b) % p = (b * a) % p (8)
分配率: ((a +b)% p * c) % p = ((a * c) % p + (b * c) % p) % p (9)
重要定理
若a≡b (% p),则对于任意的c,都有(a + c) ≡ (b + c) (%p);(10)
若a≡b (% p),则对于任意的c,都有(a * c) ≡ (b * c) (%p);(11)
若a≡b (% p),c≡d (% p),则 (a + c) ≡ (b + d) (%p),(a - c) ≡ (b - d) (%p), (a * c) ≡ (b * d) (%p),(a / c) ≡ (b / d) (%p); (12)
若a≡b (% p),则对于任意的c,都有ac≡ bc (%p); (13)
参考文章: https://blog.csdn.net/afvdxhq267883/article/details/101247388
———————————————————————————————
3.1 大数幂模(基础)
基础方法主要利用性质3
(a * b) % p = (a % p * b % p) % p
int Dsmod(int b,int num,int modnum){ //
int tempb = b % modnum;
for(int i = 1; i < num; i++)
tempb = tempb * tempb % modnum;
return tempb;
}
int main()
{
int an = Dsmod(4, 3, 3);
cout <<an<< endl;
return 0;
}
3.2 大数幂模(高阶)
上面的情况在进行 取模相乘时,结果可能越界。
此时就需要额外的操作进行处理。一般情况沿用上述大数乘法的方略;
主要涉及到以下性质:
(a + b) % p = (a % p + b % p) % p (1)
(a - b) % p = (a % p - b % p) % p (2)
(a * b) % p = (a % p * b % p) % p (3)
4.可否组成2的n次幂
4.1 反向思维
就应用之前提到的反向思维。
大概实现思路:
- 从1次方到n次方循环,存储2的n次方长度与输入数组长度相同的n值序列
- 检查数组中的序列与2的n次方序列是否完全一样;
#include <iostream>
#include <vector>
#include <sstream>
#include <string>
using namespace std;
vector<int> ans;
// 大数幂 的基础 大数乘法
void mul(int num){
int temp_add = 0;
int temp_mul = 0;
for(int i = 0; i < ans.size(); i++){
temp_mul = num * ans[i];
ans[i] = (temp_mul + temp_add) % 10;
temp_add = (temp_mul + temp_add) / 10;
}
while(temp_add){
ans.push_back( temp_add % 10);
temp_add /= 10;
}
}
void init(int b)
{
ans.clear();
while(b>0)
{
ans.push_back(b % 10); //倒序
b /= 10;
}
}
vector<int> powd(int b, int e){
init(b);
for(int i = 1; i < e; i++)
mul(b);
return ans;
}
vector<int> allo(int b){
vector<int> res;
while(b>0)
{
res.push_back(b % 10); //倒序
b /= 10;
}
return res;
}
//检查是否是相等
bool check(vector<int> a,vector<int> b)
{
//可以用堆栈来检查
for (vector<int>::iterator iter = b.begin(); iter != b.end();) //itereate
{
for (vector<int>::iterator iter2 = a.begin(); iter2 != a.end();)
{
if (*iter == *iter2)
{
a.erase(iter2);
b.erase(iter);
iter = b.begin();
iter2 = a.begin();
break;
}else
{
iter2++;
}
}
}
if(a.size()==0)
return true;
else
return false;
}
void outpur(vector<int> inv)
{
for (vector<int>::iterator iter = inv.begin(); iter != inv.end();iter++) //应该反向打印
{
cout << *iter << "-";
}
cout << endl;
}
int main()
{
int inpu;//有范围
//空格分隔的大数
// string line;
// vector<int> inpu;
// getline(cin, line);
// stringstream ss(line);
// while(ss)
// {
// int temp;
// ss >> temp;
// inpu.push_back(temp);
// }
cin >> inpu;
vector<int> inve = allo(inpu);
vector<int> a2n;
vector<int> n;
for (int i = 1;;)//1开始
{
//可能会涉及到大数
ans.clear();
a2n = powd(2, i);
if(a2n.size()<inve.size())
i++;
else if (a2n.size()==inve.size())
{
if(check(a2n,inve))
{
outpur(inve);
n.push_back(i);
}
i++;
}else
{
break;
}
}
//
if(n.size() == 0)
{
cout << -1 << endl; //没有复合个数的n
}
system("pause");
return 0;
}