随机产生和为S的N个正整数

本文介绍两种生成指定和的随机正整数的方法:尺子法和锯木头法,并提供Java实现代码。

如果给你一个问题:“随机产生和为S的N个正整数”, 你会如何做呢?

 

针对该问题,解决的方法有很多种。在这篇文章中,我将为大家给出两种比较好理解的解决方法:一个是“尺子法”;另外一个是“锯木头法”。 (名字随便取的,主要是方便理解用)。

 

方法一:尺子法

 

思想:将给定值S看成一个尺子的长度,那么,生成N个和为S的正整数的问题就变成在尺子中寻找出N-1个不同的刻度加上最小刻度0和最大刻度S 一共有N+1个刻度然后,从小到大,计算出相邻刻度的长度,这些长度就可以认为是随机的,因为尺子中产生的N-1个刻度是随机的。

有了上述思想,我们只要如下三个步骤就能完成这个功能。

 

  1. 验证参数S和N的正确性
  2. 尺子中产生N-1个不同刻度
  3. 计算相邻刻度之间的值

 

/***
	 * <p>
	 * 随机产生和为sum(如10)的num(如5)个正整数
	 * </p>
	 * 
	 * @param num
	 *            期望产生的随机数个数
	 * @param sum
	 *            所有产生随机数的和
	 * @return 返回满足和为sum的num个随机正整数组成的数组
	 */
	public Integer[] random(int num, int sum) {

		/**
		 * Step1: 验证参数正确性
		 */
		if (num < 1) {
			throw new IllegalArgumentException("产生随机数个数的num不能小于1");
		}
		if (sum < num) {
			throw new IllegalArgumentException("产生随机数个数的num不能大于和sum");
		}
		if (sum <= 0) {
			throw new IllegalArgumentException("sum需要为正整数");
		}

		/**
		 * Step2: 0~sum之间随机产生num-1个不同的刻度
		 */
		Random rand = new Random();
		Set<Integer> locations = new TreeSet<>();
		while (locations.size() < num - 1) {
			locations.add(rand.nextInt(sum - 1) + 1);
		}

		locations.add(0);
		locations.add(sum);

		/**
		 * Step3: 计算出相邻刻度的差,计算出来的长度就可以代表一个随机数
		 */
		Integer[] locationsArray = locations.toArray(new Integer[] {});
		Integer[] resultArray = new Integer[num];
		for (int i = 0; i < num; i++) {
			resultArray[i] = locationsArray[i + 1] - locationsArray[i];
		}
		
		return resultArray;
	}

 

 

 方法二:锯木头法

 

思想:锯木头法的思想则是将S看成木头的长度,随机产生和为S的N个正整数的问题转换成锯N-1次木头,将产生N段小木头,N段的小木头其长度和就是S

 


 

有了上述思想,我们便可以通过如下几个步骤实现该方法:

 

  1. 验证参数S和N的正确性
  2. 锯N-1次木头

在锯木头的时候,需要考虑可锯的长度大笑 

 

	/***
	 * <p>
	 * 随机产生和为sum(如10)的num(如5)个正整数
	 * </p>
	 * 
	 * @param num
	 *            期望产生的随机数个数
	 * @param sum
	 *            所有产生随机数的和
	 * @return 返回满足和为sum的num个随机正整数组成的数组
	 */
	public int[] random2(int num, int sum) {

		/**
		 * Step1: 验证参数正确性
		 */
		if (num < 1) {
			throw new IllegalArgumentException("产生随机数个数的num不能小于1");
		}
		if (sum < num) {
			throw new IllegalArgumentException("产生随机数个数的num不能大于和sum");
		}
		if (sum <= 0) {
			throw new IllegalArgumentException("sum需要为正整数");
		}

		/**
		 * 如果只有一个 直接返回
		 */
		if (num == 1) {
			return new int[] { sum };
		}
		
		/**
		 * Step2: 锯木头的方法
		 */

		Random rand = new Random();

		int[] randomNumbers = new int[num];

		// 剩余数
		int remainingNum = sum;
		for (int i = 0; i < num - 1; i++) {
			/**
			 * 可以锯掉可选值 
			 * remaining - (num - (i+1)) + 1 =  remainingNum - num + i + 1
			 */
			int randNum = rand.nextInt(remainingNum - num + i + 1) + 1;
			remainingNum -= randNum;
			randomNumbers[i] = randNum;
		}

		/**
		 * 最后一个直接赋值即可
		 */
		randomNumbers[num - 1] = remainingNum;

		return randomNumbers;
	}

 

 

详细代码及某次测试运行结果如下:

 

 

import java.util.Random;
import java.util.Set;
import java.util.TreeSet;

/**
 * @author wangmengjun
 *
 */
public class RandomExample_1 {

