X-number of friends want’s to go to the cinema and wish to be seated
in the best available groups. Best case is that everyone sits together
and worst case is that everyone sits alone. Fewer groups are preferred over more groups. Ballanced groups are preferred (3+3 is more preferred than 4+2).
Sitting alone is least preferred.
输入是进入影院的人数,输出应该是一个整数数组的数组,其中包含:
>有序组合(最优选的是第一个)
>每组人数
以下是一些人去电影院的例子以及这些人可以坐下的首选组合列表:
> 1人:1
> 2人:2,11
> 3人:3,2 1,1 1 1
> 4人:4,2 2,3 1 1,2 1 1,1 1 1 1
> 5人:5,3 2,4 1 1,2 2 1,2 1 1 1,1 1 1 1 1
> 6人:6,3 3,4 2 2,2 2 2,5 1 1,3 2 1,2 2 1 1,1 1 1 1 1 1 1
超过7人的示例组合爆炸,但我认为你现在明白了.
问题是:解算这个问题的算法是什么样的?
我选择的语言是C#所以如果你能用C#给出答案那就太棒了!
解决方法
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,11
3人:3,1 1,2 1 1
4人:4,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,1 5 1,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}",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 { /// <summary> /// The possible groupings are returned in order of the 'most' desirable first. Equivalent groupings are not returned (e.g. 2 + 1 vs. 1 + 2). Only one representant /// of each grouping is returned (ordered ascending. e.g. 1 + 1 + 2 + 4 + 5) /// </summary> /// <param name="numberOfFriends"></param> /// <returns></returns> 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)) { // for each group we create 'n+1' new groups // 1 + 1 + 2 + 3 + 4 // Becomes // (1+1) + 1 + 2 + 3 + 4 we can add a friend to the first group // 1 + (1+1) + 2 + 3 + 4 we can add a friend to the second group // 1 + 1 + (2+1) + 3 + 4 we can add a friend to the third group // 1 + 1 + 2 + (3+1) + 4 we can add a friend to the forth group // 1 + 1 + 2 + 3 + (4+1) we can add a friend to the fifth group // (1 + 1 + 2 + 3 + 4) + 1 friend has to sit alone 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)); } } /// <summary> /// A possible grouping of friends. E.g. /// 1 + 1 + 2 + 2 + 4 (10 friends). The array is sorted by the least friends in an group. /// </summary> 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; } } /// <summary> /// determine which group is more desirable. Example: /// Consider g1: 1 + 2 + 3 + 4 vs g2: 1 + 1 + 2 + 2 + 4 /// Group each sequence by the number of occurrences: /// /// group | g1 | g2 /// --------|------------- /// 1 | 1 | 2 /// ---------------------- /// 2 | 1 | 2 /// ---------------------- /// 3 | 1 | 0 /// ---------------------- /// 4 | 1 | 1 /// ---------------------- /// /// Sequence 'g1' should score 'higher' because it has 'less' 'ones' (least desirable) elements. /// /// If both sequence would have same number of 'ones',we'd compare the 'twos'. /// /// </summary> /// <param name="other"></param> /// <returns></returns> 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++) { // zero means that the current grouping does not contain a such group with 'numberOfFriendsInGroup' // in the example above,e.g. group '3' int thisNumberOfGroups = thisGroup.ContainsKey(numberOfFriendsInGroup) ? thisGroup[numberOfFriendsInGroup] : 0; int otherNumberOfGroups = otherGroup.ContainsKey(numberOfFriendsInGroup) ? otherGroup[numberOfFriendsInGroup] : 0; int compare = thisNumberOfGroups.CompareTo(otherNumberOfGroups); if (compare != 0) { // positive score means that the other group has more occurrences. e.g. 'this' group might have 2 groups with each 2 friends,// but the other solution might have 3 groups with each 2 friends. It's obvIoUs that (because both solutions must sum up to the same value) // this 'solution' must contain a grouping with more than 3 friends which is more desirable. return -compare; } } // they must be 'equal' in this case. 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); } /// <summary> /// may not be the best hashcode function. for alternatives look here: http://burtleburtle.net/bob/hash/doobs.html /// I got this code from here: https://stackoverflow.com/questions/3404715/c-sharp-hashcode-for-array-of-ints /// </summary> /// <param name="obj"></param> /// <returns></returns> 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; } }
现在解决方案:
brainteaser类执行递归.这个类中的一个技巧是在hashset中使用自定义比较器(PossibleGroupingComparer).这将确保当我们计算新分组时(例如1 1 2 vs 2 1 1),这些将被视为相同(我们的集合将仅包含每个等效分组的一个代表).这应该将指数运行时间减少到O(n ^ 2).
下一个技巧是可以对结果进行排序,因为我们的PossibleGroupings类实现了IComparable. Compare()方法的实现使用了上面提到的想法.此方法基本上包含此解决方案中的salt,如果您希望以不同方式对其进行分组,则应仅修改此方法.
我希望你能理解代码,否则让我知道.我试图让它可读,并不关心性能.例如,您可以在将它们返回给调用者之前对这些分组进行排序,递归中的排序不会带来太大的影响.
但有一条评论:一个典型的场景可能是电影院已经“已经”预订了许多座位而且不允许“任何”分区.在这里,您需要获取所有分区,然后逐个检查是否可以用于当前的电影院.这工作,但成本不必要的cpu.相反,我们可以使用输入来减少递归次数并缩短总体执行时间.也许有人想为此发布一个解决方案;)