我目前正在为秋季的一门课程做一些练习题,但遇到了这个问题:
编写一个方法removeAll,该方法可以添加到
LinkedIntList
类中。该方法应从整数排序列表中有效地删除出现在第二个整数排序列表中的所有值。例如,假设LinkedIntList
变量list1
和list2
引用了以下列表:
list1
:[1、3、5、7]
list2
:[1、2、3、4、5]如果拨打电话
list1.removeAll(list2)
,则列表应存储 通话后输入以下值:
list1
:[7]
list2
:[1、2、3、4、5]尽管两个列表中都可能有重复项,但两个列表都保证按排序(非降序)顺序进行。由于列表是经过排序的,因此您只需单击一次数据就可以非常有效地解决此问题。您的解决方案需要在 O(M + N)时间内运行,其中 M 和 N 是两个列表的长度。
您不得调用该类的任何其他方法来解决此问题,您不得构造任何新节点,并且您不得使用任何辅助数据结构来解决此问题(例如数组
ArrayList
,Queue
,String
等)。您也不能更改节点的任何数据字段。 您必须通过重新排列列表的链接来解决此问题。
为了解决此问题,提供了一个自定义的LinkedIntList和ListNode类。
public class LinkedIntList {
private ListNode front; // head of the list
...
}
public class ListNode {
public int data; // value stored in this node of the list
public ListNode next; // reference to next node in the list (null if end of list)
...
}
我一次过就在O(M + N)次时间内解决了这个问题。但是,我想知道是否有任何方法可以更有效地解决这个问题。我在if语句中有一个嵌套的if-else块,我认为可以清除它。我在下面包含了我的代码。
public void removeAll(LinkedIntList list2) {
if (this.front != null && list2.front != null) {
// iterators for the list to remove values from
ListNode prev = null;
ListNode current = this.front;
// iterator for the list where values from list1 may appear
ListNode list2Current = list2.front;
// reference to the front of list1
ListNode head = current;
while (current != null && list2Current != null) {
while (current != null && current.data < list2Current.data) {
prev = current;
current = current.next;
}
while (current != null && list2Current != null && list2Current.data < current.data) {
list2Current = list2Current.next;
}
if (current != null && list2Current != null && current.data == list2Current.data) {
// prev can only be null if current is still pointing at the front of list1
if (prev == null) {
ListNode nextup = current.next;
current.next = null;
current = nextup;
head = current;
} else {
prev.next = current.next;
current = prev.next;
}
} else if (current != null) {
prev = current;
current = current.next;
}
}
front = head;
}
}
答案 0 :(得分:1)
解决此问题的方法比O(M+N)
快,因为这两个列表的所有元素都应迭代。
但是,该解决方案的编码方式可能更简单,例如:
public void removeAll(LinkedIntList list2) {
ListNode previous = front;
ListNode current = front;
ListNode toRemove = list2.front;
while (current != null && toRemove != null) {
if (current.data < toRemove.data) {
previous = current;
current = current.next;
} else if (current.data > toRemove.data) {
toRemove = toRemove.next;
} else {
if (current == front) {
front = current.next;
} else {
previous.next = current.next;
}
current = current.next;
}
}
}
答案 1 :(得分:1)
您对解决方案有了正确的想法。这个问题要求您至少检查一次列表中的每个条目,因此根据定义,您做不到比单遍线性时间更好的了。我相信您找到了最佳解决方案。
关于您的代码的一些说明:
if (prev == null) {
ListNode nextup = current.next;
current.next = null;
current = nextup;
}
您分离了要删除的节点,但从未将下一个节点(新的current
)附加到列表中。您的列表头仍将指向“已删除”节点。 (我认为您可能已经尝试在循环结束时使用front = head
来解决此问题,但是您从未更新过head
,所以实际上没有任何效果。我只想删除该行一起。)
这是我重写if
的方式:
if (current != null && list2Current != null && current.data == list2Current.data) {
if (prev == null) {
this.first = current.next;
} else {
prev.next = current.next;
prev = current;
}
current = current.next;
}
关于else if
:两个退出while
循环的 only 方法是if
因此,从理论上讲,您甚至不需要检查是否相等。在任何情况下,您都不需要继续并最终进入该else
分支。
还有其他方法可以使代码更简洁(例如@MartinBG的答案),从而实现相同的解决方案,但是我尝试专门清理代码。