	/***
	 * <p>
	 * 随机产生和为sum(如10)的num(如5)个正整数
	 * </p>
	 * 
	 * @param num
	 *            期望产生的随机数个数
	 * @param sum
	 *            所有产生随机数的和
	 * @return 返回满足和为sum的num个随机正整数组成的数组
	 */
	public Integer[] random(int num, int sum) {

		/**
		 * Step1: 验证参数正确性
		 */
		if (num < 1) {
			throw new IllegalArgumentException("产生随机数个数的num不能小于1");
		}
		if (sum < num) {
			throw new IllegalArgumentException("产生随机数个数的num不能大于和sum");
		}
		if (sum <= 0) {
			throw new IllegalArgumentException("sum需要为正整数");
		}

		/**
		 * Step2: 0~sum之间随机产生num-1个不同的刻度
		 */
		Random rand = new Random();
		Set<Integer> locations = new TreeSet<>();
		while (locations.size() < num - 1) {
			locations.add(rand.nextInt(sum - 1) + 1);
		}

		locations.add(0);
		locations.add(sum);

		/**
		 * Step3: 计算出相邻刻度的差,计算出来的长度就可以代表一个随机数
		 */
		Integer[] locationsArray = locations.toArray(new Integer[] {});
		Integer[] resultArray = new Integer[num];
		for (int i = 0; i < num; i++) {
			resultArray[i] = locationsArray[i + 1] - locationsArray[i];
		}
		
		return resultArray;
	}

	/***
	 * <p>
	 * 随机产生和为sum(如10)的num(如5)个正整数
	 * </p>
	 * 
	 * @param num
	 *            期望产生的随机数个数
	 * @param sum
	 *            所有产生随机数的和
	 * @return 返回满足和为sum的num个随机正整数组成的数组
	 */
	public int[] random2(int num, int sum) {

		/**
		 * Step1: 验证参数正确性
		 */
		if (num < 1) {
			throw new IllegalArgumentException("产生随机数个数的num不能小于1");
		}
		if (sum < num) {
			throw new IllegalArgumentException("产生随机数个数的num不能大于和sum");
		}
		if (sum <= 0) {
			throw new IllegalArgumentException("sum需要为正整数");
		}

		/**
		 * 如果只有一个 直接返回
		 */
		if (num == 1) {
			return new int[] { sum };
		}
		
		/**
		 * Step2: 锯木头的方法
		 */

		Random rand = new Random();

		int[] randomNumbers = new int[num];

		// 剩余数
		int remainingNum = sum;
		for (int i = 0; i < num - 1; i++) {
			/**
			 * 可以锯掉可选值 
			 * remaining - (num - (i+1)) + 1 =  remainingNum - num + i + 1
			 */
			int randNum = rand.nextInt(remainingNum - num + i + 1) + 1;
			remainingNum -= randNum;
			randomNumbers[i] = randNum;
		}

		/**
		 * 最后一个直接赋值即可
		 */
		randomNumbers[num - 1] = remainingNum;

		return randomNumbers;
	}

}

 

 

import java.util.Arrays;

/**
 * @author wangmengjun
 *
 */
public class Main {

	public static void main(String[] args) {

		RandomExample_1 example1 = new RandomExample_1();
		int num = 6;
		int sum = 30;
		System.out.println(String.format("随机产生和为%d的%d个正整数", sum, num));
		for (int i = 1; i <= 10; i++) {
			System.out.println(String.format("第%d遍random()产生结果 -- %s", i,
					Arrays.toString(example1.random(num, sum))));
			System.out.println(String.format("第%d遍random2()产生结果 -- %s", i,
					Arrays.toString(example1.random2(num, sum))));
		}
	}

}

 

 

随机产生和为30的6个正整数

第1遍random()产生结果 -- [2, 4, 4, 6, 5, 9]

第1遍random2()产生结果 -- [24, 1, 2, 1, 1, 1]

第2遍random()产生结果 -- [6, 4, 1, 1, 6, 12]

第2遍random2()产生结果 -- [17, 1, 5, 5, 1, 1]

第3遍random()产生结果 -- [1, 15, 1, 6, 3, 4]

第3遍random2()产生结果 -- [2, 4, 1, 7, 9, 7]

第4遍random()产生结果 -- [16, 1, 1, 4, 5, 3]

第4遍random2()产生结果 -- [11, 4, 6, 5, 1, 3]

第5遍random()产生结果 -- [4, 4, 6, 7, 4, 5]

第5遍random2()产生结果 -- [6, 13, 1, 3, 6, 1]

第6遍random()产生结果 -- [10, 1, 16, 1, 1, 1]

第6遍random2()产生结果 -- [18, 7, 2, 1, 1, 1]

第7遍random()产生结果 -- [4, 1, 10, 8, 2, 5]

第7遍random2()产生结果 -- [8, 6, 6, 4, 3, 3]

