ตรวจสอบว่าสองรายการที่เชื่อมโยงรวมกันหรือไม่ ถ้ามีที่ไหน


103

คำถามนี้อาจจะเก่า แต่ฉันคิดคำตอบไม่ออก

บอกว่ามีสองรายการของความยาวที่แตกต่างกัน, การควบรวมที่จุด ; เราจะรู้ได้อย่างไรว่าจุดรวมอยู่ที่ไหน?

เงื่อนไข:

  1. เราไม่รู้ความยาว
  2. เราควรแยกวิเคราะห์แต่ละรายการเพียงครั้งเดียว

ตัวอย่างของรายการที่เชื่อมโยงกันสองรายการ


วิธีการผสานจากจุดนั้นจะมีเพียงรายการเดียว
rplusg

อนุญาตให้แก้ไขรายการหรือไม่
Artelius

1
ฉันค่อนข้างแน่ใจว่ามันใช้ไม่ได้หากไม่มีการแก้ไขรายการ (หรือแค่คัดลอกไปที่อื่นเพื่อหลีกเลี่ยงข้อ จำกัด ในการแยกวิเคราะห์เพียงครั้งเดียว)
Georg Schölly

2
อาจจะเป็นประเด็น คนสัมภาษณ์เหี้ย! Hehe
Kyle Rosendo

1
ฉันมีข้อเสนอที่น่าสนใจ ... สมมติว่าหางทั่วไปของรายการยาวไม่สิ้นสุด คุณจะหาจุดตัดโหนดโดยใช้หน่วยความจำคงที่ได้อย่างไร?
Akusete

คำตอบ:


36

ถ้า

  • โดย "ไม่อนุญาตให้แก้ไข" หมายความว่า "คุณสามารถเปลี่ยนแปลงได้ แต่ในที่สุดก็ควรได้รับการกู้คืน" และ
  • เราสามารถทำรายการซ้ำได้สองครั้ง

อัลกอริทึมต่อไปนี้จะเป็นทางออก

อันดับแรกคือตัวเลข สมมติว่ารายการแรกมีความยาวa+cและรายการที่สองมีความยาวb+cโดยที่cความยาวของ "หาง" ทั่วไปของพวกเขาอยู่ที่ไหน(หลังจุดผสาน) ขอแสดงความหมายดังนี้:

x = a+c
y = b+c

เนื่องจากเราไม่ทราบความยาวเราจึงคำนวณxและyไม่ต้องทำซ้ำเพิ่มเติม คุณจะเห็นว่า

จากนั้นเราจะวนซ้ำแต่ละรายการและย้อนกลับในขณะที่ทำซ้ำ! หากตัววนซ้ำทั้งสองไปถึงจุดผสานในเวลาเดียวกันเราจะพบว่ามันเป็นเพียงการเปรียบเทียบ มิฉะนั้นตัวชี้ตัวหนึ่งจะไปถึงจุดผสานก่อนอีกตัวหนึ่ง

หลังจากนั้นเมื่อตัววนซ้ำอีกตัวไปถึงจุดผสานมันจะไม่ไปที่หางทั่วไป แต่จะกลับไปที่จุดเริ่มต้นเดิมของรายการที่เคยถึงจุดรวมมาก่อน! ดังนั้นก่อนที่จะถึงจุดสิ้นสุดของรายการที่เปลี่ยนแปลง (เช่นจุดเริ่มต้นเดิมของรายการอื่น) เขาจะทำการa+b+1วนซ้ำทั้งหมด ขอเรียกว่าz+1.

ตัวชี้ที่มาถึงจุดผสานก่อนจะทำซ้ำไปเรื่อย ๆ จนกว่าจะถึงจุดสิ้นสุดของรายการ xจำนวนการทำซ้ำที่ทำควรจะคำนวณและจะมีค่าเท่ากับ

จากนั้นตัวชี้นี้จะวนกลับและย้อนกลับรายการอีกครั้ง แต่ตอนนี้มันจะไม่กลับไปที่จุดเริ่มต้นของรายการที่เริ่มต้นจากเดิม! แต่มันจะไปที่จุดเริ่มต้นของรายการอื่น ๆ แทน! จำนวนการทำซ้ำควรคำนวณและเท่ากับyจำนวนซ้ำมันทำให้ควรจะคำนวณและเท่ากับ

ดังนั้นเราจึงทราบตัวเลขต่อไปนี้:

x = a+c
y = b+c
z = a+b

จากที่เรากำหนดว่า

a = (+x-y+z)/2
b = (-x+y+z)/2
c = (+x+y-z)/2

ซึ่งช่วยแก้ปัญหาได้


2
ความคิดเห็นต่อสถานะคำถามไม่อนุญาตให้แก้ไขรายการ!
Skizz

1
ฉันชอบคำตอบนี้ (สร้างสรรค์มาก) ปัญหาเดียวที่ฉันมีคือสมมติว่าคุณรู้ความยาวของทั้งสองรายการ
tster

คุณไม่สามารถแก้ไขรายการและเราไม่ทราบความยาว - นี่คือข้อ จำกัด ... ยังไงก็ขอขอบคุณสำหรับคำตอบที่สร้างสรรค์
rplusg

