一 背景
求将1块钱换成为1角、2角、5角有多少种拆分方法是一个经典的递归求解问题。可以扩展到将任意一个整数拆分为几种个更小的整数,有几种拆分方式的求解问题。
二 分析
基于这样一种分治思想:1块钱换1角,2角,5角的方式 = 1块钱换不包含第一种钱币的方法 + 1块钱换必包含第一种钱币的方法。从树型结构分析(下图以把3拆成1,2为例分析):
左子树描述的是不包含第一种钱币的拆分方式,减少的是钱币种类数;右子树表示的是必包含第一种钱币的拆分方法,减少的是钱币总量;辗转相减,当出现钱币总量为0时表示找到了一种拆分方式,当出现钱币总量为负或者类型列表为空表示未找到合适的拆分方式。
三 示例代码:
import java.util.LinkedList;
import java.util.Stack;
public class CSplit {
public static void main(String[] args)
{
Stack<Integer> stSplitMethod = new Stack<Integer>();
LinkedList<Integer> lstSplit = new LinkedList<Integer>();
//以将10拆分为1,2,5为例
lstSplit.add(1);
lstSplit.add(2);
lstSplit.add(5);
System.out.print("拆分方式如下:\n");
CSplit split = new CSplit();
Integer iNum = split.Split(stSplitMethod, 10, lstSplit, false);
System.out.print("拆分方式数:\n");
System.out.print(iNum);
}
//化整为零拆分函数,输出拆分方式的种类及拆分方法
public Integer Split(Stack<Integer> stSplitMethod, Integer iTotal,
LinkedList<Integer> lstSplit, boolean bLOrR)
{
//右子树总量减去了第一个节点,压栈
if(bLOrR && !lstSplit.isEmpty())
{
stSplitMethod.push(lstSplit.getFirst());
}
//辗转相减,iTotal为0说明找到一种拆分方式
if(0 == iTotal)
{
System.out.print(stSplitMethod);
System.out.print("\n");
return 1;
}
if(iTotal < 0 || lstSplit.isEmpty())
{
return 0;
}
//拷贝一份,因为JAVA默认为传引用
LinkedList<Integer> lstSplitTemp = new LinkedList<Integer>();
lstSplitTemp.addAll(lstSplit);
if(!lstSplitTemp.isEmpty())
{
lstSplitTemp.removeFirst();
}
//计算左子树即排除第一种类型的拆分方式 + 右子树即必含第一种类型的分类
//方式
Integer iNum = Split(stSplitMethod, iTotal, lstSplitTemp, false)
+ Split(stSplitMethod, iTotal - lstSplit.getFirst(), lstSplit, true);
//调用右子树拆分,执行了压载操作。无论是否找到合适的拆分方式都要出栈回退
stSplitMethod.pop();
return iNum;
}
}
四 示例代码输出结果为:
拆分方式如下:
[5, 5]
[2, 2, 2, 2, 2]
[1, 2, 2, 5]
[1, 1, 2, 2, 2, 2]
[1, 1, 1, 2, 5]
[1, 1, 1, 1, 2, 2, 2]
[1, 1, 1, 1, 1, 5]
[1, 1, 1, 1, 1, 1, 2, 2]
[1, 1, 1, 1, 1, 1, 1, 1, 2]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
拆分方式数:
10