英文:
My method for eliminating the largest sequence of repeated elements on singly linked list does not work
问题
这段代码应该在一个节点列表中搜索最长的重复元素序列,然后消除它(每个节点只连接到下一个元素,是一个单向链表)。执行过程卡在列表的最后一个元素上(没有被打印,我不明白为什么),代码永远不会完成(永远不会打印出最后的“print”语句)。我的代码确实正确地计算了每个元素的位置,但它卡在了最后一个元素上。在之前调试代码时,要么没有消除最长序列,要么就是删除了整个列表。
```python
from slistH import SList
from slistH import SNode
import sys
class SList2(SList):
def delLargestSeq(self):
# 在这里实现你的解决方案
previous_to_seq = self._head # 我们从所有指针的第一个位置开始
first_after_sequence = self._head
pointer2 = self._head
pointer = self._head
new_pointer = self._head # 这个指针将存储重复次数最多的节点
a = 0 # 这个变量控制条件是否第一次被满足
count = 0
first = 0 # 这个变量控制序列的第一个元素的位置
last = 0
prev_first = 0 # 存储最长序列的第一个元素的位置(直到检测到更长的序列)
prev_last = 0
while pointer is not None:
if a == 0:
if pointer.elem == pointer.next.elem:
last = count + 1
else:
if (last - first) >= (prev_last - prev_first):
prev_first = first
prev_last = last
new_pointer = pointer
first = last + 1
last = first
print(f"{pointer.elem} || {count}") # 这些只是为了观察一切是否正常工作
pointer = pointer.next
count += 1
if pointer is None:
a = 1
for i in range(prev_last):
if pointer2.next.elem == new_pointer.elem:
if a == 1:
previous_to_seq = pointer2
a = 2
elif a == 2 and pointer2.next.elem != new_pointer.elem:
first_after_sequence = pointer2.next
a = 3
pointer2 = pointer2.next
if previous_to_seq.elem == new_pointer.elem:
previous_to_seq = previous_to_seq.next
previous_to_seq.next = first_after_sequence
print(f"序列后的第一个元素:{first_after_sequence.elem}")
print(f"序列前的最后一个元素:{previous_to_seq.elem}")
nodes_list = SList2()
for i in (1, 3, 3, 3, 3, 4, 4, 5, 5, 5, 6, 6):
nodes_list.addLast(i)
print(nodes_list)
nodes_list.delLargestSeq()
print(f"{nodes_list} || 最终列表")
英文:
This code is supposed to search through a Node list for the largest sequence of repeated elements and then eliminate it (each Node has connection only to the next element, it is a singly linked list). The execution gets stuck in the last element of the list ( which isnt printed, I dont understand why) and the code never finishes ( it never gets to print the last "print").My code does count correctly the position of each element but it gets stuck on the last one. In previous tinkering with the code it either not eliminate the largest sequence or it deleted the whole list.
from slistH import SList
from slistH import SNode
import sys
class SList2(SList):
def delLargestSeq(self):
# implement here your solution
previous_to_seq = self._head # we start at the first position for all pointers
first_after_sequence = self._head
pointer2 = self._head
pointer = self._head
new_pointer = self._head # this pointer will store the node that is the most repeated
a = 0 # this variable controls that it has been the first time that a condition has been fulfilled
count = 0
first = 0 # this variable controls the position of the first element o a sequence
last = 0
prev_first = 0 # stores the biggest sequence's first element position (until a bigger one is detected)
prev_last = 0
while pointer is not None:
if a == 0 :
if pointer.elem == pointer.next.elem:
last = count + 1
else:
if (last - first) >= (prev_last - prev_first): # if a bigger sequence is detected...
prev_first = first
prev_last = last
new_pointer = pointer # this will now be the most repeated element
first = last + 1 # the next element will be the first of the next sequence that we will compare
last = first
print(f"{pointer.elem} || {count} ") # these are just to observe that everything is working
pointer = pointer.next
count += 1
if pointer is None:
a = 1
for i in range(prev_last): # goes through the list until the biggest sequence's last element's position
if pointer2.next.elem == new_pointer.elem:
if a == 1: # if it is the first element of the sequence...
previous_to_seq = pointer2 # it is the first element before the biggest sequence
a = 2 # this ensures that it is the first time that this has been executed
elif a == 2 and pointer2.next.elem != new_pointer.elem:
first_after_sequence = pointer2.next # first element different from the ones in the sequence after it
a = 3
pointer2 = pointer2.next
if previous_to_seq.elem == new_pointer.elem: # in case there is no element before the sequence
previous_to_seq = previous_to_seq.next
previous_to_seq.next = first_after_sequence
print(f"first after sequence: {first_after_sequence.elem}")
print(f"last before sequence {previous_to_seq.elem}")
nodes_list = SList2()
for i in (1, 3, 3, 3, 3, 4, 4, 5, 5, 5, 6, 6):
nodes_list.addLast(i)
print(nodes_list)
nodes_list.delLargestSeq()
print(f"{nodes_list} || final list")
The execution gets stuck in the last element of the list ( which isnt printed, I dont understand why) and the code never finishes ( it never gets to print the last "print").My code does count correctly the position of each element but it gets stuck on the last one. In previous tinkering with the code it either not eliminate the largest sequence or it deleted the whole list. I also had a problem where the loop reached "None" and None has no ".next".
答案1
得分: 0
这个语句最终会在 pointer
是列表中的最后一个节点时执行:
if pointer.elem == pointer.next.elem:
在那一刻,pointer.next
是 None
,所以 pointer.next.elem
是一个无效的引用,会产生错误。
其他一些备注:
-
你有太多只是陈述显而易见事实的注释。注释不应该将语句翻译成英文,而应该提供更高层次的含义。如果没有这样的含义可提供,就不要添加注释。
-
命名很重要,可以使代码更易读(而不需要在每一行都加注释)。例如,
a
是一个不好的名称,它应该表示“...这是第一次条件被满足的时候”。 -
在第一个循环中,变量
a
仅在循环即将退出时更新,这意味着第一个循环永远不会执行else
块,并且永远不会更新first
的值。 -
该算法过于依赖位置。在链表中,你不应该需要追踪位置(索引),而是应该追踪节点。索引是你通常在使用原生列表时会使用的东西,但在链表中并不需要。追踪节点引用的优势在于你不需要第二个循环来执行实际的移除。在链表中,移除只需一个赋值操作。
-
在你的代码中,没有考虑到移除必须发生在列表的开始的情况,因为在这种情况下,
_head
引用应该被更新,但在你的实现中没有这样的语句。
还有更多问题,但我认为这已经足够解释为什么该算法不起作用了。
假设
我在下面添加了一个实现,但我不得不做一些在你的问题中没有明确说明的假设:
-
如果一个(非空)列表没有两个连续的节点具有相等的值,那么最长的重复值序列将为1。由于这实际上不能被称为“重复”,我假设在这种情况下,列表应该保持原样,不进行任何移除操作。
-
如果列表有两个或更多的具有相同长度的重复段,而且这些段碰巧是最长的,那么只有第一个节点段将被移除。
-
可以创建一个辅助节点,可以用
SNode(None)
创建。由于你没有提供SNode
的实现,我只能猜测构造函数可以这样调用。
算法
为了从链表中删除一个段,最好跟踪该段之前的节点,因为那个节点将需要更新其 next
属性。第一组重复值可能出现在列表的开头,那么就没有这样的前置节点。在这种情况下,我们有一个不同的情况,那就是列表的 _head
属性应该被更新(如果它是最长的重复系列的话)。
为了允许以相同的方式处理这些不同的情况,通常的做法是创建一个在列表的头节点之前的虚拟节点。然后第一个系列的前置节点是那个虚拟节点,如果需要移除这个系列,我们可以在不采取任何特殊预防措施的情况下更新前置节点的 next
属性。最后,当相关时,我们可以将 dummy.next
赋值回 _head
属性,这样在相关情况下会正确更新头。
实现
def delLargestSeq(self):
# 在列表前创建一个虚拟节点,这样可以简化代码,方便在处理结束时更新头节点
dummy = SNode(None)
dummy.next = self._head
# 为了有效地移除,我们应该跟踪*第一个*要移除的节点之前的节点
beforeDupes = dummy
beforeFirstToRemove = lastToRemove = None
numNodesToRemove = 1
while beforeDupes.next:
endDupes = beforeDupes.next
numNodes = 1
# 尽可能扩展节点的选择,只要它们具有相同的值
while endDupes.next and endDupes.next.elem == endDupes.elem:
numNodes += 1
endDupes = endDupes.next
# 跟踪最长的序列
if numNodes > numNodesToRemove:
numNodesToRemove = numNodes
beforeFirstToRemove = beforeDupes
lastToRemove = endDupes
beforeDupes = endDupes
# 假设移除必须涉及至少2个节点,因为单个节点不能被称为“重复”
if numNodesToRemove > 1:
# 移除最长的重复序列
beforeFirstToRemove.next = lastToRemove.next
# 如果序列从第一个节点开始,头引用必须被更新
self._head = dummy.next
在repl.it上运行。
可视化
让我们以一个例子来说明:
1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6
当算法识别到第一个重复值系列,并且这是迄今为止最长的系列时,状态如下:
dummy
↓
None → 1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6
↑ ↑
beforeDupes end
英文:
This statement will eventually run when pointer
is the last node in the list:
if pointer.elem == pointer.next.elem:
At that moment pointer.next
is None
, and so pointer.next.elem
is an invalid reference, and will produce an error.
Some other remarks:
-
You have too many comments that just state the obvious. A comment should not translate the statement into English, but should give a higher level of meaning. If there is no such meaning to give, then don't add a comment.
-
Naming is important and could make the code more readable (without the need to comment every line). For instance,
a
is a bad name for something that is supposed to indicate "...that it has been the first time that a condition has been fulfilled". -
In the first loop, the variable
a
is only updated when the loop is about to exit, so that means that the first loop will never execute theelse
block and will never update the value offirst
-
The algorithm depends too much on positions. In a linked list you should not need to keep track of positions (indices), but of nodes. Indices are what you would typically use when working with a native list, not so with linked lists. The advantage of keeping track of node references is that you will not need a second loop to perform the actual removal. In a linked list, a removal can be done with one assignment only.
-
There is no provision in your code for the case where the removal has to happen at the very start of the list, because in that case the
_head
reference should be updated, but there is no such statement in your implementation.
There are more issues, but I think this is enough to explain why the algorithm doesn't work.
Assumptions
I add an implementation below, but I had to make some assumptions which were not clarified in your question:
-
If a (non-empty) list does not have two consecutive nodes that have an equal value, then the longest sequence with repeating values would be 1. As this is not really something that could be called "duplicate", I assume that in this case the list should stay as it is, without any removal.
-
If the list has two or more sections with duplicates that have the same length, and these happen to be the longest, then only the first section of nodes will be removed.
-
It is OK to create a helper node, which can be created with
SNode(None)
. As you didn't provide theSNode
implementation, I can only guess that the constructor can be called like that.
Algorithm
To remove a section from a linked list, you can better track which is the node that precedes that section, because it is that node that will need its next
attribute to be updated.
The first section of duplicates could occur at the very start of the list, and then there is no such predecessor node. In that case we have a different situation where the _head
attribute of the list should be updated (if it happens to be the longest duplicate series).
To allow these different scenarios to be dealt with in the same way, it is common practice to create a dummy node that is prefixed before the head node of the list. Then the predecessor node for the first series is that dummy node, and if that section needs to be removed, we can update the next
attribute of the predecessor without taking any special precautions. Finally we can assign dummy.next
back to the _head
attribute and this will update the head correctly when relevant.
Implementation
def delLargestSeq(self):
# Create a dummy node in front of the list, as this facilitates the rest
# of the code, and makes it easy to update the head at the end of the process
dummy = SNode(None)
dummy.next = self._head
# For efficient removal, we should keep track of the node that *precedes*
# the first node to remove
beforeDupes = dummy
beforeFirstToRemove = lastToRemove = None
numNodesToRemove = 1
while beforeDupes.next:
endDupes = beforeDupes.next
numNodes = 1
# Extend the selection of nodes for as long as they have the same value
while endDupes.next and endDupes.next.elem == endDupes.elem:
numNodes += 1
endDupes = endDupes.next
# Keep track of the longest sequence
if numNodes > numNodesToRemove:
numNodesToRemove = numNodes
beforeFirstToRemove = beforeDupes
lastToRemove = endDupes
beforeDupes = endDupes
# Assuming that a removal must concern at least 2 nodes,
# as a single node cannot be called "duplicate"
if numNodesToRemove > 1:
# Remove the longest sequence of duplicates
beforeFirstToRemove.next = lastToRemove.next
# If the sequence started at the first node, the head reference must be updated
self._head = dummy.next
See it run on repl.it
Visualisation
Let's take the example list:
1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6
When the algorithm has identified the first series of duplicate values, and this is the longest series so far, we have this state:
dummy
↓
None → 1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6
↑ ↑
beforeDupes endDupes
beforeFirstToRemove lastToRemove
Then beforeDupes
resumes from endDupes
onwards and a next sequence is identified:
dummy
↓
None → 1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6
↑ ↑
↑ beforeDupes endDupes
beforeFirstToRemove lastToRemove
This one does not update the beforeFirstToRemove
or lastToRemove
references as it is not greater in size. Same for:
dummy
↓
None → 1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6
↑ ↑
↑ ↑ beforeDupes endDupes
beforeFirstToRemove lastToRemove
...and:
dummy
↓
None → 1 → 3 → 3 → 3 → 3 → 4 → 4 → 5 → 5 → 5 → 6 → 6
↑ ↑
↑ ↑ beforeDupes endDupes
beforeFirstToRemove lastToRemove
Then the removal part kicks in:
dummy
↓ ┌─────────────────┐
None → 1 ┘ 3 → 3 → 3 → 3 → └→ 4 → 4 → 5 → 5 → 5 → 6 → 6
↑ ↑
beforeFirstToRemove lastToRemove
And that's basically it.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论