2
@tster, @calvin คำตอบไม่ได้ถือว่าเราต้องการความยาว สามารถคำนวณแบบอินไลน์ได้ การเพิ่มคำอธิบายในคำตอบของฉัน
P Shved

2
@Forethinker แฮชโหนดที่เยี่ยมชมและ / หรือทำเครื่องหมายตามที่เห็นต้องใช้หน่วยความจำ O (ความยาวรายการ) ในขณะที่โซลูชันหลายอย่าง (รวมถึงของฉันไม่สมบูรณ์และซับซ้อน) ต้องใช้หน่วยความจำ O (1)
P Shved

163

ต่อไปนี้เป็นสิ่งที่ยิ่งใหญ่ที่สุดที่ฉันเคยเห็น - O (N) ไม่มีเคาน์เตอร์ ผมได้รับมันในระหว่างการสัมภาษณ์กับผู้สมัครที่ SN VisionMap

สร้างตัวชี้จำนวนเต็มเช่นนี้มันจะไปข้างหน้าทุกครั้งจนถึงจุดสิ้นสุดจากนั้นข้ามไปที่จุดเริ่มต้นของรายการตรงข้ามและอื่น ๆ สร้างสองสิ่งนี้โดยชี้ไปที่สองหัว เลื่อนพอยน์เตอร์ทีละ 1 ตัวทุกครั้งจนกว่าจะพบ สิ่งนี้จะเกิดขึ้นหลังจากผ่านไปหนึ่งหรือสองครั้ง

ฉันยังคงใช้คำถามนี้ในการสัมภาษณ์ แต่เพื่อดูว่าต้องใช้เวลานานแค่ไหนในการทำความเข้าใจว่าเหตุใดวิธีนี้จึงได้ผล


6
มันยอดเยี่ยมมาก!
Cong Hui

2
นี่เป็นคำตอบที่ดี แต่คุณต้องผ่านรายการสองครั้งซึ่งละเมิดเงื่อนไข # 2
tster

2
ฉันพบว่าโซลูชันนี้ค่อนข้างสง่างามหากมีการรับประกันจุดผสาน มันจะไม่ทำงานในการตรวจจับจุดผสานราวกับว่าไม่มีอยู่มันจะวนซ้ำไปเรื่อย ๆ
ทิศทางอื่น

4
สุดยอดมาก! คำอธิบาย: เรามี 2 รายการ: a-b-c-x-y-zและp-q-x-y-z. เส้นทางของตัวชี้แรกa,b,c,x,y,z,p,q,xเส้นทางของตัวชี้ที่สองp,q,x,y,z,a,b,c,x
Nikolai Golub

14
ยอดเยี่ยม. สำหรับผู้ที่ไม่เข้าใจให้นับจำนวนโหนดที่เดินทางจาก head1-> tail1 -> head2 -> จุดตัดและ head2 -> tail2-> head1 -> จุดตัด ทั้งสองจะเท่ากัน (วาดรายการที่เชื่อมโยงประเภทต่างกันเพื่อตรวจสอบสิ่งนี้) เหตุผลคือพอยน์เตอร์ทั้งสองต้องเดินทางเป็นระยะทางเดียวกัน head1-> IP + head2-> IP ก่อนถึง IP อีกครั้ง ดังนั้นเมื่อถึง IP พอยน์เตอร์ทั้งสองจะเท่ากันและเรามีจุดผสาน
adev

92

คำตอบของ Pavel ต้องการการแก้ไขรายการและการทำซ้ำแต่ละรายการสองครั้ง

นี่คือวิธีแก้ปัญหาที่ เพียง แต่ต้องมีการทำซ้ำแต่ละรายการสองครั้ง (ครั้งแรกในการคำนวณความยาวของพวกเขาถ้ายาวจะได้รับคุณต้องการเพียงเพื่อย้ำครั้งเดียว)

แนวคิดคือการละเว้นรายการเริ่มต้นของรายการที่ยาวขึ้น (จุดผสานไม่สามารถอยู่ที่นั่นได้) เพื่อให้พอยน์เตอร์ทั้งสองอยู่ห่างจากจุดสิ้นสุดของรายการเท่ากัน จากนั้นย้ายไปข้างหน้าจนกว่าจะรวมกัน

lenA = count(listA) //iterates list A
lenB = count(listB) //iterates list B

ptrA = listA
ptrB = listB

//now we adjust either ptrA or ptrB so that they are equally far from the end
while(lenA > lenB):
    ptrA = ptrA->next
    lenA--
while(lenB > lenA):
    prtB = ptrB->next
    lenB--

while(ptrA != NULL):
    if (ptrA == ptrB):
        return ptrA //found merge point
    ptrA = ptrA->next
    ptrB = ptrB->next

นี่เหมือนกับ (เวลาเชิงเส้น) แบบไม่มีอาการกับคำตอบอื่น ๆ ของฉัน แต่อาจมีค่าคงที่น้อยกว่าจึงน่าจะเร็วกว่า แต่ฉันคิดว่าคำตอบอื่นของฉันเจ๋งกว่า


