这个问题可能有些老,但我想不出答案。
假设有两个不同长度的列表,在某一点 合并;我们如何知道它们的合并点在哪里?
条件:
- 我们不知道长度
- 我们只能遍历每个列表一次。
这个问题可能有些老,但我想不出答案。
假设有两个不同长度的列表,在某一点 合并;我们如何知道它们的合并点在哪里?
条件:
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 GolubPavel的答案需要修改列表,并且需要对每个列表进行两次迭代。
这里有一个解决方案,只需要对每个列表进行两次迭代(第一次是计算它们的长度;如果已知长度,则只需要迭代一次)。
其思想是忽略较长列表的起始条目(合并点不能在那里),以使两个指针与列表末尾的距离相等。然后将它们向前移动,直到它们合并。
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
这个答案的时间复杂度与我的另一个答案是渐进相同的(线性时间),但可能有更小的常数,因此可能更快。但我认为我的另一个答案更酷。
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
这解决了问题。
如果你知道它们将要合并:
假设你从以下内容开始:
A-->B-->C
|
V
1-->2-->3-->4-->5
1) 遍历第一个列表,将每个下一个指针设置为NULL。
现在你有:
A B C
1-->2-->3 4 5
2) 现在浏览第二个列表,等待直到您看到NULL,那就是合并点。
如果您不能确定它们是否合并,则可以为指针值使用哨兵值,但这不够优雅。
如果我们能够精确地迭代列表两次,那么我可以提供一种确定合并点的方法:
这里有一个解决方案,计算速度很快(每个列表只迭代一次),但使用了大量内存:
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
您可以使用一组Node。遍历一个列表并将每个Node添加到集合中。然后遍历第二个列表,对于每次迭代,请检查Node是否存在于集合中。如果存在,则找到了合并点 :)
Data->Link->Link == NULL
时是终点,将 Data->Link
作为合并点(在列表末尾)。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.
当然,你可以维护一个新的指针列表,但这并不超出规范。然而,第一个列表只被解析一次,如果存在合并点,第二个列表将只被完全解析一次。否则,它会更早地结束(在合并点处)。
我在我的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
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;