アルゴリズム美団筆記試験(2020-9-6)スタッフの割り当て
@author:Jingdai
@date:2020.11.18
数日前の美団筆記試験を見て、記録してください。この質問は、9月6日の美団のアルゴリズム筆記試験の4番目の質問です。
タイトル説明
会社にはn人の従業員がいて、n人の従業員の所属を分割する必要があります。分割要件は次のとおりです。
- 誰もが部下を持たないか、少なくとも2人の直属の部下がいます
- 個人を(私を含め)持っている私は、提携します
ai
注:直属の部下と部下(自分自身を含む)は、それぞれ従業員の「息子」と「サブツリー」と見なすことができ、そのような関係があるかどうかを尋ねます。
説明を入力してください:
入力の最初の行は整数n(n <= 24)です。これは、会社にn人がいることを意味します。
次の行のN個の数字、i番目の数字は
ai
アイデア
この質問は明らかにツリーの質問です。最初に質問の具体的な意味を分析しましょう。図に示すように、最初に質問の要件を満たすいくつかの例を見てから、質問の意味をよりよく理解するためにそれを見てください。
要件1の場合、実際には、ツリー内の非リーフノードごとに、その息子の数が2以上である必要があることを意味します。つまり、ツリー内の各ノードの次数を1に等しくすることはできません。
請求項2において、各ノード値(
ai
)は、ノード(それ自体を含む)のサブツリーがノードを有することを表すことを意味するai
。したがって、ツリーのこのルートノードの場合、ツリー全体のノード数を表すため、最大ai
はルートノードであり、これがツリーを構成する場合、最大はai
必ずnに等しくなります。ai
事前に決定されたnに等しい場合、最大値を実行できます。請求項1および請求項2と組み合わせて
ai
、値が2に等しいかどうかを調べます。これはai
、最小で1(要求2)であり、非リーフノードが少なくとも2つのリーフノードを含むため、そのようなツリーを構成してはなりません。非リーフノード最小値は3であるため、入力値に2が含まれているかどうかに応じて、前処理の判断を行うこともできます。次に、この問題を解決する方法を見てみましょう。ここでは、バックトラッキングアルゴリズムを使用して問題を解決します。まず、変数の定義を見てください。
nodes
アレイ
nodes
入力配列ai
値を記録します。まず、nodes
降順nodes[0]
で、nodes[0]
すべてが親ノードを持っていることに加えて、それはツリーのルートです。ここで、必要なノードをルートとするサブツリーの代表nodes
など、必要なノードの数として割り当てられた配列の値は、nodes[i] = 6
5(自身のカウント1)ノードを割り当てます。nodes[i] = 1
代表的な再配布でそのノードの子が必要ない場合。
children
アレイ各ノードの子の数を記録します。これは、各ノードの次数が1でないかどうかを最終的に判断するために使用されます。
unfinishedParentSet
セットするこのセットは、子ノードの親ノードに割り当てられたインデックス、つまり
nodes[i] > 1
すべてのノードを設定する必要があることを表します。このセットが空の場合、割り当てる必要のある親ノードがないことを意味します。次に、アルゴリズムを見てください。上記のように、各入力の最初の前処理は、
ai
2に等しい場合、代表はそのようなツリーを構成しません。入力が完了した後、nodes
降順でソートされ、nodes[0]
nに等しくない場合、代表はそのようなツリーを構成しません。木。前処理が完了したら、判断できないものについては次のステップに進みます。最初
nodes
に配列をトラバースします。nodes
配列インデックスが1より大きい場合が追加されunfinishedParentSet
ます。次に、深さ優先探索とバックトラックを実行します。
示されているよう
nodes
に、配列の要素nodes[1]
(nodes[0]
ルートノード、親ノードがない)unfinishedParentSet
は、親ノードで開始するように割り当てようとします。親FIG xノードはunfinishedParentSet
ノード内にあり、親ノードの割り当ては次の親ノードの試行に失敗します。成功した場合はtrueを返し、すべての親ノードが失敗した場合はfalseを返します。尝试分配即
nodes[i] -= nodes[child]
,将children[i]
加1,同时如果nodes[i]
等于1后代表该父节点分配完毕,从unfinishedParentSet
中删去。 如果尝试分配失败就回溯,将nodes
、children
和unfinishedParentSet
还原。同时,由于题目说明节点度不能为1,可以根据此进行剪枝,当nodes[i] - nodes[child] == 1 && children[i] = 0
时,不进行分配,因为这样分配会使节点 i 的度为1,就不用往下走了,进行剪枝。同时注意一个细节,按算法逻辑来说从头到尾用一个
unfinishedParentSet
就行,但是我们在遍历过程中会对 set 集合中的元素进行增减,Java的foreach
遍历中不能增减元素(会抛异常),iterator
遍历中不能增加元素,普通的for遍历增减元素会影响遍历的语意(删除一个元素可能会少遍历到元素),所以代码中每一次分配时都会重新构建一个和原来一样的 set ,对新的 set 进行增减。当然你也可以多用一个字段记录该节点是否被删除。这里和具体的语言实现有关,和算法没什么关系。具体代码如下。
代码
import java.util.*; public class Solution { // number of nodes public static int n; public static int[] nodes; // the number of the node i's children public static int[] children; public static void main(String[] args) { Scanner in = new Scanner(System.in); n = in.nextInt(); nodes = new int[n]; children = new int[n]; in.nextLine(); boolean canMakeTree = true; for (int i = 0; i < n; i++) { nodes[i] = in.nextInt(); if (nodes[i] == 2) canMakeTree = false; } Arrays.sort(nodes); // reverse for (int i = 0; i < n/2; i++) { int temp = nodes[i]; nodes[i] = nodes[n-1-i]; nodes[n-1-i] = temp; } if (nodes[0] != n) { canMakeTree = false; } if (!canMakeTree) { System.out.println("NO"); return; } Set<Integer> unfinishedParentSet = new HashSet<>(); for (int i = 0; i < n; i++) { if (nodes[i] > 1) unfinishedParentSet.add(i); } if (allocateNode(1, unfinishedParentSet)) { System.out.println("YES"); } else { System.out.println("NO"); } } public static boolean allocateNode(int index, Set<Integer> unfinishedParentSet) { Set<Integer> newSet = new HashSet<>(unfinishedParentSet); if (index == n) { if (unfinishedParentSet.size() != 0) return false; for (int i : children) { if (i == 1) return false; } return true; } for (int parentIndex : unfinishedParentSet) { if (nodes[parentIndex] > nodes[index]) { if (nodes[parentIndex] == nodes[index] + 1 && children[parentIndex] == 0) { continue; } // try to allocate nodes[parentIndex] -= nodes[index]; if (nodes[parentIndex] == 1) newSet.remove(parentIndex); children[parentIndex]++; // succeed if (allocateNode(index + 1, newSet)) return true; // fail to allocate if (nodes[parentIndex] == 1) newSet.add(parentIndex); nodes[parentIndex] += nodes[index]; children[parentIndex]--; } } return false; } }