Прежде всего
Как упомянуто в комментарии, но, чтобы быть более ясным, вопрос от :
<Cracking the coding interview 6th>
| IX Interview Questions
| 2. Linked Lists
| Question 2.2
.
Это отличная книга Gayle Laakmann McDowell
, инженера-программиста из Google, который опросил много людей.
Подходы
(при условии, что связанный список не отслеживает длину) , есть 2 подхода за O (n) времени и O (1) пробел:
- Сначала найдите длину, затем зацикливайтесь на элементе (len-k + 1).
Это решение не упоминается в книге, насколько я помню.
- Цикл, через 2 указателя, держите их (k-1) на расстоянии.
Это решение из книги, как и в вопросе.
Код
Ниже приведена реализация в Java
с модульным тестом (без использования какой-либо продвинутой структуры данных в самом JDK) .
KthToEnd.java
/**
* Find k-th element to end of singly linked list, whose size unknown,
* <p>1-th is the last, 2-th is the one before last,
*
* @author eric
* @date 1/21/19 4:41 PM
*/
public class KthToEnd {
/**
* Find the k-th to end element, by find length first.
*
* @param head
* @param k
* @return
*/
public static Integer kthToEndViaLen(LinkedListNode<Integer> head, int k) {
int len = head.getCount(); // find length,
if (len < k) return null; // not enough element,
return (Integer) head.getKth(len - k).value; // get target element with its position calculated,
}
/**
* Find the k-th to end element, via 2 pinter that has (k-1) distance.
*
* @param head
* @param k
* @return
*/
public static Integer kthToEndVia2Pointer(LinkedListNode<Integer> head, int k) {
LinkedListNode<Integer> p0 = head; // begin at 0-th element,
LinkedListNode<Integer> p1 = head.getKth(k - 1); // begin at (k-1)-th element,
while (p1.next != null) {
p0 = p0.next;
p1 = p1.next;
}
return p0.value;
}
static class LinkedListNode<T> {
private T value;
private LinkedListNode next;
public LinkedListNode(T value) {
this.value = value;
}
/**
* Append a new node to end.
*
* @param value
* @return new node
*/
public LinkedListNode append(T value) {
LinkedListNode end = getEnd();
end.next = new LinkedListNode(value);
return end.next;
}
/**
* Append a range of number, range [start, end).
*
* @param start included,
* @param end excluded,
*/
public void appendRangeNum(Integer start, Integer end) {
KthToEnd.LinkedListNode last = getEnd();
for (int i = start; i < end; i++) {
last = last.append(i);
}
}
/**
* Get end element of the linked list this node belongs to, time complexity: O(n).
*
* @return
*/
public LinkedListNode getEnd() {
LinkedListNode end = this;
while (end != null && end.next != null) {
end = end.next;
}
return end;
}
/**
* Count of element, with this as head of linked list.
*
* @return
*/
public int getCount() {
LinkedListNode end = this;
int count = 0;
while (end != null) {
count++;
end = end.next;
}
return count;
}
/**
* Get k-th element from beginning, k start from 0.
*
* @param k
* @return
*/
public LinkedListNode getKth(int k) {
LinkedListNode<T> target = this;
while (k-- > 0) {
target = target.next;
}
return target;
}
}
}
KthToEndTest.java
(модульный тест, используя TestNG
, или вы изменяете на JUnit
/ .., по желанию)
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
/**
* KthToEnd test.
*
* @author eric
* @date 1/21/19 5:20 PM
*/
public class KthToEndTest {
private int len = 10;
private KthToEnd.LinkedListNode<Integer> head;
@BeforeClass
public void prepare() {
// prepare linked list with value [0, len-1],
head = new KthToEnd.LinkedListNode(0);
head.appendRangeNum(1, len);
}
@Test
public void testKthToEndViaLen() {
// validate
for (int i = 1; i <= len; i++) {
Assert.assertEquals(KthToEnd.kthToEndViaLen(head, i).intValue(), len - i);
}
}
@Test
public void testKthToEndVia2Pointer() {
// validate
for (int i = 1; i <= len; i++) {
Assert.assertEquals(KthToEnd.kthToEndVia2Pointer(head, i).intValue(), len - i);
}
}
}
Советы:
KthToEnd.LinkedListNode
Это простой односвязный узел списка, реализованный с нуля, он представляет собой связанный список, начинающийся с самого себя.
Он не отслеживает дополнительно голову / хвост / длину, хотя для этого есть методы.