4
วันนี้เมื่อเราดื่มวอดก้าฉันได้เสนอคำถามนี้ให้กับเพื่อนของฉันและเขาก็ให้คำตอบเดียวกับของคุณและขอให้โพสต์ไว้ใน SO แต่ดูเหมือนคุณจะเป็นคนแรก ดังนั้นฉันจะ +1 ให้คุณและฉันหวังว่าฉันจะ +1 อีกครั้ง
P Shved

2
+1 เช่นนี้และไม่จำเป็นต้องมีการแก้ไขใด ๆ ในรายการนอกจากนี้การใช้งานรายการที่เชื่อมโยงส่วนใหญ่มักจะให้ความยาว
keshav84

3
เรามีพาเวลมากเกินไป วิธีแก้ปัญหาของฉันไม่จำเป็นต้องแก้ไขรายการ
Pavel Radzivilovsky

คำตอบที่ดี. ความซับซ้อนของเวลาสำหรับสิ่งนี้จะเป็นอย่างไร 0 (n + ม.)? โดยที่ n = โหนดในรายการ 1, m = โหนดในรายการ 2?
Vihaan Verma

แทนที่จะย้ายพอยน์เตอร์ทั้งสองในทั้งสองรายการ: เราสามารถดูได้ว่า diff> = เล็กของสองพา ธ หรือไม่ถ้าใช่จากนั้นย้ายในรายการเล็ก ๆ ด้วยค่าขนาดเล็กอื่น ๆ จะย้ายในรายการเล็ก ๆ ด้วยค่า diff + 1 ถ้า diff คือ 0 ดังนั้นโหนดสุดท้ายคือคำตอบ
Vishal Anand

31

ถ้าคุณรู้ว่าพวกเขาจะรวมเข้าด้วยกัน:

สมมติว่าคุณเริ่มต้นด้วย:

A-->B-->C
        |
        V
1-->2-->3-->4-->5

1) ผ่านรายการแรกที่ตั้งค่าตัวชี้แต่ละตัวถัดไปเป็น NULL

ตอนนี้คุณมี:

A   B   C

1-->2-->3   4   5

2) ไปที่รายการที่สองและรอจนกว่าคุณจะเห็น NULL นั่นคือจุดผสานของคุณ

หากคุณไม่สามารถแน่ใจได้ว่าพวกมันผสานคุณสามารถใช้ค่า Sentinel สำหรับค่าตัวชี้ได้ แต่ก็ไม่ได้สวยหรู


4
อย่างไรก็ตามคุณทำลายรายชื่อในกระบวนการและจะไม่ถูกนำมาใช้อีก: P
Kyle Rosendo

@Kyle Rozendo วิธีแก้ปัญหาของฉันเปลี่ยนรายการตามวิธีที่สามารถเรียกคืนได้หลังจากการประมวลผล แต่นี่เป็นการสาธิตแนวคิดที่ชัดเจนมากขึ้น
P Shved

ฉันไม่เห็นว่าไม่อนุญาตให้แก้ไขรายการ ฉันจะลองคิดดู แต่ไม่มีอะไรอยู่ในใจโดยไม่เก็บทุกโหนดที่เห็น
tster

10
นั่นคือคำตอบที่ถูกต้อง! เราแค่ต้องปรับคำถาม :)
P Shved

24
อัลกอริทึมที่ยอดเยี่ยมในการสร้างการรั่วไหลของหน่วยความจำ
Karoly Horvath

14

หากเราสามารถทำซ้ำรายการได้สองครั้งมากกว่าที่ฉันสามารถระบุวิธีการกำหนดจุดผสาน:

  • ทำซ้ำทั้งสองรายการและคำนวณความยาว A และ B
  • คำนวณความแตกต่างของความยาว C = | AB |;
  • เริ่มทำซ้ำทั้งสองรายการพร้อมกัน แต่ทำขั้นตอน C เพิ่มเติมในรายการซึ่งมากกว่า
  • พอยน์เตอร์สองตัวนี้จะพบกันในจุดผสาน

8

นี่เป็นวิธีแก้ปัญหาอย่างรวดเร็วในการคำนวณ (วนซ้ำแต่ละรายการครั้งเดียว) แต่ใช้หน่วยความจำมาก:

for each item in list a
  push pointer to item onto stack_a

for each item in list b
  push pointer to item onto stack_b

while (stack_a top == stack_b top) // where top is the item to be popped next
  pop stack_a
  pop stack_b

// values at the top of each stack are the items prior to the merged item

2
นั่นเท่ากับการประมวลผลรายการสองครั้ง
Georg Schölly

ฉันคิดว่าในทางเทคนิคคุณกำลังทำสิ่งต่างๆกับรายการสองครั้ง แต่เป็นการปรับปรุงโซลูชันของ Kyle Rozendo อย่างมีนัยสำคัญ ตอนนี้ถ้า 'การประมวลผลรายการ' ถูกกำหนดให้เป็น 'การอ่านค่าลิงค์และตามตัวชี้' อาจเป็นที่ถกเถียงกันอยู่ว่ามันประมวลผลรายการเพียงครั้งเดียว - มันอ่านค่าลิงค์แต่ละครั้งเก็บไว้แล้วเปรียบเทียบ
Skizz

