La fonction suivante tente de trouver l'élément nth
to last d'une liste à liens simples.
Par exemple:
Si les éléments sont 8->10->5->7->2->1->5->4->10->10
, le résultat est 7th
jusqu'au dernier nœud est 7
.
Quelqu'un peut-il m'aider sur le fonctionnement de ce code ou existe-t-il une approche plus simple et meilleure?
LinkedListNode nthToLast(LinkedListNode head, int n) {
if (head == null || n < 1) {
return null;
}
LinkedListNode p1 = head;
LinkedListNode p2 = head;
for (int j = 0; j < n - 1; ++j) { // skip n-1 steps ahead
if (p2 == null) {
return null; // not found since list size < n
}
p2 = p2.next;
}
while (p2.next != null) {
p1 = p1.next;
p2 = p2.next;
}
return p1;
}
Votre algorithme fonctionne en créant d’abord des références à deux nœuds de votre liste liée qui sont séparés par N nœuds. Ainsi, dans votre exemple, si N est 7, il définira p1 à 8 et p2 à 4.
Il fera ensuite avancer chaque référence de noeud au prochain noeud de la liste jusqu'à ce que p2 atteigne le dernier élément de la liste. Encore une fois, dans votre exemple, ce sera lorsque p1 est 5 et p2 est 10. À ce stade, p1 fait référence au Nième jusqu'au dernier élément de la liste (par la propriété qu'ils sont séparés de N nœuds).
La clé de cet algorithme consiste à définir deux pointeurs p1
et p2
séparés par les nœuds n-1
. Nous voulons donc que p2
pointe vers le nœud (n-1)th
à partir du début de la liste, puis nous déplaçons p2
jusqu'à atteindre le nœud last
de la liste. Une fois que p2
aura atteint la fin de la liste, p1
pointera vers le nième nœud à partir de la fin de la liste.
J'ai mis l'explication en ligne sous forme de commentaires. J'espère que ça aide:
// Function to return the nth node from the end of a linked list.
// Takes the head pointer to the list and n as input
// Returns the nth node from the end if one exists else returns NULL.
LinkedListNode nthToLast(LinkedListNode head, int n) {
// If list does not exist or if there are no elements in the list,return NULL
if (head == null || n < 1) {
return null;
}
// make pointers p1 and p2 point to the start of the list.
LinkedListNode p1 = head;
LinkedListNode p2 = head;
// The key to this algorithm is to set p1 and p2 apart by n-1 nodes initially
// so we want p2 to point to the (n-1)th node from the start of the list
// then we move p2 till it reaches the last node of the list.
// Once p2 reaches end of the list p1 will be pointing to the nth node
// from the end of the list.
// loop to move p2.
for (int j = 0; j < n - 1; ++j) {
// while moving p2 check if it becomes NULL, that is if it reaches the end
// of the list. That would mean the list has less than n nodes, so its not
// possible to find nth from last, so return NULL.
if (p2 == null) {
return null;
}
// move p2 forward.
p2 = p2.next;
}
// at this point p2 is (n-1) nodes ahead of p1. Now keep moving both forward
// till p2 reaches the last node in the list.
while (p2.next != null) {
p1 = p1.next;
p2 = p2.next;
}
// at this point p2 has reached the last node in the list and p1 will be
// pointing to the nth node from the last..so return it.
return p1;
}
Alternativement, nous pouvons définir p1
et p2
séparés par n nœuds au lieu de (n-1)
, puis déplacer p2
jusqu'à la fin de la liste au lieu de passer au dernier nœud:
LinkedListNode p1 = head;
LinkedListNode p2 = head;
for (int j = 0; j < n ; ++j) { // make then n nodes apart.
if (p2 == null) {
return null;
}
p2 = p2.next;
}
while (p2 != null) { // move till p2 goes past the end of the list.
p1 = p1.next;
p2 = p2.next;
}
return p1;
Que pensez-vous de cette approche?.
//this is the recursive solution
//initial call
find(HEAD,k);
// main function
void find(struct link *temp,int k)
{
if( temp->next != NULL)
find( temp->next, k);
if((c++) == k) // c is initially declared as 1 and k is the node to find from last.
cout<<temp->num<<' ';
}
Il y a déjà beaucoup de réponses ici, mais ils parcourent tous la liste deux fois (de manière séquentielle ou parallèle) ou utilisent beaucoup d'espace de stockage supplémentaire.
Vous pouvez le faire en parcourant la liste une seule fois (plus un peu) en utilisant un espace supplémentaire constant:
Node *getNthFromEnd(Node *list, int n) {
if (list == null || n<1) {
return null; //no such element
}
Node *mark1 = list, *mark2 = list, *markend = list;
int pos1 = 0, pos2 = 0, posend = 0;
while (markend!=null) {
if ((posend-pos2)>=(n-1)) {
mark1=mark2;
pos1=pos2;
mark2=markend;
pos2=posend;
}
markend=markend->next;
++posend;
}
if (posend<n) {
return null; //not enough elements in the list
}
//mark1 and mark2 are n-1 elements apart, and the end is at least
//1 element after mark2, so mark1 is at least n elements from the end
while((posend - pos1) > n) {
mark1 = mark1->next;
++pos1;
}
return mark1;
}
Cette version utilise 2 pointeurs supplémentaires qui font moins que N+n
traversals, où N
est la longueur de la liste et n
est l'argument.
Si vous utilisez des pointeurs M
extra, vous pouvez le réduire à N+ceil(n/(M-1))
(et vous devez les stocker dans un tampon circulaire).
Comme cela ressemble à des devoirs, je préfère vous aider à vous aider vous-même plutôt que de donner une solution réelle.
Je vous suggère d'exécuter ce code sur un petit échantillon de données. Utilisez votre débogueur pour exécuter des lignes pas à pas (vous pouvez définir un point d'arrêt au début de la fonction). Cela devrait vous donner une idée du fonctionnement du code.
Vous pouvez également Console.WriteLine()
pour afficher les variables d’intérêt.
Juste une autre solution à ce problème. Bien que la complexité temporelle reste la même, ce code réalise la solution en une seule boucle.
public Link findKthElementFromEnd(MyLinkedList linkedList, int k)
{
Link current = linkedList.getFirst();//current node
Link currentK = linkedList.getFirst();//node at index k
int counter = 0;
while(current.getNext()!=null)
{
counter++;
if(counter>=k)
{
currentK = currentK.getNext();
}
current = current.getNext();
}
//reached end
return currentK;
}
Vous pouvez simplement parcourir la liste liée et obtenir la taille. Une fois que vous avez la taille, vous pouvez trouver le deuxième terme dans 2n qui est encore O(n).
public T nthToLast(int n) {
// return null if linkedlist is empty
if (head == null) return null;
// declare placeholder where size of linkedlist will be stored
// we are hoping that size of linkedlist is less than MAX of INT
int size = 0;
// This is O(n) for sure
Node i = head;
while (i.next != null) {
size += 1;
i = i.next;
}
// if user chose something outside the size of the linkedlist return null
if (size < n)
return null;
// This is O(n) if n == size
i = head;
while(size > n) {
size--;
i = i.next;
}
// Time complexity = n + n = 2n
// therefore O(n)
return i.value;
}
Solution en C #. Créez une LinkedList avec des valeurs factices.
LinkedList<int> ll = new LinkedList<int>();
ll.AddFirst(10);
ll.AddLast(12);
ll.AddLast(2);
ll.AddLast(8);
ll.AddLast(9);
ll.AddLast(22);
ll.AddLast(17);
ll.AddLast(19);
ll.AddLast(20);
Créez 2 pointeurs p1 et p1 qui pointent vers le premier nœud.
private static bool ReturnKthElement(LinkedList<int> ll, int k)
{
LinkedListNode<int> p1 = ll.First;
LinkedListNode<int> p2 = ll.First;
Parcourez la boucle jusqu'à ce que p2 soit nul - ce qui signifie que la longueur de la liste chaînée est inférieure à Kth élément OR jusqu'à l'élément Kth
for (int i = 0; i < k; i++)
{
p2 = p2.Next;
if (p2 == null)
{
Console.WriteLine($"Linkedlist is smaller than {k}th Element");
return false;
}
}
Maintenant, itérez les deux pointeurs jusqu'à ce que p2 soit nul. La valeur contenue dans le pointeur p1 correspondra à Kth Element
while (p2 != null)
{
p1 = p1.Next;
p2 = p2.Next;
}
//p1 is the Kth Element
Console.WriteLine($"Kth element is {p1.Value}");
return true;
}
Inversez simplement la liste chaînée en temps linéaire et trouvez le kième élément. Il fonctionne toujours en temps linéaire.
public int nthFromLast(int n){
Node current = head;
Node reference = head;
for(int i=0;i<n;i++){
reference=reference.getNext();
}
while(reference != null){
current = current.getNext();
reference = reference.getNext();
}
return current.getData();
}
Utilisez deux pointeurs pTemp et NthNode. Initialement, les deux points sont dirigés vers le noeud principal de la liste. NthNode commence à se déplacer uniquement après que pTemp ait effectué n déplacements. Les deux avancent jusqu'à ce que pTemp atteigne la fin de la liste. En conséquence, NthNode pointe sur le nième nœud à partir de la fin de la liste liée.
public ListNode NthNodeFromEnd(int n){
ListNode pTemp = head, NthNode = null;
for(int count=1; count<n;count++){
if(pTemp!=null){
pTemp = pTemp.getNext();
}
}
while(pTemp!=null){
if(NthNode==null){
NthNode = head;
}
else{
NthNode = NthNode.getNext();
}
pTemp = pTemp.getNext();
}
if(NthNode!=null){
NthNode = NthNode.getNext();
return NthNode;
}
return null;
}
Référence TextBook: "Structure de données et algorithmes simplifiés en Java"
Pour comprendre ce problème, nous devrions faire une simple analogie avec un exemple de mesure. Disons que vous devez trouver la position de votre bras à exactement 1 mètre de votre majeur, comment mesureriez-vous? Il vous suffirait de saisir une règle de 1 mètre de long et de placer l'extrémité supérieure de cette règle au bout de votre majeur, l'extrémité inférieure du mètre se trouvant à exactement 1 mètre du haut de votre milieu. doigt.
Ce que nous faisons dans cet exemple sera le même, nous avons juste besoin d’un cadre avec un élément n large et nous devons placer le cadre à la fin de la liste, ainsi le nœud de départ du cadre sera exactement n- e élément à la fin de la liste.
Ceci est notre liste en supposant que nous avons M éléments dans la liste et notre cadre avec N élément large;
HEAD -> EL(1) -> EL(2) -> ... -> EL(M-1) -> EL(M)
<-- Frame -->
Cependant, nous n'avons besoin que des limites de la trame, ainsi la limite de fin de la trame sera exactement (N-1) éléments à l'écart de la limite de début de la trame. Donc, ne devez stocker que ces éléments de frontière. Appelons-les A et B;
HEAD -> EL(1) -> EL(2) -> ... -> EL(M-1) -> EL(M)
A <- N-Element Wide-> B
La première chose à faire est de trouver B, qui est la fin du cadre.
ListNode<T> b = head;
int count = 1;
while(count < n && b != null) {
b = b.next;
count++;
}
Maintenant, b est le n-ième élément du tableau et a est situé sur la tête . Pour que notre cadre soit défini, nous allons incrémenter les deux nœuds limites étape par étape jusqu'à ce que b atteigne la fin de la liste, où a sera le n-ème avant-dernier élément;
ListNode<T> a = head;
while(b.next != null) {
a = a.next;
b = b.next;
}
return a;
Pour tout rassembler, et avec les contrôles HEAD, N <M (où M est la taille de la liste) et autres, voici la méthode de la solution complète;
public ListNode<T> findNthToLast(int n) {
if(head == null) {
return null;
} else {
ListNode<T> b = head;
int count = 1;
while(count < n && b != null) {
b = b.next;
count++;
}
if(count == n && b!=null) {
ListNode<T> a = head;
while(b.next != null) {
a = a.next;
b = b.next;
}
return a;
} else {
System.out.print("N(" + n + ") must be equal or smaller then the size of the list");
return null;
}
}
}
J'ai ma solution récursive à un autre thread dans StackOverflow ici
Non, vous ne connaissez pas la longueur de la liste de liens .... Vous devrez parcourir une fois pour obtenir la longueur de la liste des éléments préférés, votre approche est donc peu efficace;
Nous prenons ici deux pointeurs pNode et qNode, les deux points initiaux étant dirigés vers la tête qNode. Ensuite, parcourez jusqu'à la fin de la liste et le pNode ne traversera que lorsqu'il existe une différence entre le nombre et la position est supérieure à 0 et que pthNode s'incrémente une fois dans chaque boucle.
static ListNode nthNode(int pos){
ListNode pNode=head;
ListNode qNode=head;
int count =0;
while(qNode!=null){
count++;
if(count - pos > 0)
pNode=pNode.next;
qNode=qNode.next;
}
return pNode;
}
Le problème posé dans le livre de la coupe est légèrement différent. Il est dit de trouver le dernier élément d’une liste à liens simples.
Voici mon code:
public void findntolast(int index)
{
Node ptr = front; int count = 0;
while(ptr!=null)
{
count++;
if (count == index)
{
front = ptr;
break;
}
ptr = ptr.next;
}
Node temp=front;
while(temp!=null)
{
Console.WriteLine(temp.data);
temp=temp.next;
}
}
Solution récursive:
Node findKth (Node head, int count, int k) {
if(head == null)
return head;
else {
Node n =findKth(head.next,count,k);
count++;
if(count == k)
return head;
return n;
}
}
mon approche, ce que je pense est simple et a une complexité temporelle O (n).
Étape 1: commencez par obtenir le nombre de nœuds. Exécuter une boucle for à partir du premier nœud jusqu'au dernier nœud
Étape 2: Une fois que vous avez le compte, appliquez un calcul simple, par exemple, si nous avons trouvé le 7ème noeud du dernier noeud et que le nombre de tous les noeuds est égal à 12, alors (count - index) - 1 donnera un kème noeud, jusqu'à lequel vous devrez traverser et ce sera le nième nœud au dernier nœud. Dans ce cas (12 -7) -1 = 4
Si les éléments sont 8-> 10-> 5-> 7-> 2-> 1-> 5-> 4-> 10-> 10 alors le résultat est le 7ème et dernier noeud est 7, ce qui n'est rien d'autre que le 4ème noeud de le début.
pouvez-vous utiliser une structure de données supplémentaire .. si c'est simple ... commencez à placer tous les nœuds dans une pile, maintenez un compteur et affichez-le. selon votre exemple, 8-> 10-> 5-> 7-> 2-> 1-> 5-> 4-> 10-> 10 commencez à lire la liste chaînée et commencez à pousser les nœuds ou le nœud-> données sur une pile. ainsi la pile ressemblera à top -> {10, 10,4, 5, 1, 2, 7, 5, 10, 8} <- bottom.
commencez maintenant à sortir du haut de la pile en maintenant un compteur = 1 et chaque fois que vous sautez augmentez le compteur de 1, lorsque vous atteignez le n-ième élément (dans votre exemple 7ème élément), arrêtez de sauter.
remarque: ceci imprimera ou récupérera les données/nœuds en ordre inverse
Voici le code utilisant l'approche à 2 pointeurs: ( source )
struct node
{
int data;
struct node *next;
}mynode;
mynode * nthNodeFrmEnd(mynode *head, int n /*pass 0 for last node*/)
{
mynode *ptr1,*ptr2;
int count;
if(!head)
{
return(NULL);
}
ptr1 = head;
ptr2 = head;
count = 0;
while(count < n)
{
count++;
if((ptr1=ptr1->next)==NULL)
{
//Length of the linked list less than n. Error.
return(NULL);
}
}
while((ptr1=ptr1->next)!=NULL)
{
ptr2=ptr2->next;
}
return(ptr2);
}
node* findNthNode (node* head, int find, int& found){
if(!head) {
found = 1;
return 0;
}
node* retval = findNthNode(head->next, find, found);
if(found==find)
retval = head;
found = found + 1;
return retval;
}
Voici la version C # de la recherche du nième enfant à partir de Linklist.
public Node GetNthLast(Node head, int n)
{
Node current, nth;
current = nth = head;
int counter = 0;
while (current.next != null)
{
counter++;
if (counter % n == 0)
{
for (var i = 0; i < n - 1; i++)
{
nth = nth.next;
}
}
current = current.next;
}
var remainingCounts = counter % n;
for (var i = 0; i < remainingCounts; i++)
{
nth = nth.next;
}
return nth;
}
Vous pouvez également résoudre le problème ci-dessus à l'aide de tables de hachage.Les entrées de la table de hachage sont la position du nœud et l'adresse du nœud. Donc, si nous voulons trouver le nième nœud à partir de la fin (cela signifie m-n + 1 à partir du premier où m est le nombre de nœuds). Maintenant, lorsque nous entrons dans la table de hachage, nous obtenons le nombre de nœuds.
1. Traversez chaque noeud et faites les entrées correspondantes dans la table de hachage.
2.Look pour le nœud m-n + 1 dans la table de hachage, nous obtenons l'adresse.
La complexité temporelle est O (n).
En fonction de la tolérance au coût de la mémoire (O (k) dans cette solution), nous pourrions allouer un tableau de pointeurs de longueur k et le renseigner avec les nœuds sous forme de tableau circulaire tout en parcourant la liste liée.
Lorsque nous aurons fini de parcourir la liste liée, le premier élément du tableau (assurez-vous simplement de calculer l'indice 0 correctement car il s'agit d'un tableau circulaire), nous aurons la réponse.
Si le premier élément du tableau est null, il n'y a pas de solution à notre problème.
Je pense qu'il y a une faille dans le code de la question, et je me demande si elle a été extraite d'un livre. Comment est-ce possible? Il peut s'exécuter correctement, mais le code est quelque peu logiquement incorrect. Dans la boucle for ... la condition if doit être vérifiée par rapport à p2->next ! = NULL
for (int j = 0; j < n - 1; ++j) { // skip n-1 steps ahead
if (p2->next == null) {
return null; // not found since list size < n
}
... le reste est correct et les explications étant donné que le code change déjà p2
(n-1)
positions d'avance sur p1
, puis dans la boucle, il les déplace simultanément jusqu'à ce que p2->next
atteigne la fin.
Comme mentionné dans le commentaire, mais pour être plus clair, la question est de :
<Cracking the coding interview 6th>
|IX Interview Questions
|2. Linked Lists
|Question 2.2
.
C'est un excellent livre de Gayle Laakmann McDowell
, un ingénieur en logiciels de Google, qui a interviewé beaucoup de gens.
(En supposant que la liste chaînée ne garde pas la trace de la longueur), il existe deux approches dans O(n) time et O(1) espace:
Voici l’implémentation dans Java
, avec le test unitaire, (sans utiliser de structure de données avancée dans JDK lui-même).
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
(test unitaire, en utilisant TestNG
, ou vous passez à JUnit
/.., comme vous le souhaitez)
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);
}
}
}
Conseils:
KthToEnd.LinkedListNode
En Java, je vais utiliser
public class LL {
Node head;
int linksCount;
LL(){
head = new Node();
linksCount = 0;
}
//TRAVERSE TO INDEX
public Node getNodeAt(int index){
Node temp= head;
if(index > linksCount){
System.out.println("index out of bound !");
return null;
}
for(int i=0;i<index && (temp.getNext() != null);i++){
temp = temp.getNext();
}
return temp.getNext();
}
}
Personne ici n'a remarqué que la version de Jonathan lève une exception NullPinterException si le n est supérieur à la longueur de LinkedList . Voici ma version:
public Node nth(int n){
if(head == null || n < 1) return null;
Node n1 = head;
Node n2 = head;
for(int i = 1; i < n; i++){
if(n1.next == null) return null;
n1 = n1.next;
}
while (n1.next != null){
n1 = n1.next;
n2 = n2.next;
}
return n2;
}
Je ne fais que peu de changement ici: lorsque le nœud n1 avance, au lieu de vérifier si n1 est nul, je vérifie que le temps n1.next est nul ou sinon, la boucle while n1.next lève une exception NullPinterException.