【问题标题】:Divide array into sub arrays such that no sub array contains duplicate elements将数组划分为子数组,使子数组不包含重复元素
【发布时间】:2023-03-17 17:41:01
【问题描述】:

我有一个由 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

如您所见,上述方法在将所有重复的数字放在最后时失败,因此我一次又一次地重复所有步骤,直到得到结果。

我正在使用 JavaScript,但欢迎使用任何语言的答案,只要它们可以转换为 JavaScript。

如果有人能提供 N 个元素和 K 个组的通用解决方案,那就太好了。

这是我在 SO 的第一个问题。随意编辑/提出改进建议。

【问题讨论】:

  • @MarkMeyer 这是[1,2,3,4][4,5,6,7][4,5,6,7][4,5,7,8][5,7,9,10][5,10,11,12][13,14,15,17][13,14,16,17][1,2,13,14][4,5,16,17][4,5,6,7][4,5,7,8][5,7,9,10][5,10,11,12][13,14,15,17][3,4,6,7][17,2,13,4][4,5,6,7][4,5,6,7][4,5,7,8][5,7,9,10][5,10,11,12][13,14,15,17][3,14,16,1] 的三种可能组合

标签: arrays algorithm permutation combinatorics


【解决方案1】:

您可以使用位掩码来解决此问题。首先生成所有 4 位设置为 1 的所有 17 位数字。这些数字将代表一组中的可能元素,如果设置了数字的第 i 位,则 i+1 是其中的一部分团体。

现在,从这些生成的数字中,您的任务只是选择 8 个重复满足每个元素频率限制的数字,这很容易完成。

如果我找到其他方法,我会回复你。

编辑:或者,您可以通过以下方式使用递归:从 8 个数字开始,所有初始设置为 0,首先将 (a[i]-1)'th 位设置为 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;
}

这是你要找的吗?

【讨论】:

  • Hi Himanshu... 让我把你的代码转换成 JS,我会告诉你它是否有效
  • 好的,但是您也可以通过在 C++ 中运行此代码来检查输出。
【解决方案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(',')))

【讨论】:

  • 嗨,马克。非常感谢您的努力,但这每次运行时都会给我相同的输出。我想在每次运行时生成一个随机排列。
  • 我一定是误会了。我以为您是通用解决方案,退回的订单无关紧要。这将返回尽可能多的子列表,但它总是相同的。我好像错过了I just have to generate a random permutation every time my program is run
【解决方案3】:

这是枚举集合的所有可能性的演示(不是您的示例中的多重集合),只是为了展示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('------');
}

【讨论】:

  • 嗨 גלעד ברקן 让我为我的作业调整你的代码,我会知道它是否对我有用
【解决方案4】:

以下 python 代码使用一种简单的方法在每次运行时生成随机分区。它对 32 个整数的列表进行打乱(以给出随机结果),然后使用首次拟合 + 回溯方法来找到由该打乱产生的第一个排列。效率:这里使用的 Fisher-Yates shuffle 是一个 O(n) 算法。从 shuffle 中找到第一个排列可能接近 O(n) 或者可能更糟,这取决于原始数字和 shuffle,如下所述。

注意事项:理想情况下,不同的 shuffle 会导致不同的分区。但这不可能,因为不同的分区有很多不同的 shuffle(可能是 shuffle 与分区的 1020 倍)。同样理想的是,每个可能的分区都应该具有相同的产生概率。我不知道这里是不是这样,甚至不知道这种方法是否涵盖了所有可能的分区。例如,可以想象,有些分区不能通过 first-fit + backtracking 的方式生成。

虽然此方法生成绝大多数解决方案的速度非常快(例如不到一毫秒),但它有时会陷入困境并浪费大量时间,因为在递归早期发生冲突,直到几层才能检测到更深。例如,找到四组 1000 个不同解的时间分别为 96 s、166 s、125 s 和 307 s,而找到 100 个不同解的集合的时间包括 56 ms、78 ms、1.7 s、5 s、50秒。

一些程序注释:在洗牌列表s 中,我们保留 2mn-k 而不是 k。将数据用作位掩码而不是计数数字可以加快重复测试。指数 mn-k (in 2mn-k) 让数组 u 排序,以便输出按升序排列。在 python 中,# 引入了 cmets。带有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]

【讨论】:

    猜你喜欢
    • 2019-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-18
    相关资源
    最近更新 更多