จะเร็วกว่าของฉันอย่างแน่นอนอย่างไม่ต้องสงสัย
Kyle Rosendo

8

คุณสามารถใช้ชุดของโหนด วนซ้ำในรายการเดียวและเพิ่มแต่ละโหนดในชุด จากนั้นวนซ้ำในรายการที่สองและสำหรับการวนซ้ำทุกครั้งให้ตรวจสอบว่ามีโหนดอยู่ในชุดหรือไม่ ถ้าเป็นเช่นนั้นคุณจะพบจุดผสานของคุณ :)


ฉันกลัว (เพราะΩ (n) ช่องว่างเพิ่มเติม) นี่เป็นวิธีเดียว (ไม่ใช่การสร้างรายการใหม่และ) ไม่แยกวิเคราะห์รายการมากกว่าหนึ่งครั้ง การตรวจจับลูปในรายการเป็นเรื่องเล็กน้อยสำหรับรายการแรก (ตรวจสอบว่าโหนดอยู่ในชุดหรือไม่) - ใช้วิธีการตรวจจับลูปใด ๆ ในรายการที่สองเพื่อให้แน่ใจว่าการสิ้นสุด (คำถามสัมภาษณ์อาจเกี่ยวกับการฟังคำชี้แจงปัญหาอย่างรอบคอบและไม่กระโดดเข้าไปใช้ค้อนที่คุณบังเอิญรู้ว่าโดนอะไรสักอย่างที่ไม่ใช่ตะปู)
greybeard

6

เนื้อหานี้ละเมิดเงื่อนไข "แยกวิเคราะห์แต่ละรายการเพียงครั้งเดียว" แต่ใช้อัลกอริทึมเต่าและกระต่าย (ใช้เพื่อค้นหาจุดผสานและความยาวรอบของรายการแบบวนรอบ) ดังนั้นคุณจึงเริ่มต้นที่รายการ A และเมื่อคุณไปถึง NULL ที่ สิ้นสุดที่คุณแสร้งทำเป็นตัวชี้ไปที่จุดเริ่มต้นของรายการ B ดังนั้นการสร้างลักษณะของรายการแบบวนรอบ จากนั้นอัลกอริทึมจะบอกให้คุณทราบว่า List A การผสานอยู่ไกลแค่ไหน (ตัวแปร 'mu' ตามคำอธิบายของ Wikipedia)

นอกจากนี้ค่า "lambda" จะบอกความยาวของรายการ B และหากคุณต้องการคุณสามารถคำนวณความยาวของรายการ A ระหว่างอัลกอริทึม (เมื่อคุณเปลี่ยนเส้นทางลิงก์ NULL)


สิ่งที่ฉันพูดค่อนข้างมากแค่มีชื่อที่น่าสนใจ : P
Kyle Rosendo

ไม่ใช่เลย. โซลูชันนี้คือ O (n) ในการดำเนินการและ O (1) ในการใช้หน่วยความจำ (ในความเป็นจริงต้องการตัวแปรตัวชี้สองตัวเท่านั้น)
Artelius

ใช่ควรลบความคิดเห็นก่อนหน้าของฉันเนื่องจากการแก้ปัญหาของฉันเปลี่ยนไปเล็กน้อย ฮิฮิ.
Kyle Rosendo

แต่ฉันไม่เห็นว่ามันมีผลอย่างไรในตอนแรก?
Artelius

คำอธิบายของคุณไม่ใช่อัลกอริทึมเอง บางทีฉันอาจจะมองว่ามันต่างออกไป แต่เดี๋ยวก่อน
Kyle Rosendo

3

บางทีฉันอาจจะง่ายกว่านี้ แต่เพียงแค่วนซ้ำรายการที่เล็กที่สุดและใช้โหนดสุดท้ายLinkเป็นจุดรวม?

ดังนั้นData->Link->Link == NULLจุดสิ้นสุดอยู่ที่ไหนโดยให้Data->Linkเป็นจุดรวม (ที่ส่วนท้ายของรายการ)

แก้ไข:

เอาล่ะจากภาพที่คุณโพสต์คุณจะแยกวิเคราะห์สองรายการที่เล็กที่สุดก่อน ด้วยรายการที่เล็กที่สุดคุณสามารถรักษาการอ้างอิงไปยังโหนดต่อไปนี้ ตอนนี้เมื่อคุณแยกวิเคราะห์รายการที่สองคุณจะทำการเปรียบเทียบกับข้อมูลอ้างอิงเพื่อค้นหาว่า Reference [i] คือการอ้างอิงที่ LinkedList [i] -> Link สิ่งนี้จะทำให้จุดผสาน เวลาอธิบายด้วยรูปภาพ (ซ้อนค่าบนภาพ OP)

คุณมีรายการที่เชื่อมโยง (ข้อมูลอ้างอิงที่แสดงด้านล่าง):

A->B->C->D->E

คุณมีรายการที่สองที่เชื่อมโยง:

1->2->