第8遍random()产生结果 -- [1, 6, 3, 8, 1, 11]

第8遍random2()产生结果 -- [4, 7, 3, 7, 2, 7]

第9遍random()产生结果 -- [3, 5, 13, 3, 1, 5]

第9遍random2()产生结果 -- [13, 4, 1, 4, 2, 6]

第10遍random()产生结果 -- [4, 5, 12, 3, 3, 3]

第10遍random2()产生结果 -- [17, 3, 7, 1, 1, 1]

 

<think>首先,用户的问题是基于之前的引用内容:生成为32的4个随机正整数。引用中提供了两种方法:一种是C#风格的代码,用于生成固定的随机整数序列;另一种是Python中的方法,使用random.sample来截取列表。 引用[1]描述了这样一个方法:先生成n-1个随机数(在0到sum之间),排序后添加0sum,然后取差值来得到序列。这样可以保证总正好是sum。 引用[2]提到使用Python的random.sample来生成指定范围内的数值序列,但在示例中更多是用于截取列表。实际上,random.sample用于从列表中随机选择元素,而不是直接生成随机整数序列。 用户的具体需求是:生成为32的4个随机正整数。 关键点是: - 总固定为32。 - 4个必须是正整数(即大于0)。 - 随机生成。 在引用[1]的方法中,C#代码生成的是非负整数序列(包括0),因为随机数是在[0, sum]之间生成。但用户要求正整数,所以需要生成大于0的数。这意味着序列中的每个数至少为1。 为了确保总为32且有4个正整数,每个数的最小值应该是1。因此,总的最小可能值是4(如果所有数都是1),最大取决于上限,但用户没有指定上限。 用户没有指定每个数的上限,但既然是正整数,且总32,理论上可以有较大范围,但为了合理性,可能需要设置上限以避免极端值。然而,引用中没有指定上限。 在引用[1]的方法中,生成随机数在[0, sum]之间,但排序差值后可能包含0。但在用户的问题中,要求正整数,所以需要调整。 一个方法是: 1. 生成3个随机整数(因为要4个数,生成n-1=3个随机数)。 2. 这些随机数应该在[0, 32]之间(总为32)。 3. 然后添加032,排序,取差值。 4. 但差值可能为0,这不是正整数。 所以需要修改以确保所有数都大于0。 标准方法是生成固定的随机正整数: - 生成n-1个随机数在[1, sum-1]之间或类似范围,但需要确保最小值为1。 一个常见技巧是: - 先为每个数分配最小值1(因为正整数),所以总剩余为32 - 4 = 28。 - 然后生成4个非负整数,其总为28。 - 然后每个数加上1恢复为正整数。 参考星星条方法或multiset方法。 具体步骤: 1. 由于有4个数,每个至少1,先减掉最小值:剩余总 = 32 - 4 * 1 = 28。 2. 现在生成4个非负整数(可以是0),其为28。 3. 然后每个数加1,这样每个数至少1,总为28 + 4 = 32。 如何生成4个非负整数为28? 使用引用[1]的方法: - 生成3个随机数在[0, 28]之间(因为n-1=3)。 - 排序这些数:比如 r1, r2, r3 (排序后)。 - 添加028:0, r1, r2, r3, 28。 - 取差值:d1 = r1 - 0, d2 = r2 - r1, d3 = r3 - r2, d4 = 28 - r3。 - 这些差值是非负整数,是28。 - 然后每个d_i 加1得到最终序列:a_i = d_i + 1。 这样确保每个a_i >=1,为(∑d_i) + 4 = 28 + 4 = 32。 在Python中,可以用random.randint或random.sample来实现。 引用[3]提到np.random.randint(5,15,n)来生成范围在5到14的n个整数,但用户没有指定范围,只要求为32的正整数。 所以,我需要提供一个Python函数来生成这样的序列。 用户在上一次交互中提到了引用,但在这里用户直接问如何生成,所以我可以直接回答。 在回答中,我需要: - 使用中文。 - 对于数学部分,行内公式用$...$,独立公式用$$...$$。 - 结构清晰。 - 生成相关问题。 用户要求:"我想获取总为32的4个随机正整数",所以我应该提供一个方法。 基于引用[1]的方法,调整为正整数。 在Python中实现。 定义一个函数: import random def generate_fixed_sum_integers(total, count, min_val=1): # 每个数最小为min_val(默认1) # 先计算剩余总 remaining = total - count * min_val if remaining < 0: raise ValueError("总太小,无法生成正整数序列") # 生成count-1个随机数在[0, remaining]之间 randoms = [0] # 开始添加0 for i in range(count - 1): randoms.append(random.randint(0, remaining)) randoms.append(remaining) # 添加剩余总 randoms.sort() # 排序 # 取差值 diffs = [] for i in range(1, len(randoms)): diffs.append(randoms[i] - randoms[i-1]) # 每个差值加上min_val result = [diff + min_val for diff in diffs] return result 测试:总32,count=4,min_val=1。 这样应该工作。 引用[2]提到random.sample,但那更适用于从一个固定列表抽样,不适合这里。 所以使用上述方法。 在回答中,先解释方法,然后提供代码。 可能需要考虑效率,但对于小数量没问题。 生成相关问题:例如,如何确保范围?如何避免极端值?等。 回答结构: 1. 解释方法。 2. 提供Python代码。 3. 生成相关问题。 使用中文。 在解释中,提到基于引用[1]的方法。 </think>基于您的需求(生成为32的4个随机正整数提供的引用内容(特别是引用[1]中的方法),我将逐步解释一个可靠的方法,并提供一个Python实现。核心思路是利用“差分法”生成固定的序列,但需确保所有数字均为正数(≥1)。引用[1]中的方法先生成随机分隔点,排序后取差值,但原始方法可能产生0(非正整数)。因此,我们需要调整以确保每个数至少为1。 ### 方法原理 1. **问题转换**: 生成4个正整数(每个≥1)为32,等价于: - 先为每个数分配最小值1(确保正数),剩余总 = $32 - 4 \times 1 = 28$。 - 生成4个非负整数(允许0),其为28。 - 最后每个数加1,恢复为正整数序列(为 $28 + 4 = 32$)。 2. **生成非负整数序列**(引用[1]方法调整): - 生成3个随机分隔点(因为序列长度n=4,需n-1个点),在[0, 28]范围内均匀采样。 - 添加边界点028,排序后形成有序列表(如 $[0, r_1, r_2, r_3, 28]$)。 - 取连续差值:$d_1 = r_1 - 0$,$d_2 = r_2 - r_1$,$d_3 = r_3 - r_2$,$d_4 = 28 - r_3$,这些 $d_i$ 是非负整数且为28。 - 最终序列:$a_i = d_i + 1$(每个≥1,总32)。 该方法保证: - **均匀分布**:序列在满足约束的空间中均匀随机(引用[1]的策略)。 - **效率**:时间复杂度 $O(n \log n)$(仅排序步骤),适用于小规模数据。 ### Python实现 以下是完整的Python函数,可直接生成为固定值的随机正整数序列: ```python import random def generate_fixed_sum_integers(total_sum, count, min_val=1): """ 生成固定的随机正整数序列。 参数: total_sum (int): 总(如32) count (int): 数字个数(如4) min_val (int): 每个数字的最小值(默认为1,确保正整数) 返回: list: 随机正整数列表,为total_sum """ # 计算剩余总(扣除最小值基数) remaining = total_sum - count * min_val if remaining < 0: raise ValueError("总太小,无法生成正整数序列") # 生成分隔点:添加0剩余总,中间插入count-1个随机点 points = [0] # 起始点 for _ in range(count - 1): points.append(random.randint(0, remaining)) # 在[0, remaining]随机采样 points.append(remaining) # 结束点 points.sort() # 排序 # 计算差值(非负整数) diffs = [] for i in range(1, len(points)): diffs.append(points[i] - points[i-1]) # 加回最小值,得到最终正整数序列 result = [diff + min_val for diff in diffs] return result # 示例:生成为32的4个随机正整数 random_sequence = generate_fixed_sum_integers(32, 4) print("生成序列:", random_sequence) print("验证总:", sum(random_sequence)) # 应输出32 ``` **运行示例**: - 每次执行可能输出不同序列,例如: - `生成序列: [5, 9, 7, 11]`(总为 $5+9+7+11=32$) - `生成序列: [3, 10, 8, 11]`(总为 $3+10+8+11=32$) - `生成序列: [6, 6, 12, 8]`(总为 $6+6+12+8=32$) ### 注意事项 - **随机性保证**:使用`random.randint`确保均匀随机采样,符合引用[1]引用[2]的核心思想。 - **边界处理**:如果总过小(如生成10个正整数但总<10),函数会报错(避免无效输入)。 - **扩展性**:可通过`min_val`参数调整最小值(如设为2则所有数≥2)。 - **效率**:对于更大规模(如count>1000),排序步骤可能成为瓶颈,但本例中(n=4)非常高效。 ### 相关问题 1. 如果需要限制每个随机数的上限(如最大值不超过15),如何修改算法?[^1] 2. 这种方法在生成较大的序列时(如总1000, count=100),如何优化性能? 3. 生成固定的随机序列在哪些实际场景中有应用(如资源分配或游戏设计)?[^2] [^1]: 引用[1]提到限制最大值的需求,可通过修改随机数范围实现。 [^2]: 引用[2][3]讨论了在Python中生成随机序列的应用背景。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值