これは、マイクロソフトからの筆記テスト中に尋ねられたプログラミングの質問の1つです。私が思いついた質問と答えを出しています。 (少なくとも私にとっては)包括的に見えますが、それが私の答えです。行数を減らすことができると感じています。それはCで尋ねられ、私はJavaの人ですが、コードを作成することができました(私の回答に含まれているJava構文のように)
では、質問です。
すでにソートされている2つのリストがある場合、それらをマージして、新しいノードを追加せずに新しいリストを返す必要があります。返されたリストもソートする必要があります。
メソッドのシグネチャは、
Node* MergeLists(Node* list1, Node* list2);
struct Node{
int data;
Node *next;
}
以下は私が思いついた解決策です、
Node* MergeLists(Node* list1, Node* list2){
Node* mergedList;
if(list1 == null && list2 ==null){//if both are null, return null
return null;
}
if(list1 == null){//if list1 is null, simply return list2
return list2;
}
if(list2 == null){//if list2 is null, simply return list1
return list1;
}
if(list1.data < list2.data){//initialize mergedList pointer to list1 if list1's data is lesser
mergedList = list1;
}else{//initialize mergedList pointer to list2 if list2's data is lesser or equal
mergedList = list2;
}
while(list1!=null && list2!=null){
if(list1.data < list2.data){
mergedList->next = list1;
list1 = list1->next;
}else{
mergedList->next = list2;
list2 = list2->next;
}
}
if(list1 == null){//remaining nodes of list2 appended to mergedList when list1 has reached its end.
mergedList->next = list2;
}else{//remaining nodes of list1 appended to mergedList when list2 has reached its end
mergedList->next = list1;
}
return mergedList;
}
これは改善できると確信しています。追加した行が重複しているかどうかを確認してください。私の構文エラーとロジックを自由に批判してください。
ありがとう!
コードは、「特別な」ケースを処理するために挿入されたif
- sでオーバーロードされます。これにより、コードが大きく膨らみ、読みにくくなります。これは通常、「データによって」それらを処理する方法を見つけるのではなく、「コードによって」特殊なケースを処理することにしたときに発生します。デビッドウィーラーに起因する声明は、「コンピューターサイエンスにおけるすべての問題は、別のレベルの間接化によって解決できる」と述べています。その「特別なレベルの間接参照」は通常、リストで非常にうまく機能し、それらのif
sによって作成される混乱を大幅に減らすのに役立ちます。
上記を説明するために、私のコードは次のようになります
#define SWAP_PTRS(a, b) do { void *t = (a); (a) = (b); (b) = t; } while (0)
Node* MergeLists(Node* list1, Node* list2)
{
Node *list = NULL, **pnext = &list;
if (list2 == NULL)
return list1;
while (list1 != NULL)
{
if (list1->data > list2->data)
SWAP_PTRS(list1, list2);
*pnext = list1;
pnext = &list1->next;
list1 = *pnext;
}
*pnext = list2;
return list;
}
一部の人は、pnext
ポインターで追加レベルの間接参照を使用すると、実際にコードが読みにくくなると主張するかもしれません。初心者の場合、このアプローチはいくつかの困難をもたらす可能性がありますが、経験豊富なプログラマーにとっては、これはイディオムとして簡単に消化できるはずです。
最も目立つバグはループにあり、mergedList-> nextを上書きし続けると、以前に追加したノードが失われます。つまり、入力に関係なく、返されるリストには3つ以上のノードが含まれることはありません...
Cをやったのは久しぶりですが、次のようにします。
Node* merge(Node* list1, Node* list2) {
Node* merged = null;
Node** tail = &merged;
while (list1 && list2) {
if (list1->data < list2->data) {
*tail = list1;
list1 = list1->next;
} else {
*tail = list2;
list2 = list2->next;
}
tail = &((*tail)->next);
}
*tail = list1 ? list1 : list2;
return merged;
}
これまでのところ、すべての回答は面白く、よくできています。これは、DRY/DIEとTDDをフィーチャーした、インタビュアーが見たいものに似ている可能性があります。 :-)
#include <stdio.h>
typedef struct ns {
int data;
struct ns *next;
} Node;
Node l1[] = {
{ 1, &l1[1] },
{ 3, &l1[2] },
{ 5, &l1[3] },
{ 7, &l1[4] },
{ 9, &l1[5] },
{11, 0 },
};
Node l2[] = {
{ 2, &l2[1] },
{ 4, &l2[2] },
{ 6, 0 },
};
Node* MergeLists(Node* list1, Node* list2) {
Node *t, **xt;
for(xt = &t; list1 || list2;) {
Node **z = list1 == NULL ? &list2 :
list2 == NULL ? &list1 :
list1->data < list2->data ? &list1 : &list2;
*xt = *z;
xt = &(*z)->next;
*z = *xt;
}
*xt = NULL;
return t;
}
int main(void) {
for(Node *t = MergeLists(l1, l2); t; t = t->next)
printf("%d\n", t->data);
return 0;
}
これほどエレガントなものはありません。
_Node* merge2(Node* n1, Node* n2) {
n1->next = merge(n1->next, n2);
return n1;
}
Node* merge(Node* n1, Node* n2) {
return (n1 == null) ? n2 :
(n2 == null) ? n1 :
(n1->data < n2->data) ?
merge2(n1, n2) :
merge2(n2, n1);
}
_
再帰を理解しているとすれば、これは当然のことながら明らかです。
これはインタビューの回答のみに適していることを指摘しておきます(おそらく、プログラムの書き方を知っていることを単に示すよりも、思考の明確さを示すことがより大きな影響を与えます)。実際には、スタックオーバーフローを引き起こす可能性のあるO(n)
スタック深度を使用するため、この方法でマージすることは望ましくありません。また、これは末尾再帰ではないため、コンパイラーに最適化できません。
Divide et Impera
(つまり MergeSort )
AndreyTを使用してpolygenをマージすると、次のようになります。
Node* merge(Node* n1, Node* n2) {
return (n1 == null) ? n2 :
(n2 == null) ? n1 :
(n1->data < n2->data) ?
(n1->next = merge(n1->next, n2), n1) :
(n2->next = merge(n2->next, n1), n2)}
私はこれについて信用を主張することはできませんが、これは最も簡潔であり、2つの引数の対称性を示し、あいまいなヘルパー関数を導入していません。最適化コンパイラーがここで末尾再帰を参照するかどうかはわかりませんが、そうです。インデントは最後のタッチです。
再帰を使用します。コードは次のとおりです。
ListNode* mergeTwoSortedLists(ListNode* pHead1, ListNode* pHead2)
{
if(pHead1 == NULL)
return pHead2;
else if(pHead2 == NULL)
return pHead1;
ListNode* pMergedHead = NULL;
if(pHead1->m_nValue < pHead2->m_nValue)
{
pMergedHead = pHead1;
pMergedHead->m_pNext = mergeTwoSortedLists(pHead1->m_pNext, pHead2);
}
else
{
pMergedHead = pHead2;
pMergedHead->m_pNext = mergeTwoSortedLists(pHead1, pHead2->m_pNext);
}
return pMergedHead;
}
これは私の見解です。他のソリューションとは異なり、他のリストのヘッドノードより小さいか等しい、1つのリスト上の連続するノードを識別してスキップします。他のリストの先頭はそのようなシーケンスの最後に添付され、プロセスは役割を切り替えた後に繰り返されます。このアプローチは、Node.nextへの割り当ての数を最小限に抑え、NULLテストを各反復での単一チェックに制限します。
Node * merge(Node *list1, Node *list2)
{
if (!list1) return list2;
if (!list2) return list1;
Node *tmp;
// compare head nodes and swap lists to guarantee list1 has the smallest node
if (list1->val > list2->val) {
tmp = list2;
list2 = list1;
list1 = tmp;
}
Node *tail = list1;
do {
// Advance the tail pointer skipping over all the elements in the result
// which have smaller or equal value than the first node on list2
while (tail->next && (tail->next->val <= list2->val)) {
tail = tail->next;
}
// concat list2 at tail of result and make the rest after tail list2
tmp = tail->next;
tail->next = list2;
tail = list2;
list2 = tmp;
} while (list2);
return list1;
}
再帰を使用できます:
Node* MergeLists(Node *headA, Node* headB)
{
if(headA==NULL){
return headB;
}else if(headB ==NULL){
return headA;
}
Node* head = NULL;
if(headA->data <= headB->data){
head= headA;
head->next = MergeLists(headA->next,headB);
}else{
head= headB;
head->next = MergeLists(headA,headB->next);
}
return head;
}
#include<stdio.h>
typedef struct NODE
{
int data;
struct NODE * next;
}NODE;
NODE * func(NODE*,NODE*);
int main()
{
int i;
int size;
int value;
NODE * head1,*head2,*newNode,*ptr,*final;
printf("\nPlease enter the number of elements\n");
scanf("%d",&size);
for(i=0;i<size;i++)
{
printf("List 1\n");
printf("Please enter the value number %d \n",i+1);
scanf("%d",&value);
newNode=(NODE*)malloc(sizeof(NODE));
newNode->data=value;
newNode->next=NULL;
if(i!=0)
{
ptr->next=newNode;
ptr=ptr->next;
}
if(i==0)
{
head1=newNode;
ptr=newNode;
}
}
for(i=0;i<size;i++)
{
printf("\n\nList 2\n");
printf("Please enter the value number %d \n",i+1);
scanf("%d",&value);
newNode=(NODE*)malloc(sizeof(NODE));
newNode->data=value;
newNode->next=NULL;
if(i!=0)
{
ptr->next=newNode;
ptr=ptr->next;
}
if(i==0)
{
head2=newNode;
ptr=newNode;
}
}
final=func(head1,head2);
printf("\n\n");
while (final!=NULL)
{
printf("%d -->",final->data);
final=final->next;
}
printf("NULL
");
return 0;
}
NODE* func(NODE* list1, NODE* list2)
{
NODE* mergedList,*mergeHead=NULL;
if(list1 == NULL && list2 ==NULL){//if both are NULL, return NULL
return NULL;
}
if(list1 == NULL){//if list1 is NULL, simply return list2
return list2;
}
if(list2 == NULL){//if list2 is NULL, simply return list1
return list1;
}
mergedList = (NODE*)malloc(sizeof(NODE));
if(list1->data < list2->data){//initialize mergedList pointer to list1 if list1's data is lesser
mergedList->data=list1->data;
mergedList->next=NULL;
list1 = list1->next;
}else{//initialize mergedList pointer to list2 if list2's data is lesser or equal
mergedList->data=list2->data;
mergedList->next=NULL;
list2 = list2->next;
}
mergeHead=mergedList;
while(list1!=NULL && list2!=NULL){
if(list1->data < list2->data){
mergedList->next = (NODE*)malloc(sizeof(NODE));
mergedList=mergedList->next;
mergedList->data=list1->data;
mergedList->next=NULL;
list1 = list1->next;
}else{
mergedList->next = (NODE*)malloc(sizeof(NODE));
mergedList=mergedList->next;
mergedList->data=list2->data;
mergedList->next=NULL;
list2 = list2->next;
}
}
if(list1 == NULL){//remaining nodes of list2 appended to mergedList when list1 has reached its end.
while(list2!=NULL)
{
mergedList->next = (NODE*)malloc(sizeof(NODE));
mergedList=mergedList->next;
mergedList->data=list2->data;
mergedList->next=NULL;
list2 = list2->next;
}
}else{//remaining nodes of list1 appended to mergedList when list2 has reached its end
mergedList->next = (NODE*)malloc(sizeof(NODE));
mergedList=mergedList->next;
mergedList->data=list1->data;
mergedList->next=NULL;
list1 = list1->next;
}
return mergeHead;
}
public void Merge(LinkList list1, LinkList list2) {
if (list1.head == null && list2.head == null) {
System.out.println("Empty list"); //Check if lists are empty
}
if (list1.head == null) { //If list1 is empty print list2
list2.printList();
}
if (list2.head == null) { //If list2 is empty print list1
list1.printList();
}
LinkList list3 = new LinkList(); //create a new LinkList object for merging
Node a = list1.head; //Beginning of list1
Node b = list2.head; //Beginning of list2
while (a != null && b != null) { //Until list ends
if (a.value <= b.value) { //Compare values of list1 against list2
list3.insert(a.value); //Insert values to new list
a = a.next;
} else if (a.value >= b.value) {
list3.insert(b.value);
b = b.next;
} else if (a.value == b.value){ //Insert only unique values and discard repeated values
list3.insert(a.value);
a = a.next;
b = b.next;
}
}
if (a == null) {
while (b != null) {
list3.insert(b.value); //If list1 becomes empty, attach rest of the list2 to list3
b = b.next;
}
}
if (b == null) {
while (a != null) {
list3.insert(a.value);
a = a.next;
}
}
list3.printList(); //print merged list
}
}
こんにちは、みんな !今月は面接の準備をしていたところ、この問題に取り組んでいたときに、これが私が思いついた解決策です。私の解決策を、あなたがここに投稿した多くの解決策と比較したところ、私のプログラムは非常に長いことがわかりました。これを理解して実装する方が簡単だと思いますが、既存のコードに対してJavaでより良い解決策があります。より良い時間複雑性の解決策を探しています。ヘルプ/指示/ヒントはありがたいです。
PS:ポインタを使用するCで上記にリストされたプログラムのJavaソリューションを思い付くことができませんでした。
Java 8、Stream APIを使用して、マージ、識別、ソートを行うことができます。以下は、2つのリストを整数要素でソートおよびマージするためのサンプルコードです。
List<Integer> list1= Arrays.asList(2,3,5,8);
List<Integer> list2 = Arrays.asList(3,6,8);
List<Integer> finalList = new ArrayList<>();
finalList.addAll(list1);
finalList.addAll(list2);
List<Integer> mergeSortedList =
finalList.stream()
.distinct()
.sorted()
.collect(Collectors.toList());
System.out.println(mergeSortedList);
そのための再帰関数を作成しました。これが私の解決策です:
Node* merge_recursion(Node* l1, Node* l2)
{
if (!l1)
return l2;
if (!l2)
return l1;
if (l1->data < l2->data) {
l1->next = merge_recursion(l1->next, l2);
return l1;
} else {
l2->next = merge_recursion(l1, l2->next);
return l2;
}
}
(main()/呼び出し関数で)新しいポインター変数に戻りポインターを格納し、リンクされたリストを新しいポインターでトラバースしてデータを印刷します。これにより、ソートされたマージされたリンクされたリストが生成されます。