ด้วยรายการที่ผสานการอ้างอิงจะเป็นดังนี้:

1->2->D->E->

ดังนั้นคุณจึงจับคู่รายการที่ "เล็กกว่า" รายการแรก (เนื่องจากรายการที่ผสานซึ่งเป็นสิ่งที่เรากำลังนับมีความยาว 4 และรายการหลัก 5)

วนซ้ำรายการแรกรักษาข้อมูลอ้างอิงของการอ้างอิง

Pointers { 1, 2, D, E }รายการนี้จะมีการอ้างอิงต่อไป

ตอนนี้เราไปถึงรายการที่สอง:

-> A - Contains reference in Pointers? No, move on
-> B - Contains reference in Pointers? No, move on
-> C - Contains reference in Pointers? No, move on
-> D - Contains reference in Pointers? Yes, merge point found, break.

แน่นอนว่าคุณมีรายการตัวชี้ใหม่ แต่ไม่ได้อยู่นอกข้อกำหนด อย่างไรก็ตามรายการแรกจะถูกแยกวิเคราะห์เพียงครั้งเดียวและรายการที่สองจะถูกแยกวิเคราะห์อย่างสมบูรณ์ก็ต่อเมื่อไม่มีจุดผสาน มิฉะนั้นจะสิ้นสุดเร็วกว่า (ที่จุดผสาน)


การเปลี่ยนแปลงเล็กน้อยจากสิ่งที่ฉันอยากจะพูดในตอนแรก แต่จากสิ่งที่ OP ดูเหมือนจะต้องการสิ่งนี้จะเป็นเคล็ดลับ
Kyle Rosendo

ตอนนี้ชัดเจนขึ้นแล้ว แต่ใช้หน่วยความจำเชิงเส้น ฉันไม่ชอบแบบนั้น
Artelius

ไม่ได้ถามคำถามเพิ่มเติมมิฉะนั้นกระบวนการทั้งหมดอาจเป็นแบบมัลติเธรดได้ นี่ยังคงเป็นมุมมอง "ระดับบนสุด" ที่เรียบง่ายของโซลูชันโค้ดสามารถใช้งานได้หลายวิธี :)
Kyle Rosendo

1
เอ่ออะไรนะ? มัลติเธรดเป็นวิธีการใช้พลังประมวลผลที่ดีขึ้นโดยไม่ลดกำลังการประมวลผลทั้งหมดที่อัลกอริทึมต้องการ และการบอกว่าโค้ดสามารถใช้งานได้หลายวิธีเป็นเพียงข้ออ้าง
Artelius

1
นี่เป็นการโค้งงอ 'แยกวิเคราะห์แต่ละรายการเพียงครั้งเดียว' จนใกล้ถึงจุดแตกหัก สิ่งที่คุณทำคือคัดลอกรายการหนึ่งรายการจากนั้นตรวจสอบรายการอื่นเทียบกับสำเนา
Skizz

3

ฉันได้ทดสอบกรณีการผสานบน FC9 x86_64 ของฉันแล้วและพิมพ์ที่อยู่โหนดทั้งหมดตามที่แสดงด้านล่าง:

Head A 0x7fffb2f3c4b0
0x214f010
0x214f030
0x214f050
0x214f070
0x214f090
0x214f0f0
0x214f110
0x214f130
0x214f150
0x214f170


Head B 0x7fffb2f3c4a0
0x214f0b0
0x214f0d0
0x214f0f0
0x214f110
0x214f130
0x214f150
0x214f170

หมายเหตุเนื่องจากฉันได้จัดแนวโครงสร้างโหนดดังนั้นเมื่อ malloc () โหนดแอดเดรสจะถูกจัดแนวด้วย 16 ไบต์โปรดดูอย่างน้อย 4 บิต บิตน้อยที่สุดคือ 0s คือ 0x0 หรือ 000b ดังนั้นหากคุณอยู่ในกรณีพิเศษเดียวกัน (ที่อยู่โหนดที่จัดตำแหน่ง) ด้วยคุณสามารถใช้ 4 บิตเหล่านี้เป็นอย่างน้อย ตัวอย่างเช่นเมื่อเดินทางทั้งสองรายการจากหัวไปหางตั้งค่า 1 หรือ 2 จาก 4 บิตของแอดเดรสโหนดการเยี่ยมชมนั่นคือตั้งค่าสถานะ

next_node = node->next;
node = (struct node*)((unsigned long)node | 0x1UL);

หมายเหตุด้านบนแฟล็กจะไม่ส่งผลกระทบต่อที่อยู่โหนดจริง แต่เฉพาะค่าตัวชี้โหนดที่บันทึกไว้ของคุณ

เมื่อพบว่ามีคนตั้งค่าแฟล็กบิตแล้วโหนดแรกที่พบควรเป็นจุดผสาน หลังจากเสร็จสิ้นคุณจะกู้คืนที่อยู่โหนดโดยล้างแฟล็กบิตที่คุณตั้งไว้ ในขณะที่สิ่งสำคัญคือคุณควรระมัดระวังในการทำซ้ำ (เช่น node = node-> next) เพื่อทำความสะอาด จำไว้ว่าคุณตั้งค่าแฟล็กบิตแล้วให้ทำแบบนี้

