`
luzitian
  • 浏览: 26675 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

把一个数字分离成不同的数字的和

阅读更多

一道有趣的题目,把一个整数num拆分成n个整数的合,要求这n个整数各不相同,不能有重复,如num为10,则有以下几种情况:

1 9
2 8
3 7
4 6
1 2 7
1 3 6
1 4 5
1 2 3 4

我想了一个算法,不知正确与否,希望各位大牛来拍砖。

分析:

首先,把10拆分成两个数,因为各个数不能相同,所以只当第一个数为4时为止,即

1 9
2 8
3 7
4 6

其次,再把10拆分1和9,对9采用上面的拆法,9会被拆成如下:

1 8
2 7
3 6
4 5

因为各数字不能重复出现,所以把1 8这对去掉,所以10被拆成如下:

1 2 7
1 3 6
1 4 5

再次,再把10拆分成1、2和7,对7采用上面的拆法,7会被拆成如下:

1 6
2 5
3 4
1 2 4

因为拆分出的数字中不能含有1、2所以只留下3 4这对数字,所以10被拆成如下:

1 2 3 4

最后,再把10拆分成1、2、3和4,由于4拆分出的两个数字必须大于3,这是不可能的,所以已经无法继续拆分。至此10的拆分结束。

 

从上面的分析可以看出,最后一步都是把经过前面拆分后剩下的数值拆分成两个数,而前面的拆分则是拆分出1或1、2或1、2、3……,由此写出了第一个程序:

package calc.printsum;
import junit.framework.TestCase;
/**
 * 打印和为num的所有情况,并且每行中不能有重复数字 
 */
public class PrintResult1 extends TestCase {
	public void test() {
		int num = 10;
	                // i <= Math.ceil(num / 2)
                                //因为i再大就会打出重复数字了
		for (int i = 1; i <= Math.ceil(num / 2); i++) {
			splitNum(i, num - sumToN(i));
//			System.out.println("------------");
		}
	}

	/**
	 * @param start
	 * @param num
	 */
	private void splitNum(int start, int num) {
		if (start > num) {
			return;
		}
		for (int i = start; i <= Math.ceil(num / 2) && i != (num - i); i++) {
			// 打出从1到start的数字
			printSerial(start);
			// 把剩下的num分成两半
			System.out.print(i + " ");
			System.out.println(num - i);
		}
	}

	/**
	 * 顺序打印1到n
	 * @param n
	 */
	private void printSerial(int n) {
		for (int i = 1; i < n; i++) {
			System.out.print(i + " ");
		}
	}

	/**
	 * 计算1到n的总和
	 * @param n
	 * @return
	 */
	private int sumToN(int n) {
		int sum = 0;
		for (int i = 1; i < n; i++) {
			sum += i;
		}
		return sum;
	}
}

函数printSerial由于是i<n所以当i=1时是不会打印任何东西的,所以splitNum的for循环中当start是1时实际上只执行了“把剩下的num分成两半”的两句打印语句,这正是我们想要的。

 

分析此类可以发现存在两个问题:

1、sumToN实际上是不需要的,我们完全可以充分利用先计算过的结果,从而无须每次都从头到尾计算前n个数的和;

2、如果把System.out.println("------------");这一句的注释打开,可以看到在最后一组数字后面还打多了两次------------,如下:

1 2 3 4
------------
------------
------------

于是有了修改后的PrintResult2:

package calc.printsum;
import junit.framework.TestCase;
/**
 * 打印和为num的所有情况,并且每行中不能有重复数字
 */
public class PrintResult2 extends TestCase {
	public void test() {
		int num = 10;
		int sum = 0;
		// i <= Math.ceil(num / 2)
                                //因为i再大就会打出重复数字了
		for (int i = 1; i <= Math.ceil(num / 2); i++) {
                                                // sum利用了上次计算的结果
			sum += (i - 1);
			// 由i > (num - sum) / 2演变而来,当i再增大时num-sum的值已经无法继续拆分成两个不同于前i个数字的数字之合了
			if (2 * i > (num - sum)) {
				break;
			}
			splitNum(i, num - sum);
//			System.out.println("------------i:" + i + " sum:" + sum
//					+ " (num - sum) / 2:" + (num - sum) / 2);
		}
	}

	/**
	 * 把num按从start开始分离成两个不同的数相加
	 * @param start
	 * @param num
	 */
	private void splitNum(int start, int num) {
		if (start >= num) {
			return;
		}
		for (int i = start; i <= Math.ceil(num / 2) && i != (num - i); i++) {
			// 打出从1到start的数字
			printSerial(start);
			// 把剩下的num分成两半
			System.out.print(i + " ");
			System.out.println(num - i);
		}
	}

	/**
	 * 顺序打印1到n
	 * 
	 * @param n
	 */
	private void printSerial(int n) {
		for (int i = 1; i < n; i++) {
			System.out.print(i + " ");
		}
	}
}

这是我对这个题目的思考过程,有错误的地方欢迎大牛批评指出。

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics