题目描述如下:
小Q同学持有面值为2^n(n>=0)的硬币各两枚,例如:1,1,2,2,4,4,8,8....。现在小Q购买商品,商品价格为K,要求给出所有可能的硬币组合方式,同样面额不同数量记为不同的方式。1<K<10^18(题目提示注意K的范围)
例如:
输入6(商品价格6元)
输出3(原题目答案为5,怎么都想不通。。。,欢迎指正拍砖),即有3种组合方式:(4,2) ; (4,1,1) ; (2,2,1,1)
解题思路:
可以非常容易的想到,这是一道可以用动态规划求解的问题,构造出状态转移方程以后可求得结果。
但是,有没有一种不用动态规划的方法呢?(动态规划学的不熟练,惭愧)答案是有的。以下是我的思路:
- 我们将所持有的硬币对等的分为两份,X =1,2,4,8...2^n ,Y=1,2,4,8...2^n
- K = X +Y,X和Y都由2的幂构成
- 计算(X+Y)的不同组合数,结束
思路非常简单,也很容易实现。
但是,不要忽略重复组合,举例来说,K=6,其中(6,0)与(4,2)是相同的,都是4+2的形式,而与(5,1)、(3,3)不同。
由于这些数字都由2的幂组成,注意到比较这两个组合的二进制形式(110,000)VS(100,010),实质上只是中间一位交换了下位置而已,与(101,001)VS(011,011)比较,则并不是简单的交换,而是发生了进位,因此:
- 我们对不同的组合进行异或,保存异或的结果,结果作为区别标志,即可去除重复项。
保存异或的结果可以使用哈希表实现,免去每次都要与其他组合异或比较。同时对大数除法,我们也用位运算代替,进一步减小开销。
实测:用哈希表代替红黑树,可以获得10%左右的时间节省,位运算则节省了2%左右。(使用100000进行测试得到)
给出代码
int payWays(int num)
{
//num限制为大于2的正数
if (num < 0) return 0;
if (num <= 2) return num;
//num除以2,获得循环结束位,使用位运算,速度更快
int middle = num >> 1;
//不要求顺序,无需红黑树,使用哈希表,查找速度更快
unordered_map<int, int> ways;
for (int i = 0; i <= middle; i++)
{
int flag = i ^ (num - i);
ways[flag] = 1;
}
return ways.size();
}
举一反三
1、如果小Q每种面值都有n枚,该怎么做?
2、如果面值为8进制、16进制,该怎么做?
3、如果面值为n进制,该怎么做?
欢迎交流~