real_node = (struct node*)((unsigned long)node) & ~0x1UL);
real_node = real_node->next;
node = real_node;

เนื่องจากข้อเสนอนี้จะคืนค่าที่อยู่โหนดที่แก้ไขจึงถือได้ว่า "ไม่มีการแก้ไข"


+1 นี่คือสิ่งที่อยู่ในใจโดยธรรมชาติเมื่อ "ย้ำเพียงครั้งเดียว" ไม่รู้ทำไมถึงไม่ได้รับการโหวต! ทางออกที่สวยงาม
jman

3

อาจมีวิธีง่ายๆ แต่จะต้องมีช่องว่างเสริม แนวคิดคือการสำรวจรายการและจัดเก็บที่อยู่แต่ละรายการในแผนที่แฮชตอนนี้สำรวจรายการอื่นและจับคู่ว่าที่อยู่นั้นอยู่ในแผนที่แฮชหรือไม่ แต่ละรายการจะถูกส่งผ่านเพียงครั้งเดียว ไม่มีการแก้ไขรายการใด ๆ ยังไม่ทราบความยาว ช่องว่างเสริมที่ใช้: O (n) โดยที่ 'n' คือความยาวของรายการแรกที่ข้ามผ่าน


2

โซลูชันนี้วนซ้ำแต่ละรายการเพียงครั้งเดียว ... ไม่จำเป็นต้องปรับเปลี่ยนรายการด้วย. แม้ว่าคุณอาจบ่นเกี่ยวกับช่องว่าง ..
1) โดยทั่วไปคุณทำซ้ำใน list1 และจัดเก็บที่อยู่ของแต่ละโหนดในอาร์เรย์ (ซึ่งเก็บค่า int ที่ไม่ได้ลงชื่อ)
2) จากนั้นคุณวนซ้ำ list2 และสำหรับที่อยู่ของแต่ละโหนด ---> คุณค้นหาอาร์เรย์ที่คุณพบว่าตรงกันหรือไม่ ... ถ้าคุณทำแล้วนี่คือโหนดที่ผสาน

//pseudocode
//for the first list
p1=list1;
unsigned int addr[];//to store addresses
i=0;
while(p1!=null){
  addr[i]=&p1;
  p1=p1->next;
}
int len=sizeof(addr)/sizeof(int);//calculates length of array addr
//for the second list
p2=list2;
while(p2!=null){
  if(search(addr[],len,&p2)==1)//match found
  {
    //this is the merging node
    return (p2);
  }
  p2=p2->next;
}

int search(addr,len,p2){
  i=0;  
  while(i<len){
    if(addr[i]==p2)
      return 1;
    i++;
  }
 return 0;
} 

หวังว่าคงเป็นทางออกที่ถูกต้อง ...


สิ่งนี้จะวนซ้ำรายการใดรายการหนึ่งมากกว่าหนึ่งครั้งแม้ว่าจะอยู่ในรูปแบบของอาร์เรย์แทนที่จะเป็นรายการเอง
syockit

1

ไม่จำเป็นต้องแก้ไขรายการใด ๆ มีวิธีแก้ปัญหาที่เราต้องสำรวจแต่ละรายการเพียงครั้งเดียว

  1. สร้างสองสแต็กสมมติว่า stck1 และ stck2
  2. สำรวจรายการที่ 1 และพุชสำเนาของแต่ละโหนดที่คุณสำรวจใน stck1
  3. เหมือนกับขั้นตอนที่สอง แต่คราวนี้ข้ามรายการที่ 2 และผลักสำเนาของโหนดใน stck2
  4. ตอนนี้ป๊อปจากทั้งสองสแต็กและตรวจสอบว่าทั้งสองโหนดเท่ากันหรือไม่ถ้าใช่ให้อ้างอิงถึงพวกเขา ถ้าไม่เช่นนั้นโหนดก่อนหน้าซึ่งเท่ากันจะเป็นจุดผสานที่เรากำลังมองหา

1
int FindMergeNode(Node headA, Node headB) {
  Node currentA = headA;
  Node currentB = headB;

  // Do till the two nodes are the same
  while (currentA != currentB) {
    // If you reached the end of one list start at the beginning of the other
    // one currentA
    if (currentA.next == null) {
      currentA = headA;
    } else {
      currentA = currentA.next;
    }
    // currentB
    if (currentB.next == null) {
      currentB = headB;
    } else {
      currentB = currentB.next;
    }
  }
  return currentB.data;
}

ในการแก้ไขเดิมเพียงแค่นี้สะกดออกมาเป็นคำตอบที่ได้รับการโหวตสูงสุด (พาเวล Radzivilovsky, 2013)
greybeard

0

นี่คือวิธีแก้ปัญหาที่ไร้เดียงสาไม่จำเป็นต้องสำรวจรายการทั้งหมด

หากโหนดที่มีโครงสร้างของคุณมีสามฟิลด์เช่น

struct node {
    int data;   
    int flag;  //initially set the flag to zero  for all nodes
    struct node *next;
};

