我有32个数字组成的数组[1,2,3,4,4,4,4,5,5,5,5,5,6,6,7,7,7,7,8,9 ,10,10,11,12,13,13,14,14,15,16,17,17]
我想将此数组划分为8个子数组,每个子数组的大小为4,这样子数组就不会有重复的元素。
我可以通过几种方式做到这一点?生成所有排列和单个随机排列的最佳解决方案是什么。子数组的顺序无关紧要。每个子数组中的元素顺序也不是。
对于我的原始问题,我不需要生成所有排列。每次运行程序时,我只需要生成一个随机排列即可。
我的方法是使用Fisher-Yates算法随机地对数组进行混洗,并不断对其进行改组,直到获得所有8个没有重复元素的子数组。当然,这不是最好的方法。
作为解决方案的一部分,我对数组进行了混洗,并从此混洗后的数组开始向子数组一个接一个地添加元素。如果任何子数组已经有一个数字,那么我会不断从混洗的数组中跳过元素,直到达到一个不是我的子数组的数字。在某些情况下,这种方法会失败。
我尝试过的伪代码
let shuffledArray = shuffle(originalArray);
let subArrays = [];
for (let i = 0; i < 8; i++) {
subArrays[i] = [];
for (let j = 0; j < 32; j++) {
if (!subArrays[i].contains(shuffledArray[j]) && !shuffledArray[j].used) {
subArrays[i].push(shuffledArray[j])
shuffledArray[j].used = true;
}
if (subArrays[i].length == 4) {
break;
}
}
}
if subArrays has any sub array such that it has duplicate elements then repeat above steps
else we have generated a random permutation
如您所见,当将所有重复的数字混排在一起后,上述方法将失败,因此,作为hack,我一次又一次重复所有步骤,直到得到结果。
我正在使用JavaScript,但是欢迎使用任何语言的答案,只要它们可以转换为JavaScript。
如果任何人都可以提供N个元素和K个组的一般解决方案,那将是很好的选择。
这是我在SO上的第一个问题。随时编辑/建议改进。
答案 0 :(得分:2)
一种选择是首先将您的列表分成相同编号的组,然后按长度排序。然后,您可以通过从最长,第二最长,第三最长,第四最长的每个组中选取元素来进行分组。清空子组时,将其删除。
这是JS实现:
function partition(arr, N){
// group items together and sort by length
// groups will be [[5, 5, 5, 5, 5], [4, 4, 4, 4], ...]
let groups = Object.values(l.reduce((obj, n) =>{
(obj[n] || (obj[n] = [])).push(n)
return obj
}, {})).sort((a,b) => b.length - a.length)
let res = []
while(groups.length >= N){
let group = []
let i = 0
while (group.length < N){
group.push(groups[i].pop())
if (groups[i].length < 1) groups.splice(i, 1)
else i++
}
res.push(group)
}
return res
}
let l = [1,2,3,4,4,4,4,5,5,5,5,5,6,6,7,7,7,7,8,9,10,10,11,12,13,13,14,14,15,16,17,17]
console.log(partition(l, 4).map(arr => arr.join(',')))
// with 5
console.log(partition(l, 5).map(arr => arr.join(',')))
答案 1 :(得分:1)
您可以使用位掩码解决此问题。首先生成所有17位数字,其中恰好有4位被设置为1。这些数字将表示一组中的可能元素,其方式是,如果设置了数字的第i位,则i + 1是其中的一部分组。
现在,从这些生成的数字中,您的任务就是选择重复满足每个元素频率限制的8个数字,这很容易做到。
如果找到其他方法,我会尽快与您联系。
编辑:或者,您可以按以下方式使用递归:以8个数字开头,所有数字最初都设置为0,首先将第(a [i] -1)位设置为1,即位设置为0,并且该位的总设置位小于4。
以递归方式到达叶子时,将有8个数字表示如上所述的位掩码。您可以将它们用于分区。
您可以使用这种方法,首先创建100组8个数字,然后从递归中返回。一旦使用完所有这100个,就可以再次运行此递归,以创建上一步中形成的集合的两倍,依此类推。
#include<bits/stdc++.h>
using namespace std;
int num=0;
vector<vector<int> > sets;
void recur(int* arr, vector<int>& masks, int i) {
if(num == 0)
return;
if(i==32){
vector<int> newSet;
for(int j=0; j<8; j++)
newSet.push_back(masks[j]);
sort(newSet.begin(), newSet.end());
int flag=0;
for(int j=0; j<sets.size(); j++){
flag=1;
for(int k=0; k<8; k++)
flag = flag && (newSet[k] == sets[j][k]);
if(flag) break;
}
if(!flag){
sets.push_back(newSet);
num--;
}
return;
}
for(int ii=0; ii<8; ii++) {
if(__builtin_popcount(masks[ii]) < 4 && (masks[ii] & (1 << (arr[i]-1))) == 0){
masks[ii] = masks[ii] ^ (1<<(arr[i] - 1));
recur(arr, masks, i+1);
masks[ii] = masks[ii] ^ (1<<(arr[i] - 1));
}
}
}
int main() {
num = 100;
int num1 = num;
vector<int> masks;
for(int i=0; i<8; i++)
masks.push_back(0);
int arr[] = {1,2,3,15,16,4,4,4,4,5,5,5,5,5,6,6,7,7,7,7,8,9,10,10,11,12,13,13,14,14,17,17};
recur(arr, masks, 0);
for(int j=0; j<num1; j++){
for(int i=0; i<8; i++){
//ith group
printf("%d group : ",i);
for(int k=0; k<17; k++){
if(sets[j][i] & (1<<k))
printf("%d ",k+1);
}
printf("\n");
}
printf("\n\n\n======\n\n\n");
}
return 0;
}
这是您要寻找的吗?
答案 2 :(得分:1)
这里是枚举一个集合(而不是您的示例中的多重集合)的所有可能性的演示,只是为了展示rapidly的组合数量如何增加。一个8个4元素部分的分区的组合数量将是巨大的。我不确定,但是您可能可以调整其中的一些想法以合并多集,或者至少首先进行部分枚举,然后随机添加重复的元素。
function f(ns, subs){
if (ns.length != subs.reduce((a,b) => a+b))
throw new Error('Subset cardinality mismatch');
function g(i, _subs){
if (i == ns.length)
return [_subs];
let res = [];
const cardinalities = new Set();
function h(j){
let temp = _subs.map(x => x.slice());
temp[j].push(ns[i]);
res = res.concat(g(i + 1, temp));
}
for (let j=0; j<subs.length; j++){
if (!_subs[j].length && !cardinalities.has(subs[j])){
h(j);
cardinalities.add(subs[j]);
} else if (_subs[j].length && _subs[j].length < subs[j]){
h(j);
}
}
return res;
}
let _subs = [];
subs.map(_ => _subs.push([]));
return g(0, _subs);
}
// https://oeis.org/A025035
let N = 12;
let K = 3;
for (let n=K; n<=N; n+=K){
let a = [];
for (let i=0; i<n; i++)
a.push(i);
let b = [];
for (let i=0; i<n/K; i++)
b.push(K);
console.log(`\n${ JSON.stringify(a) }, ${ JSON.stringify(b) }`);
let result = f(a, b);
console.log(`${ result.length } combinations`);
if (n < 7){
let str = '';
for (let i of result)
str += '\n' + JSON.stringify(i);
console.log(str);
}
console.log('------');
}
答案 3 :(得分:1)
以下python代码使用简单的方法在每次运行时生成随机分区。它对32个整数列表进行混洗(以提供随机结果),然后使用“先验拟合+回溯”方法查找该次混洗产生的第一个排列。效率:此处使用的Fisher-Yates随机播放是O(n)算法。从混洗中找到第一个排列可能接近O(n),甚至可能更差,具体取决于原始数字和混洗,如下所述。
注意事项:理想情况下,采用不同的混洗应该导致不同的分区。但这不可能,因为混洗比不同的分区多得多(大约是混洗与分区的10 20 倍)。同样理想地,每个可能的分区应该具有相等的产生概率。我不知道这种情况是否存在,甚至不知道此方法是否涵盖所有可能的分区。例如,可以想象某些分区不能通过先拟合+回溯方法生成。
尽管此方法相当快地生成了绝大多数解决方案(例如,在一毫秒之内),但由于递归早期发生的冲突(直到几层才被发现),它有时陷入困境并浪费大量时间更深的。例如,查找四组1000个不同解的时间分别为96 s,166 s,125 s和307 s,而查找100个不同解的集合的时间包括56 ms,78 ms,1.7 s,5 s,50
一些程序说明:在经过改组的列表s
中,我们保留2 mn-k 而不是k。将数据作为位掩码而不是对数字进行计数可以加快重复测试的速度。指数mn-k
(在2 mn-k 中)使数组u
排序,以便输出按升序排列。在python中,#
引入了注释。内部带有for
表达式的方括号表示“列表理解”,这是一种表示可以使用for
语句生成的列表的方式。表达式[0]*nc
表示nc
元素的列表或数组,最初都是0。
from random import randint
A = [1,2,3,4,4,4,4,5,5,5,5,5,6,6,7,7,7,7,8,
9,10,10,11,12,13,13,14,14,15,16,17,17] # Original number list
ns = len(A) # Number of numbers
nc = 8 # Number of cells
mc = 4 # Max cell occupancy
rc = range(nc) # [0,1,...nc-1]
mn = max(A) # Max number
s = [ 2**(mn-k) for k in A]
for i in range(ns-1,0,-1):
j = randint(0,i)
s[i], s[j] = s[j], s[i] # Do a shuffle exchange
# Create tracking arrays: n for cell count, u for used numbers.
n = [0]*nc
u = [0]*nc
def descend(level):
if level == ns:
return True
v = s[level] # The number we are trying to place
for i in rc:
if (v & u[i] == 0) and n[i] < mc:
u[i] |= v # Put v into cell i
n[i] += 1
if descend(level+1):
return True # Got solution, go up and out
else:
u[i] ^= v # Remove v from cell i
n[i] -= 1
return False # Failed at this level, so backtrack
if descend(0):
for v in sorted(u, reverse=True):
c = [ mn-k for k in range(mn+1) if v & 2**k]
print (sorted(c))
else:
print ('Failed')
一些示例输出:
[1, 2, 5, 9]
[3, 4, 6, 14]
[4, 5, 6, 10]
[4, 5, 7, 17]
[4, 10, 15, 16]
[5, 7, 8, 17]
[5, 7, 11, 13]
[7, 12, 13, 14]
[1, 4, 7, 13]
[2, 5, 7, 8]
[3, 4, 5, 17]
[4, 5, 6, 14]
[4, 6, 7, 9]
[5, 10, 11, 13]
[5, 10, 12, 16]
[7, 14, 15, 17]