インタビュー中に、プロセス間の通信方法について尋ねられましたが、
ジョセフリングの問題は古典的な質問です。誰もが聞いたことがあると思います。その後、筆記試験で遭遇しました。次に、3つの方法を使用して、この質問を詳細に説明します。最後の方法を学習した後、保証します。ふりをさせてください。
問題の説明:1-Nの番号が付けられたN人の兵士が一緒に座って、1番の兵士から始めて順番に報告し(1、2、3 ...など)、mまで数えた兵士が殺されます死後、兵士は1から数え始めます。最後の兵士が残るまで、兵士の番号を尋ねます。
1.方法1:アレイ
新入生の年に初めてこの問題に遭遇したとき、私はアレイを使ってそれをしました。ほとんどの人はそれを行う方法を知っていると思います。方法は次のとおりです。
図に示すように、配列を使用して1、2、3…n個のこれらのn個の数値を格納します(ここでは、n = 6、m = 3と想定しています)。
次に、配列をトラバースし続けます。選択した番号に対して、マークを付けます。たとえば、番号arr [2] = 3が選択されます。次に、マークを付けることができます。たとえば、arr [2] = -1とすると、 arr [2]に保存されている番号がなくなりました。
次に、この方法に従い、配列をトラバースし続け、配列内の1つの要素のみが1以外になるまでマークを付け続け、残りの要素が探している要素になるようにします。実演させてください:
この方法は簡単ですか?考え方は単純ですが、コーディングはそれほど単純ではありません。多くの重大な条件があります。配列の最後の要素にトラバースするたびに、添え字を0にリセットし、トラバース時に要素が-1であるかどうかを判断する必要があります。興味があれば、手作業でコードを書くことができます。この配列メソッドを使用してください。非常に単純だとは思わないでください。コーディングプロセスは依然として非常に困難です。
このアプローチの時間の複雑さはO(n * m)であり、空間の複雑さはO(n)です。
2.方法2:循環リンクリスト
リンクリストを研究した人は、ジョセフリングの問題に対処するためにリンクリストを使用すると推定されます。リンクリストを使用して対処することは、実際には上記のプロセスと同様ですが、リンクリストを処理する場合、選択した番号はマークされなくなりますが、リンクリストから要素を削除する時間の複雑さが非常に低いため、直接削除、O(1)。もちろん、上記の配列メソッドを削除することもできますが、配列削除の時間の複雑さはO(n)です。したがって、リンクリストを使用したソリューションは次のとおりです。
1.最初に、要素を格納するための循環リンクリストを作成します。
2.次に、リンクリストにノードが1つだけ残るまで、リンクリストをトラバースしながら削除します。ここでは、それらすべてを示すわけではありません。
コードは次のように表示されます。
// 定义链表节点
class Node{
int date;
Node next;
public Node(int date) {
this.date = date;
}
}
コアコード
public static int solve(int n, int m) {
if(m == 1 || n < 2)
return n;
// 创建环形链表
Node head = createLinkedList(n);
// 遍历删除
int count = 1;
Node cur = head;
Node pre = null;//前驱节点
while (head.next != head) {
// 删除节点
if (count == m) {
count = 1;
pre.next = cur.next;
cur = pre.next;
} else {
count++;
pre = cur;
cur = cur.next;
}
}
return head.date;
}
static Node createLinkedList(int n) {
Node head = new Node(1);
Node next = head;
for (int i = 2; i <= n; i++) {
Node tmp = new Node(i);
next.next = tmp;
next = next.next;
}
// 头尾串联
next.next = head;
return head;
}
この方法は、時間の複雑さはO(n * m)、空間の複雑さはO(n)であり、人々によって最も使用されていると推定されています。
より良い方法はありますか?はいと答えてください、見下ろしてください
方法3:再帰
実際、この問題は再帰によって解決できます。再帰の考え方は、特定の兵士を削除するたびに、これらの兵士の番号を付け直すことです。その後、削除前後の兵士番号間のマッピング関係を見つけるのが困難です。 。
再帰関数f(n、m)の戻り結果は、生き残った兵士の数であると定義します。明らかに、n = 1の場合、f(n、m)= 1です。f(n、m)とf(n-1、m)の関係がわかれば、再帰的に解くことができます。人数はn人、mに報告した人は自殺すると仮定します。その場合、最初の番号は
…
1
…
m-2
m-1
m
m + 1
m + 2
…
n
…
1回削除すると、mという番号のノードが削除されます。削除後、残っているノードはn-1個だけです。削除前後の数値変換関係は次のとおりです。
削除する前---削除した後
…---…
m-2 --- n-2
m-1 --- n-1
m ----なし(番号が削除されているため)
m + 1 --- 1(次回はここから数えるため)
m + 2 ---- 2
…----…
新しいリングにはn-1個のノードしかありません。また、削除前にm + 1、m + 2、m + 3の番号が付けられたノードは、削除後に1、2、3の番号が付けられたノードになります。
oldが削除前のノード番号、newがノード削除後の番号であるとすると、oldとnewの関係はold =(new + m-1)%n +1です。
このようにして、f(n、m)とf(n-1、m)の間の関係、およびf(1、m)= 1を取得します。したがって、再帰的な方法でそれを行うことができます。コードは次のように表示されます。
注:なぜ古いものではないのか疑問に思う人もいるかもしれません=(新しい+ m)%n?主な理由は、番号付けが0からではなく1から始まるためです。new + m == nの場合、最終的な計算結果はold = 0になります。とても古い=(新しい+ m-1)%n +1。
int f(int n, int m){
if(n == 1) return n;
return (f(n - 1, m) + m - 1) % n + 1;
}
手放すと、2行のコードが実行され、時間の複雑さはO(n)で、空間の複雑さはO(1)です。それで、他の人に伝えたいのなら、ジョセフの問題を解決するためのコード行が欲しいですか?次のように、答えは問題ありません。
int f(int n, int m){
return n == 1 ? n : (f(n - 1, m) + m - 1) % n + 1;
}
インタビュアーがジョセフの質問を書くように頼んだ後、あなたはただこのコード行をそれに投げます。
総括する
しかし、その筆記試験では、再帰的な方法ではなく、リンクされたリストの方法で行いました、、、、、当時、1行のコードで実行できるとは知りませんでした、、、、半分の行を提供することを歓迎します。コードを取得する方法!
あなたは
1を好むかもしれません。10セントのインタビューの質問:バイナリ検索ツリーで、なぜあなたは赤黒の木が必要ですか?
2.なぜあなたは再帰を学ぶことができないのですか?再帰に別れを告げて、私の経験のいくつかについて話して
ください
。3 。コンピューターが別のコンピューターにデータを送信する方法を理解するための記事。4 。わずか2GBのメモリで200/400億/ 800億の整数から最も頻繁に発生するものを見つける方法番号
5.文字列マッチングBoyer-Mooreアルゴリズム:テキストエディタの検索機能はどのように実装されていますか?