สมมติว่าคุณมีสองหัว (head1 และ head2) ที่ชี้ไปที่หัวของสองรายการ

สำรวจทั้งสองรายการในจังหวะเดียวกันและใส่ค่าสถานะ = 1 (แฟล็กที่เยี่ยมชม) สำหรับโหนดนั้น

  if (node->next->field==1)//possibly longer list will have this opportunity
      //this will be your required node. 

0

แล้วสิ่งนี้ล่ะ:

  1. หากคุณได้รับอนุญาตให้สำรวจแต่ละรายการเพียงครั้งเดียวคุณสามารถสร้างโหนดใหม่สำรวจรายการแรกเพื่อให้ทุกโหนดชี้ไปยังโหนดใหม่นี้และสำรวจรายการที่สองเพื่อดูว่าโหนดใดชี้ไปยังโหนดใหม่ของคุณ ( นั่นคือจุดผสานของคุณ) หากการข้ามผ่านครั้งที่สองไม่นำไปสู่โหนดใหม่ของคุณรายการเดิมจะไม่มีจุดผสาน

  2. หากคุณได้รับอนุญาตให้สำรวจรายการมากกว่าหนึ่งครั้งคุณสามารถสำรวจแต่ละรายการเพื่อค้นหาความยาวของเราและหากแตกต่างกันให้ละเว้นโหนด "พิเศษ" ที่จุดเริ่มต้นของรายการที่ยาวขึ้น จากนั้นสำรวจทั้งสองรายการทีละขั้นตอนและค้นหาโหนดแรกที่รวมเข้าด้วยกัน


1. ไม่เพียง แต่แก้ไข แต่ทำลายรายการแรก 2. แนะนำครั้งแล้วครั้งเล่า
greybeard

0

