我一直在处理我最近购买的一本书中的标准编码面试问题,并且我遇到了以下问题和答案:
实现算法以查找链表中的第n个到最后一个元素。
以下是提供的答案:
public static LinkedListNode findNtoLast(LinkedListNode head, int n) { //changing LinkedListNode to ListNode<String>
if(head == null || n < 1) {
return null;
}
LinkedListNode p1 = head;
LinkedListNode p2 = head;
for(int j = 0; j < n-1; ++j) {
if(p2 == null) {
return null;
}
p2 = p2.next;
}
if(p2 == null) {
return null;
}
while(p2.next != null) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
我理解算法,它是如何工作的,以及为什么本书将其列为答案,但我对如何访问LinkedListNodes以作为参数发送方法感到困惑。我知道我必须创建一个LinkedListNode类(因为Java还没有),但我似乎无法弄清楚如何做到这一点。这令人沮丧,因为我觉得我应该知道如何做到这一点。这是我一直在努力的事情。我非常感谢任何澄清。您可以扩展/评论我的代码或提供您自己的替代方案。感谢。
class ListNode<E> {
ListNode<E> next;
E data;
public ListNode(E value) {
data = value;
next = null;
}
public ListNode(E value, ListNode<E> n) {
data = value;
next = n;
}
public void setNext(ListNode<E> n) {
next = n;
}
}
public class MyLinkedList<E> extends LinkedList {
LinkedList<ListNode<E>> list;
ListNode<E> head;
ListNode<E> tail;
ListNode<E> current;
ListNode<E> prev;
public MyLinkedList() {
list = null;
head = null;
tail = null;
current = null;
prev = null;
}
public MyLinkedList(LinkedList<E> paramList) {
list = (LinkedList<ListNode<E>>) paramList; //or maybe create a loop assigning each ListNode a value and next ptr
head = list.getFirst();
tail = list.getLast(); //will need to update tail every time add new node
current = null;
prev = null;
}
public void addNode(E value) {
super.add(value);
//ListNode<E> temp = tail;
current = new ListNode<E>(value);
tail.setNext(current);
tail = current;
}
public LinkedList<ListNode<E>> getList() {
return list;
}
public ListNode<E> getHead() {
return head;
}
public ListNode<E> getTail() {
return tail;
}
public ListNode<E> getCurrent() {
return current;
}
public ListNode<E> getPrev() {
return prev;
}
}
LinkedListNode如何从LinkedList转向?
更新:我认为我的一些困惑来自于主要方法的内容。我是否需要创建ListNode的LinkedList?如果我这样做,我将如何将ListNodes彼此连接?如何在不使用LinkedList集合对象的情况下连接它们?如果有人能告诉我他们如何编写主要方法的代码,我认为这会让我有足够的视角来解决我的问题。这是我对主要方法的最新尝试:
public static void main(String args[]) {
LinkedList<ListNode<String>> list = new LinkedList<ListNode<String>>();
//MyLinkedList<ListNode<String>> list = new MyLinkedList(linkedList);
list.add(new ListNode<String>("Jeff"));
list.add(new ListNode<String>("Brian"));
list.add(new ListNode<String>("Negin"));
list.add(new ListNode<String>("Alex"));
list.add(new ListNode<String>("Alaina"));
int n = 3;
//ListIterator<String> itr1 = list.listIterator();
//ListIterator<String> itr2 = list.listIterator();
LinkedListNode<String> head = new LinkedListNode(list.getFirst(), null);
//String result = findNtoLast(itr1, itr2, n);
//System.out.println("The " + n + "th to the last value: " + result);
//LinkedListNode<String> nth = findNtoLast(list.getFirst(), n);
ListNode<String> nth = findNtoLast(list.getFirst(), n);
System.out.println("The " + n + "th to the last value: " + nth);
}
尝试在不使用自定义链接列表的情况下连接节点时,我已将ListNode类编辑为以下内容:
class ListNode<E> {
ListNode<E> next;
ListNode<E> prev; //only used for linking nodes in singly linked list
ListNode<E> current; //also only used for linking nodes in singly linked list
E data;
private static int size = 0;
public ListNode() {
data = null;
next = null;
current = null;
if(size > 0) { //changed from prev != null because no code to make prev not null
prev.setNext(this);
}
size++;
}
public ListNode(E value) {
data = value;
next = null;
current = this;
System.out.println("current is " + current);
if(size > 0) {
prev.setNext(current);//this line causing npe
}
else
{
prev = current;
System.out.println("prev now set to " + prev);
}
size++;
System.out.println("after constructor, size is " + size);
}
public ListNode(E value, ListNode<E> n) {
data = value;
next = n;
current = this;
if(size > 0) {
prev.setNext(this);
}
size++;
}
public void setNext(ListNode<E> n) {
next = n;
}
}
现在,程序将一直运行,直到达到prev.setNext(current);在ListNode的单个参数构造函数中。到达此行时,current和prev都不为null。任何建议将不胜感激。感谢。
答案 0 :(得分:1)
您实际上并不需要单独的LinkedList类; ListNode类是链表。或者,为了说明不同,对列表头部的引用是对列表的引用。
您发布的示例代码中head,tail,current,prev的使用来自双链表,这是一种双向链接的数据类型。这对于某些类型的应用程序(例如查找第n个最后一个项目)更有效。
所以我建议将ListNode类重命名为LinkedList,并将next
重命名为tail
。
要向列表添加新项目,您需要一种方法来创建一个新项目,其中包含新项目。这是一个例子:
class LinkedList<E> {
...
private LinkedList(E value, LinkedList<E> tail) {
this.data = value;
this.tail = tail;
}
public LinkedList<E> prependItem(E item) {
return new LinkedList(item, this);
}
}
然后向i
添加新项目list
,您使用list = list.prependItem(i);
如果出于某种原因需要始终将项目添加到最后,那么:
private LinkedList(E value) {
this.data = value;
this.tail = null;
}
public void appendItem(E item) {
LinkedList<E> list = this;
while (list.tail != null)
list = list.tail;
list.tail = new LinkedList<>(item);
}
然而,对于长列表来说,这显然效率很低。如果您需要这样做,那么要么使用不同的数据结构,要么在完成添加后反转列表。
顺便提一下,这有趣的副作用是对列表中任何项的引用是对链表的引用。这使得递归变得非常容易。例如,这是一个用于查找列表长度的递归解决方案:
public int getLength(LinkedList list) {
if (list == null) {
return 0;
} else {
return 1 + getLength(list.getTail());
}
}
使用这个简单(但非常低效!)解决你提供的问题 - 我已经重命名了方法,使其功能更加明显:
public LinkedList getTailOfListOfLengthN(LinkedList list, int n) {
int length = getLength(list);
if (length < n) {
return null;
} else if (length == n) {
return list;
} else {
return getTailOfLengthN(list.getTail(), n);
}
}
要反转清单:
public LinkedList<E> reverse() {
if (tail == null) {
return this;
} else {
LinkedList<E> list = reverse(tail);
tail.tail = this;
tail = null;
return list;
}
}
我希望你能看到这使得方法比分离节点列表类更优雅。
答案 1 :(得分:0)
实际上,您已经使用类ListNode创建了一个链接列表。
链接列表由节点和对另一个链表的引用组成(请参阅递归?)。