先举例子说说我的思路
如题
1,2,4,9
目标值是2533
先对数据从大到小取出
第一个数字是9,没问题,然后计算一下后面最多还能拼接多少个数,
因为9开头,所以最大能取出的就是三位数了,因此只剩下2个名额拼接数字了(9比目标值的最高位2要大,所以最终值要比目标值少1位长度),
然后递归,下一次又拼接一个9,然后就剩下最后1个名额了,然后再递归,就发现没名额了,就需要退出递归了
(说那么多废话,就是想告诉你们,我这里是排列组合,然后借助剩余的名额是否等于0去结束递归)
上面是整体的实现思路,结果一跑,小数值还行,大数值从昨天跑到今天都没跑出来
排列组合太可怕了~
我后面想到了剪枝,具体剪枝看代码中的注释吧~
用了剪枝之后,输入再大的值,都是瞬间出结果~真舒服
不要害怕下面的代码!!!下面有一半都是注释!!!怕大家伙看的吃力!!也怕我忘了。。
最后我附上一版没有那么多注释的
public class combinationMaxNum {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.addAll(Arrays.asList(0,1,2,3,4,5,6,7,8,9));
System.out.println(getMaxNum(list,Integer.MAX_VALUE));
}
public static Integer getMaxNum(List<Integer> list,Integer n){
return maxNum(list, String.valueOf(n),String.valueOf(n).length(), "",new HashMap<>(),Integer.MIN_VALUE);
}
/**
*
* @param list 0-9数组
* @param n 目标值
* @param size 当前 nowValue 下最大还能放多少个数字
* @param nowValue 组合的数字
* @param map 用于剪枝,不然会超时,而且那就是纯纯的排列组合了
* @param maxValue 存储最大值用的
* @return
*/
public static Integer maxNum(List<Integer> list,String n,Integer size,String nowValue,HashMap<Integer,Integer> map,Integer maxValue){
if(size==0){
return Integer.valueOf(nowValue);
}
for (int i = list.size()-1; i >= 0; i--) {
String value = nowValue+list.get(i);
if(value.compareTo(n)>=0){
//当前值大于等于目标值,就说明已经超出大小了,就不要了
continue;
}
final Integer otherSize = getOtherSize(n, value);
//重点是这个剪枝!
//没有这个剪枝的话,代码一样能跑,但是那就是排列组合所有可能了,直接超时
//那到底剪掉了哪些肯定不是最优答案的递归呢?
//例如说n=9001时,当我循环第二次组合出了一个 99(value),那otherSize就只有1了,也就是最大值就是999了,
//那请问,98以及后面循环和递归的还需要么?
//很明确就已经不需要了, 因为98的otherSize也是1了,无论后面再拼接什么,都不会再大于99开头的三位数了,
//所以这个剪枝用的map的键是otherSize,值是剩余otherSize时拼接的最大的值;怎么存储的移步到下面的map.put();
if(map.containsKey(otherSize)&&map.get(otherSize)<list.get(i)){
//剪枝
break;
}
//这里就是递归做组合,不详细说了
int max = maxNum(list,n,otherSize,value,map,maxValue);
//为什么要判断max!=Integer.MIN_VALUE?
//因为例如9000的时候,当组合的数值到达9000后,发现等于目标值n了,因此没有结果返回,递归返回的max就是默认值,也就是Integer.MIN_VALUE
//因此这里需要专门将这种情况过滤掉
if(max!=Integer.MIN_VALUE){
//这里就是存储otherSize和拼接的最大的值的地方了
map.put(otherSize,list.get(i));
}
maxValue = Math.max(max,maxValue);
}
return maxValue;
}
//用于计算组合最新值后还可以再放多少个数字
public static Integer getOtherSize(String n,String nowValue){
if(n.compareTo(nowValue)>=0){
//如果n目标值比现在的值大,返回长度差
return n.length() - nowValue.length();
}
//如果n目标值比现在的值大,返回长度差再减1
return n.length() - nowValue.length()-1;
}
}
简化注释后的代码,核心方法maxNum 就十几行代码
public class combinationMaxNum {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
list.addAll(Arrays.asList(0,1,2,3,4,5,6,7,8,9));
System.out.println(getMaxNum(list,Integer.MAX_VALUE));
}
public static Integer getMaxNum(List<Integer> list,Integer n){
return maxNum(list, String.valueOf(n),String.valueOf(n).length(), "",new HashMap<>(),Integer.MIN_VALUE);
}
/**
*
* @param list 0-9数组
* @param n 目标值
* @param size 当前 nowValue 下最大还能放多少个数字
* @param nowValue 组合的数字
* @param map 用于剪枝,不然会超时,而且那就是纯纯的排列组合了
* @param maxValue 存储最大值用的
* @return
*/
public static Integer maxNum(List<Integer> list,String n,Integer size,String nowValue,HashMap<Integer,Integer> map,Integer maxValue){
if(size==0){
return Integer.valueOf(nowValue);
}
for (int i = list.size()-1; i >= 0; i--) {
String value = nowValue+list.get(i);
if(value.compareTo(n)>=0){
continue;
}
final Integer otherSize = getOtherSize(n, value);
if(map.containsKey(otherSize)&&map.get(otherSize)<list.get(i)){
//剪枝
break;
}
int max = maxNum(list,n,otherSize,value,map,maxValue);
if(max!=Integer.MIN_VALUE){
map.put(otherSize,list.get(i));
}
maxValue = Math.max(max,maxValue);
}
return maxValue;
}
//用于计算组合最新值后还可以再放多少个数字
public static Integer getOtherSize(String n,String nowValue){
if(n.compareTo(nowValue)>=0){
return n.length() - nowValue.length();
}
return n.length() - nowValue.length()-1;
}
}