ขั้นตอนใน Java:

  1. สร้างแผนที่
  2. เริ่มการข้ามผ่านในทั้งสองสาขาของรายการและใส่โหนดที่ข้ามผ่านรายการทั้งหมดลงในแผนที่โดยใช้สิ่งที่ไม่ซ้ำกันบางอย่างที่เกี่ยวข้องกับโหนด (พูดว่า node Id) เป็นคีย์และใส่ค่าเป็น 1 ในการเริ่มต้นสำหรับทั้งหมด
  3. เมื่อมีคีย์ที่ซ้ำกันครั้งแรกให้เพิ่มค่าสำหรับคีย์นั้น (สมมติว่าตอนนี้ค่าของมันกลายเป็น 2 ซึ่งเป็น> 1
  4. รับคีย์ที่มีค่ามากกว่า 1 และควรเป็นโหนดที่รวมสองรายการเข้าด้วยกัน

1
จะเกิดอะไรขึ้นถ้าเรามีวงจรในส่วนที่ผสาน?
Rohit

แต่สำหรับรอบการจัดการข้อผิดพลาดสิ่งนี้ดูเหมือนคำตอบของ isyiมาก
greybeard

0

เราสามารถแก้ไขได้อย่างมีประสิทธิภาพโดยการแนะนำช่อง "isVisited" สำรวจรายการแรกและตั้งค่า "isVisited" เป็น "จริง" สำหรับโหนดทั้งหมดจนจบ เริ่มจากวินาทีและค้นหาโหนดแรกที่แฟล็กเป็นจริงและบูมซึ่งเป็นจุดรวมของคุณ


0

ขั้นตอนที่ 1: ค้นหาความยาวของทั้งสองรายการขั้นตอนที่ 2: ค้นหาความแตกต่างและย้ายรายการที่ใหญ่ที่สุดด้วยความแตกต่างขั้นตอนที่ 3: ตอนนี้ทั้งสองรายการจะอยู่ในตำแหน่งที่ใกล้เคียงกัน ขั้นตอนที่ 4: วนซ้ำตามรายการเพื่อค้นหาจุดผสาน

//Psuedocode
def findmergepoint(list1, list2):
lendiff = list1.length() > list2.length() : list1.length() - list2.length() ? list2.lenght()-list1.lenght()
biggerlist = list1.length() > list2.length() : list1 ? list2  # list with biggest length
smallerlist = list1.length() < list2.length() : list2 ? list1 # list with smallest length


# move the biggest length to the diff position to level both the list at the same position
for i in range(0,lendiff-1):
    biggerlist = biggerlist.next
#Looped only once.  
while ( biggerlist is not None and smallerlist is not None ):
    if biggerlist == smallerlist :
        return biggerlist #point of intersection


return None // No intersection found

(ฉันชอบรายการที่แต่ละรายการขึ้นต้นบรรทัดดีกว่าลองใช้เครื่องตรวจการสะกดคำ)
greybeard

0
int FindMergeNode(Node *headA, Node *headB)
{
    Node *tempB=new Node;
    tempB=headB;
   while(headA->next!=NULL)
       {
       while(tempB->next!=NULL)
           {
           if(tempB==headA)
               return tempB->data;
           tempB=tempB->next;
       }
       headA=headA->next;
       tempB=headB;
   }
    return headA->data;
}

คุณต้องเพิ่มคำอธิบายในคำตอบของคุณ รหัสเฉพาะคำตอบอาจถูกลบ
rghome

0

ใช้แผนที่หรือพจนานุกรมเพื่อจัดเก็บ addressess เทียบกับค่าของโหนด หากที่อยู่มีอยู่ในแผนที่ / พจนานุกรมค่าของคีย์คือคำตอบ ฉันทำอย่างนี้:

int FindMergeNode(Node headA, Node headB) {

Map<Object, Integer> map = new HashMap<Object, Integer>();

while(headA != null || headB != null)
{
    if(headA != null && map.containsKey(headA.next))
    {
        return map.get(headA.next);
    }

    if(headA != null && headA.next != null)
    {
         map.put(headA.next, headA.next.data);
         headA = headA.next;
    }

    if(headB != null && map.containsKey(headB.next))
    {
        return map.get(headB.next);
    }

    if(headB != null && headB.next != null)
    {
        map.put(headB.next, headB.next.data);
        headB = headB.next;
    }
}

return 0;
}

0

โซลูชันความซับซ้อน AO (n) แต่อยู่บนสมมติฐาน.

สมมติฐานคือ: ทั้งสองโหนดมีจำนวนเต็มบวกเท่านั้น

ตรรกะ: ทำให้จำนวนเต็มทั้งหมดของ list1 เป็นลบ จากนั้นเดินไปตาม list2 จนได้จำนวนเต็มลบ เมื่อพบ => นำไปแล้วให้เปลี่ยนเครื่องหมายกลับเป็นบวกและส่งกลับ

static int findMergeNode(SinglyLinkedListNode head1, SinglyLinkedListNode head2) {

    SinglyLinkedListNode current = head1; //head1 is give to be not null.

    //mark all head1 nodes as negative
    while(true){
        current.data = -current.data;
        current = current.next;
        if(current==null) break;
    }

    current=head2; //given as not null
    while(true){
        if(current.data<0) return -current.data;
        current = current.next;
    }

}

0

เราสามารถใช้พอยน์เตอร์สองตัวและเลื่อนไปมาในลักษณะที่ว่าถ้าพอยน์เตอร์ตัวใดตัวหนึ่งเป็นโมฆะเราจะชี้ไปที่ส่วนหัวของรายการอื่นและเหมือนกันสำหรับอีกรายการด้วยวิธีนี้หากความยาวของรายการต่างกันพวกเขาจะได้พบกันในรอบที่สอง . ถ้าความยาวของ list1 คือ n และ list2 คือ m ความแตกต่างคือ d = abs (nm) พวกเขาจะครอบคลุมระยะทางนี้และพบกันที่จุดผสาน
รหัส:

int findMergeNode(SinglyLinkedListNode* head1, SinglyLinkedListNode* head2) {
    SinglyLinkedListNode* start1=head1;
    SinglyLinkedListNode* start2=head2;
    while (start1!=start2){
        start1=start1->next;
        start2=start2->next;
        if (!start1)
        start1=head2;
        if (!start2)
        start2=head1;
    }
    return start1->data;
}

0

คุณสามารถเพิ่มโหนดของlist1ไปยังแฮชเซ็ตและลูปผ่านวินาทีและหากมีโหนดใด ๆlist2อยู่ในชุดแล้วถ้าใช่แสดงว่าโหนดผสาน

static int findMergeNode(SinglyLinkedListNode head1, SinglyLinkedListNode head2) {
    HashSet<SinglyLinkedListNode> set=new HashSet<SinglyLinkedListNode>();
    while(head1!=null)
    {
        set.add(head1);
        head1=head1.next;
    }
    while(head2!=null){
        if(set.contains(head2){
            return head2.data;
        }
    }
    return -1;
}

0

วิธีแก้ไขโดยใช้ javascript

var getIntersectionNode = function(headA, headB) {
    
    if(headA == null || headB == null) return null;
    
    let countA = listCount(headA);
    let countB = listCount(headB);
    
    let diff = 0;
    if(countA > countB) {

        diff = countA - countB;
        for(let i = 0; i < diff; i++) {
            headA = headA.next;
        }
    } else if(countA < countB) {
        diff = countB - countA;
        for(let i = 0; i < diff; i++) {
            headB = headB.next;
        }
    }

    return getIntersectValue(headA, headB);
};

function listCount(head) {
    let count = 0;
    while(head) {
        count++;
        head = head.next;
    }
    return count;
}

function getIntersectValue(headA, headB) {
    while(headA && headB) {
        if(headA === headB) {
            return headA;
        }
        headA = headA.next;
        headB = headB.next;
    }
    return null;
}

0

หากอนุญาตให้แก้ไขรายการที่เชื่อมโยงได้

  1. จากนั้นทำให้ตัวชี้โหนดถัดไปของโหนดทั้งหมดของรายการ 2 เป็นโมฆะ
  2. ค้นหาค่าข้อมูลของโหนดสุดท้ายของรายการ 1 สิ่งนี้จะทำให้คุณได้โหนดที่ตัดกันในการข้ามผ่านรายการเดียวของทั้งสองรายการโดย "no hi fi logic"
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.