腾讯校招的笔试题目:凑硬币
拼凑硬币:
时间限制:(每个case)2s 空间限制:128MB
小Q十分富有,拥有非常多的硬币,小Q拥有的硬币是有规律的,对于所有的非负整数K,小Q恰好各有两个面值为2^K的硬币,所以小Q拥有的硬币就是1,1,2,2,4,4,8,8,,,,,小Q有一天去商店购买东西需要支付n元钱,小Q想知道有多少中方案从他拥有的硬币中选取一些拼凑起来恰好是n元(如果两种方案某个面值的硬币选取的个数不一样就考虑为不一样的方案)
输入:
输入包括一个整数n(1 <= n <= 10^18),表示小 Q需要支付多少钱。注意n的范围。
输出:
输出一个整数,表示小Q可以拼凑出n元钱的方案数。
样例输入: 6
样例输出: 3
下面代码采用的是暴力破解,自顶向下,暴力破解,试图优化,但是效果不好,暂时没有想到别的思路。
package others;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
public class CoinCoinTencent {
public static void main(String[] args) {
// TODO Auto-generated method stub
long n = 60; //总的钱数目
ArrayList<ArrayList<String>> arr = coinCoinTencent(n);
System.out.println(arr);
System.out.println("总数 = " + arr.size());
}
/*
* 下面这部分代码找到了所有可能的拼凑方法,但是时间复杂度是指数级别的
*/
private static ArrayList<ArrayList<String>> coinCoinTencent(long n) {
long m = (long) (Math.log(n)/Math.log(2));
//int型数据最大为2^32 = 42 9496 7296,大约40亿,这是无符号数范围,
//long型数据最大为2^64 = 1.844674407371 * 10 19,这是无符号范围,
//有符号也就是一半,这表示题目数字范围已经够了。
ArrayList<ArrayList<String>> arr = new ArrayList<ArrayList<String>>();
HashMap<String, Integer> h = new HashMap<String, Integer>();
coinCoinTencent(n, m, arr, h);
return arr;
}
private static void coinCoinTencent(long n, long m,
ArrayList<ArrayList<String>> arr, HashMap<String, Integer> h) {
if(n == 0) { //递归的结束条件,同时将结果存储到链表中
ArrayList<String> arr_temp = new ArrayList<String>();
//将HashMap中的键值对全部取出,存储到arr中。
for(Iterator<String> it = h.keySet().iterator(); it.hasNext();) {
String key = (String)it.next();
for(int j = 1 ; j <= h.get(key); j++) {
//键所对应的值位2,就增加两个键,否则增加一个键
arr_temp.add(key);
}
}
if(!arr.contains(arr_temp)) {
arr.add(arr_temp);
}
}
for(long i = 0; i <= m; i++) {
//hash表中没有这个键,或者键值为1
if(!h.containsKey(Long.toString(i))) {
h.put(Long.toString(i), 1); //添加元素
n = (long) (n - Math.pow(2, i));
m = (long) (Math.log(n)/Math.log(2));
coinCoinTencent(n, m, arr, h);
//数据恢复
h.remove(Long.toString(i));
n = (long) (n + Math.pow(2, i));
m = (long) (Math.log(n)/Math.log(2));
} else if(h.get(Long.toString(i)) == 1) {
h.put(Long.toString(i), 2);
n = (long) (n - Math.pow(2, i));
m = (long) (Math.log(n)/Math.log(2));
//数据恢复
coinCoinTencent(n, m, arr, h);
h.put(Long.toString(i), 1);
n = (long) (n + Math.pow(2, i));
m = (long) (Math.log(n)/Math.log(2));
}
}
}
}
代码运行效果:
[[0, 0, 1, 1], [0, 0, 2], [1, 2]]
总数 = 3
这是第二种方法,效率提高了不少,但是也解决不了钱数较大的情况。
/*
* 无重复分类,效率提高了很多,钱数在50000以内,2s内可以输出结果。
*/
public class CoinCoinTencent {
public static void main(String[] args) {
// TODO Auto-generated method stub
int n = 50000; //总的钱数目
int times = coinCoinTencent(n); //总的拼凑次数
System.out.println(times);
}
private static int coinCoinTencent(int n) {
int m = (int) (Math.log(n)/Math.log(2));
int[] coinValue = new int[m + 1]; //一共有m + 1种币值可以使用
for(int i = 0; i < m + 1; i++) {
coinValue[i] = i;
}
int times = coinCoinTencent(n, coinValue, m);
return times;
}
private static int coinCoinTencent(int n, int[] coinValue, int m) {
if(n == 0){
return 1;
}
if(n < 0) {
return 0;
}
if(m < 0) {
return 0;
}
int times_temp =
//一次用了2个相同币值的硬币
coinCoinTencent((int)(n-2*Math.pow(2,coinValue[m])),coinValue,m-1) +
//一次用了1个币值的硬币
coinCoinTencent((int)(n-Math.pow(2,coinValue[m])), coinValue,m - 1) +
//一次用了0个币值的硬币
coinCoinTencent(n, coinValue,m-1);
return times_temp;
}
}