美団筆記試験(2020-9-6)スタッフの割り当て

アルゴリズム美団筆記試験(2020-9-6)スタッフの割り当て

@author:Jingdai
@date:2020.11.18

数日前の美団筆記試験を見て、記録してください。この質問は、9月6日の美団のアルゴリズム筆記試験の4番目の質問です。

タイトル説明

会社にはn人の従業員がいて、n人の従業員の所属を分割する必要があります。分割要件は次のとおりです。

  1. 誰もが部下を持たないか、少なくとも2人の直属の部下がいます
  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] = 65(自身のカウント1)ノードを割り当てます。nodes[i] = 1代表的な再配布でそのノードの子が必要ない場合。

  • children アレイ

    各ノードの子の数を記録します。これは、各ノードの次数が1でないかどうかを最終的に判断するために使用されます。

  • unfinishedParentSet セットする

    このセットは、子ノードの親ノードに割り当てられたインデックス、つまりnodes[i] > 1すべてのノードを設定する必要があることを表しますこのセットが空の場合、割り当てる必要のある親ノードがないことを意味します。

次に、アルゴリズムを見てください。上記のように、各入力の最初の前処理は、ai2に等しい場合、代表はそのようなツリーを構成しません。入力が完了した後、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 中删去。 如果尝试分配失败就回溯,将 nodeschildrenunfinishedParentSet 还原。同时,由于题目说明节点度不能为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;
    }
}

参考

おすすめ

転載: blog.csdn.net/qq_41512783/article/details/109790001