我认为你需要递归执行,但要确保不要一遍又一遍地对同一组进行分区,否则执行时间将呈指数级增长。在我的解决方案中,看起来我有O(n*n)的复杂度(你可以验证一下;),请参见下面的结果。另一件事是你提到的期望函数。我不知道这样的函数会是什么样子,但你可以比较两个分区。例如,分区1 + 1 + 2 + 4不如1 + 2 + 2 + 3好,因为它有两个“1”。一个通用规则可能是“如果一个分区中有更多相同人数的人被分组,则该分区不太理想”。这很有道理,人们越聚在一起,就越好。我的解决方案采用这种方法比较两种可能的分组方式,并得到了你想要实现的结果。让我先展示一些结果,然后是代码。
var sut = new BrainTeaser();
for (int n = 1; n <= 6; n++) {
StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0} person{1}: ", n, n > 1 ? "s" : "");
var array = sut.Solve(n).Select(x => x.ToString()).ToArray();
sb.AppendLine(string.Join(", ", array));
Console.WriteLine(sb.ToString());
}
1个人:1
2个人:2,1+1
3个人:3,1+2,1+1+1
4个人:4,2+2,1+3,1+1+2,1+1+1+1
5个人:5,2+3,1+4,1+2+2,1+1+3,1+1+1+2,1+1+1+1+1
6个人:6,3+3,2+4,2+2+2,1+5,1+2+3,1+1+4,1+1+2+2,1+1+1+3,1+1+1+1+2,1+1+1+1+1+1
性能看起来是O(n*n):
var sut = new BrainTeaser();
for (int n = 1; n <= 40; n++) {
Stopwatch watch = new Stopwatch();
watch.Start();
var count = sut.Solve(n).Count();
watch.Stop();
Console.WriteLine("Problem solved for {0} friends in {1} ms. Number of solutions {2}", n, watch.ElapsedMilliseconds, count);
}
Problem solved for 1 friends in 17 ms. Number of solutions 1
Problem solved for 2 friends in 49 ms. Number of solutions 2
Problem solved for 3 friends in 2 ms. Number of solutions 3
Problem solved for 4 friends in 1 ms. Number of solutions 5
Problem solved for 5 friends in 0 ms. Number of solutions 7
Problem solved for 6 friends in 2 ms. Number of solutions 11
Problem solved for 7 friends in 0 ms. Number of solutions 15
Problem solved for 8 friends in 0 ms. Number of solutions 22
Problem solved for 9 friends in 1 ms. Number of solutions 30
Problem solved for 10 friends in 1 ms. Number of solutions 42
Problem solved for 11 friends in 4 ms. Number of solutions 56
Problem solved for 12 friends in 4 ms. Number of solutions 77
Problem solved for 13 friends in 7 ms. Number of solutions 101
Problem solved for 14 friends in 9 ms. Number of solutions 135
Problem solved for 15 friends in 15 ms. Number of solutions 176
Problem solved for 16 friends in 21 ms. Number of solutions 231
Problem solved for 17 friends in 30 ms. Number of solutions 297
Problem solved for 18 friends in 43 ms. Number of solutions 385
Problem solved for 19 friends in 61 ms. Number of solutions 490
Problem solved for 20 friends in 85 ms. Number of solutions 627
Problem solved for 21 friends in 117 ms. Number of solutions 792
Problem solved for 22 friends in 164 ms. Number of solutions 1002
Problem solved for 23 friends in 219 ms. Number of solutions 1255
Problem solved for 24 friends in 300 ms. Number of solutions 1575
Problem solved for 25 friends in 386 ms. Number of solutions 1958
Problem solved for 26 friends in 519 ms. Number of solutions 2436
Problem solved for 27 friends in 677 ms. Number of solutions 3010
Problem solved for 28 friends in 895 ms. Number of solutions 3718
Problem solved for 29 friends in 1168 ms. Number of solutions 4565
Problem solved for 30 friends in 1545 ms. Number of solutions 5604
Problem solved for 31 friends in 2025 ms. Number of solutions 6842
Problem solved for 32 friends in 2577 ms. Number of solutions 8349
Problem solved for 33 friends in 3227 ms. Number of solutions 10143
Problem solved for 34 friends in 4137 ms. Number of solutions 12310
Problem solved for 35 friends in 5300 ms. Number of solutions 14883
Problem solved for 36 friends in 6429 ms. Number of solutions 17977
Problem solved for 37 friends in 8190 ms. Number of solutions 21637
Problem solved for 38 friends in 10162 ms. Number of solutions 26015
Problem solved for 39 friends in 12643 ms. Number of solutions 31185
现在让我贴出解决方案中涉及的3个类:
public class BrainTeaser {
public IEnumerable<PossibleGrouping> Solve(int numberOfFriends) {
if (numberOfFriends == 1) {
yield return new PossibleGrouping(1);
yield break;
}
HashSet<PossibleGrouping> possibleGroupings = new HashSet<PossibleGrouping>(new PossibleGroupingComparer());
foreach (var grouping in Solve(numberOfFriends - 1)) {
AddAllPartitions(grouping, possibleGroupings);
}
foreach (var possibleGrouping in possibleGroupings.OrderByDescending(x => x)) {
yield return possibleGrouping;
}
}
private void AddAllPartitions(PossibleGrouping grouping, HashSet<PossibleGrouping> possibleGroupings) {
for (int i = 0; i < grouping.FriendsInGroup.Length; i++) {
int[] newFriendsInGroup = (int[]) grouping.FriendsInGroup.Clone();
newFriendsInGroup[i] = newFriendsInGroup[i] + 1;
possibleGroupings.Add(new PossibleGrouping(newFriendsInGroup));
}
var friendsInGroupWithOneAtTheEnd = grouping.FriendsInGroup.Concat(new[] {1}).ToArray();
possibleGroupings.Add(new PossibleGrouping(friendsInGroupWithOneAtTheEnd));
}
}
public class PossibleGrouping : IComparable<PossibleGrouping> {
private readonly int[] friendsInGroup;
public int[] FriendsInGroup {
get { return friendsInGroup; }
}
private readonly int sum;
public PossibleGrouping(params int[] friendsInGroup) {
this.friendsInGroup = friendsInGroup.OrderBy(x => x).ToArray();
sum = friendsInGroup.Sum();
}
public int Sum {
get { return sum; }
}
public int CompareTo(PossibleGrouping other) {
var thisGroup = (from n in friendsInGroup group n by n).ToDictionary(x => x.Key,
x => x.Count());
var otherGroup = (from n in other.friendsInGroup group n by n).ToDictionary(x => x.Key,
x => x.Count());
return WhichGroupIsBetter(thisGroup, otherGroup);
}
private int WhichGroupIsBetter(IDictionary<int, int> thisGroup, IDictionary<int, int> otherGroup) {
int maxNumberOfFriendsInAGroups = Math.Max(thisGroup.Keys.Max(), otherGroup.Keys.Max());
for (int numberOfFriendsInGroup = 1;
numberOfFriendsInGroup <= maxNumberOfFriendsInAGroups;
numberOfFriendsInGroup++) {
int thisNumberOfGroups = thisGroup.ContainsKey(numberOfFriendsInGroup)
? thisGroup[numberOfFriendsInGroup]
: 0;
int otherNumberOfGroups = otherGroup.ContainsKey(numberOfFriendsInGroup)
? otherGroup[numberOfFriendsInGroup]
: 0;
int compare = thisNumberOfGroups.CompareTo(otherNumberOfGroups);
if (compare != 0) {
return -compare;
}
}
return 0;
}
public override string ToString() {
return string.Join("+", friendsInGroup.Select(x => x.ToString()).ToArray());
}
}
public class PossibleGroupingComparer : EqualityComparer<PossibleGrouping> {
public override bool Equals(PossibleGrouping x, PossibleGrouping y) {
return x.FriendsInGroup.SequenceEqual(y.FriendsInGroup);
}
public override int GetHashCode(PossibleGrouping obj) {
var array = obj.FriendsInGroup;
int hc = obj.FriendsInGroup.Length;
for (int i = 0; i < array.Length; ++i) {
hc = unchecked(hc*314159 + array[i]);
}
return hc;
}
}
现在来介绍解决方案:
递归的功能由脑筋急转弯类完成。该类中的一个技巧是在哈希集中使用自定义比较器(PossibleGroupingComparer)。这将确保当我们计算新的分组时(例如,1 + 1 + 2与2 + 1 + 1),它们将被视为相同(我们的集合将只包含每个等效分组的一个代表)。这应该将指数运行时间降至O(n ^ 2)。
下一个技巧是可以对结果进行排序,因为我们的PossibleGroupings类实现了IComparable。 Compare()方法的实现使用上述思想。这种方法本质上包含了此解决方案中的关键点,如果您想以不同的方式对其进行分组,则应仅修改此方法。
希望您能理解代码,否则请告诉我。我试图使其易读,并且并不太关心性能。例如,您可以仅在将它们返回给调用者之前对分组进行排序,在递归内部进行排序并没有太大作用。
但是需要注意的一点是:典型情况可能是电影院已经预订了许多座位,并且不允许进行任何分区。在这种情况下,您需要获取所有分区,然后逐个检查是否可以用于当前电影院。这可以工作,但会浪费不必要的CPU。相反,我们可以使用输入来减少递归的次数并提高整体执行时间。也许有人想发布此解决方案;)
2+2+2
比5+1
更受欢迎的原因。 - Tung