Inventory the linked list of data structure and algorithm questions that must be asked

0 Overview
As a basic data structure, linked lists are used in many places. For example, it is used in Linux kernel code, redis source code, and python source code. In addition to singly linked lists, there are also doubly linked lists. This article mainly focuses on singly linked lists (including some circular linked lists, which will be indicated in the title, and other cases are discussed in simple singly linked lists). The doubly linked list has a good implementation in redis, and I also copied a copy in my repository for testing. The relevant code of this article is here.

1 Definition

First define a singly linked list structure, as follows, define two structures of linked list node and linked list. Here I do not define a structure of a linked list to save information such as the head pointer, tail pointer, length of the linked list, etc. The purpose is to practice the operation of the pointer more.

// aslist.h

// Linked list node definition
typedef struct ListNode { struct ListNode *next; int value; } listNode;


image.png

2 Basic operations

Based on the linked list definition in the previous section, we complete several basic operation functions, including linked list initialization, adding nodes to the linked list, and deleting nodes from the linked list.

/**

  • Create a linked list node
    */
    ListNode *listNewNode(int value)
    { ListNode *node; if (!(node ​​= malloc(sizeof(ListNode)))) return NULL;


    node->value = value;
    node->next = NULL;
    return node;
    }

/**

  • The header insertion method is inserted into the node.
    */
    ListNode *listAddNodeHead(ListNode *head, int value)
    { ListNode *node; if (!(node ​​= listNewNode(value))) return NULL;


    if (head)
    node->next = head;

    head = node;
    return head;
    }

/**

  • Tail interpolation inserts a node whose value is value.
    */
    ListNode *listAddNodeTail(ListNode *head, int value)
    { ListNode *node; if (!(node ​​= listNewNode(value))) return NULL;


    return listAddNodeTailWithNode(head, node);
    }

/**

  • 尾插法插入结点。
    */
    ListNode *listAddNodeTailWithNode(ListNode *head, ListNode *node)
    {
    if (!head) {
    head = node;
    } else {
    ListNode *current = head;
    while (current->next) {
    current = current->next;
    }
    current->next = node;
    }
    return head;
    }

/**

  • Remove the node whose value is value from the linked list.
    */
    ListNode *listDelNode(ListNode *head, int value)
    { ListNode *current=head, *prev=NULL;

    while (current) {
    if (current->value == value) {
    if (current == head)
    head = head->next;

         if (prev)
             prev->next = current->next;
    
         free(current);
         break;
     }
    
     prev = current;
     current = current->next;
    

    }
    return head;
    }

/**

  • Linked list traversal.
    */
    void listTraverse(ListNode *head)
    { ListNode *current = head; while (current) { printf(“%d”, current->value); printf(“->”); current = current->next; if (current == head) // handle the case of the first and last circular linked list break; }







    printf(“NULL\n”);
    }

/**

  • Initializes a linked list with an array of len elements.
    */
    ListNode *listCreate(int a[], int len)
    { ListNode *head = NULL; int i; for (i = 0; i < len; i++) { if (!(head = listAddNodeTail(head, a[i ]))) return NULL; } return head; }







/**

  • List length function
    */
    int listLength(ListNode *head)
    { int len ​​= 0; while (head) { len++; head = head->next; } return len; }






3 Linked list related interview questions

3.1 Reverse order of linked list

Question: ** Given a singly linked list 1->2->3->NULL, the reverse order becomes 3->2->1->NULL. **

Solution: It is common to use a circular method to connect each node in reverse order, as follows:

/**

  • Reverse linked list, non-recursive implementation.
    */
    ListNode *listReverse(ListNode *head)
    { ListNode *newHead = NULL, *current = head; while (current) { ListNode *next = current->next; current->next = newHead; newHead = current; current = next ; }






    return newHead;
    }

If it is a bit dazzling, then come to a recursive solution, as follows:

/**

  • The linked list is reversed, recursively implemented.
    */
    ListNode *listReverseRecursive(ListNode *head)
    { if (!head || !head->next) { return head; }


    ListNode *reversedHead = listReverseRecursive(head->next);
    head->next->next = head;
    head->next = NULL;
    return reversedHead;
    }

3.2 Linked list replication

Question: ** Given a singly linked list, copy and return the new linked list head node. **

Solution: There are also two solutions, non-recursive and recursive, as follows:

/**

  • Linked list copy - non-recursive
    */
    ListNode *listCopy(ListNode *head)
    { ListNode *current = head, *newHead = NULL, *newTail = NULL; while (current) { ListNode *node = listNewNode(current->value); if (!newHead) { // first node newHead = newTail = node; } else { newTail->next = node; newTail = node; } current = current->next; } return newHead;












/**

  • Linked List Copy - Recursive
    */
    ListNode *listCopyRecursive(ListNode *head)
    { if (!head) return NULL;

    ListNode *newHead = listNewNode(head->value);
    newHead->next = listCopyRecursive(head->next);
    return newHead;
    }

3.3 Linked list merging

Question: ** Two ordered singly linked lists are known, please merge these two linked lists so that the merged linked list is still in order (Note: **** These two linked lists have no common node, that is, they do not intersect). ** If the linked list 1 is 1->3->4->NULL, and the linked list 2 is 2->5->6->7->8->NULL, the combined linked list is 1->2->3 ->4->5->6->7->8->NULL.

Solution: This is very similar to the last step of merge sort, just merge the two ordered lists together. Use 2 pointers to traverse the two linked lists respectively, and merge the smaller value node into the result linked list. If there are still nodes in another linked list after the merging of one linked list is completed, add the rest of the other linked list to the end of the resulting linked list. Here I recommend an architecture learning exchange circle to everyone. Communication study guide pseudo-xin: 1253431195 (there are a lot of interview questions and answers), which will share some video recordings recorded by senior architects: there are Spring, MyBatis, Netty source code analysis, high concurrency, high performance, distributed, micro-service architecture The principle of JVM performance optimization, distributed architecture, etc. have become the necessary knowledge system for architects. You can also receive free learning resources, which have benefited a lot at present. The
codes are as follows:

/**

  • Linked list merge - non-recursive
    */
    ListNode *listMerge(ListNode *list1, ListNode *list2)
    { ListNode dummy; // use empty node to save merged list ListNode *tail = &dummy;

    if (!list1)
    return list2;

    if (!list2)
    return list1;

    while (list1 && list2) {
    if (list1->value <= list2->value) {
    tail->next = list1;
    tail = list1;
    list1 = list1->next;
    } else {
    tail->next = list2;
    tail = list2;
    list2 = list2->next;
    }
    }

    if (list1) {
    tail->next = list1;
    } else if (list2) {
    tail->next = list2;
    }

    return dummy.next;
    }

Of course, it is not difficult to implement a recursion, the code is as follows:

ListNode *listMergeRecursive(ListNode *list1, ListNode *list2)
{ ListNode *result = NULL;

if (!list1)
    return list2;

if (!list2)
    return list1;

if (list1->value <= list2->value) {
    result = list1;
    result->next = listMergeRecursive(list1->next, list2);
} else {
    result = list2;
    result->next = listMergeRecursive(list1, list2->next);
}

return result;

}

3.4 Intersection judgment of linked lists

Question: ** Given two singly linked lists list1 and list2, determine whether the two linked lists intersect. ** If it intersects, find the intersection node.

Solution 1: You can traverse list1 directly, and then judge whether each node of list1 is in list2 in turn, but the complexity of this solution is O(length(list1) * length(list2)). Of course, when we traverse list1, we can use a hash table to store the nodes of list1, so we can judge by traversing list2 again. The time complexity is O(length(list1) + length(list2)), and the space complexity is O(length (list1)), so that the intersecting nodes are naturally found. Of course, there are better ways to find intersections.

Solution 2: If two linked lists intersect, then their nodes must be the same from the intersection. Assuming that the length of list1 is len1, the length of list2 is len2, and len1 > len2, we only need to traverse list1 to len1-len2 nodes first, and then traverse the two nodes together. If an equal node is encountered, the node is the first intersection node.
/**

  • If the linked list intersects, it returns the intersecting node, otherwise it returns NULL.
    */
    ListNode *listIntersect(ListNode *list1, ListNode *list2)
    { int len1 = listLength(list1); int len2 = listLength(list2); int delta = abs(len1 - len2);


    ListNode *longList = list1, *shortList = list2;

    if (len1 < len2) {
    longList = list2;
    shortList = list1;
    }

    int i;
    for (i = 0; i < delta; i++) {
    longList = longList->next;
    }

    while (longList && shortList) {
    if (longList == shortList)
    return longList;

     longList = longList->next;
     shortList = shortList->next;
    

    }

    return NULL;
    }

3.5 Determine whether there is a cycle in the linked list

Question: ** Given a linked list, determine whether there is a cycle in the linked list. **

image.png

Solution 1: The easiest way to think of is to use a hash table to record the nodes that have appeared, and traverse the linked list. If a node appears repeatedly, it means that there is a cycle in the linked list. If you don't need a hash table, you can also add a visited field to the ListNode structure of the linked list node to mark it, and the visited mark is 1, which can also be detected. Since we haven't implemented a hash table yet, this method code will be added later.

Solution 2: A better method is the Floyd cycle algorithm, which was first invented by Robert Freud. By using two pointers fast and slow to traverse the linked list, the fast pointer takes two steps each time, and the slow pointer takes one step each time. If fast and slow meet, it means that there is a ring, otherwise there is no ring. (Note that if the linked list has only one node and no cycle, it will not enter the while loop)

/**

  • Detecting whether a linked list has a cycle - Flod algorithm

  • If there is a loop, return the encounter node, otherwise return NULL
    */
    ListNode *listDetectLoop(ListNode *head)
    { ListNode *slow, *fast; slow = fast = head;

    while (slow && fast && fast->next) {
    slow = slow->next;
    fast = fast->next->next;
    if (slow == fast) {
    printf(“Found Loop\n”);
    return slow;
    }
    }

    printf(“No Loop\n”);
    return NULL;
    }

void testListDetectLoop()
{
printf(“\nTestListDetectLoop\n”);
int a[] = {1, 2, 3, 4};
ListNode *head = listCreate(a, ALEN(a));
listDetectLoop(head);

// 构造一个环
head->next->next->next = head;
listDetectLoop(head);

}
Extension: If a loop is detected, how to find the entry point of the loop of the linked list?

First, let's prove why the algorithm mentioned in Solution 2 above is correct. If there is no ring in the linked list, because the fast pointer takes 2 steps at a time, it will inevitably reach the end of the linked list before the slow pointer and will not meet.

If there is a ring, assuming that the fast and slow pointers meet after s cycles, then the distance traveled by the fast pointer is 2s, and the distance traveled by the slow pointer is s. Assuming that the number of nodes in the ring is r, the following conditions must be met to meet: That is, the number of encounters satisfies s = nr. That is, the next encounter after the starting point needs to cycle r times.

2s - s = nr => s = nr

If the ring length r=4, then the next encounter from the starting point needs to go through 4 cycles.

So how do you find the entry point of the ring? It has been known before that the first encounter needs to cycle r times, and the distance traveled by the slow pointer when encountering is s=r. Let the total length of the linked list be L, the distance from the head of the linked list to the ring entrance is a, and the distance from the ring entrance to the meeting point is x, then L = a + r, and a = (Lxa) can be deduced, where Lxa is the distance from the encounter point to the ring entry point, that is, the distance a from the head of the linked list to the ring entry is equal to the distance from the encounter point to the ring entry.

s = r = a + x => a + x = (L-a) => a = L-x-a

Therefore, after judging that there is a ring in the linked list, the traversal starts from the encounter point and the head node respectively, and the two pointers take one step each time. When the two pointers are equal, it is the entry point of the ring.

/**

  • Find loop entry in linked list
    */
    ListNode *findLoopNode(ListNode *head)
    { ListNode *meetNode = listDetectLoop(head); if (!meetNode) return NULL;


    ListNode *headNode = head;
    while (meetNode != headNode) {
    meetNode = meetNode->next;
    headNode = headNode->next;
    }
    return meetNode;
    }

3.6 Linked List Simulates Addition

Question: ** Given two linked lists, the node value of each linked list is the number on each digit of the number, try to find the sum of the numbers represented by the two linked lists, and return the result in the form of a linked list. ****Assume that the two linked lists are list1 and list2 respectively, and the values ​​of each node of list1 are the numbers in the ones, tens and hundreds of the number 513 respectively. Similarly, each node value of the list2 is on the digits of the number 295. numbers. ** Then these two numbers add up to 808, so the output is output in the order from the ones to the hundreds, and the returned result list is as follows.

list1:  (3 -> 1 -> 5 -> NULL)list2:  (5 -> 9 -> 2 -> NULL)result: (8 -> 0 -> 8 -> NULL)

Solution: This topic is more interesting and requires more proficiency in linked list operations. We consider the process of adding two numbers, and add them sequentially from low to high. If there is a carry, mark the carry flag until the highest bit is terminated. Let the node of the current bit be current, then there are:

current->data = list1->data + list2->data + carry(其中carry为低位的进位,如果有进位为1,否则为0)

The non-recursive code is as follows:

/**

  • Linked list simulation addition - non-recursive solution
    */
    ListNode *listEnumarateAdd(ListNode *list1, ListNode *list2)
    { int carry = 0; ListNode *result = NULL;

    while (list1 || list2 || carry) {
    int value = carry;
    if (list1) {
    value += list1->value;
    list1 = list1->next;
    }

     if (list2) {
         value += list2->value;
         list2 = list2->next;
     }
    
     result = listAddNodeTail(result, value % 10);
     carry = ( value >= 10 ? 1: 0);
    

    }

    return result;
    }

The non-recursive implementation is as follows:

/**

  • Linked list analog addition - recursive solution
    */
    ListNode *listEnumarateAddRecursive(ListNode *list1, ListNode *list2, int carry)
    { if (!list1 && !list2 && carry==0) return NULL;

    int value = carry;
    if (list1)
    value += list1->value;

    if (list2)
    value += list2->value;

    ListNode *next1 = list1 ? list1->next : NULL;
    ListNode *next2 = list2 ? list2->next : NULL;
    ListNode *more = listEnumarateAddRecursive(next1, next2, (value >= 10 ? 1 : 0));
    ListNode *result = listNewNode(carry);
    result->value = value % 10;
    result->next = more;

    return result;
    }

3.7 Inserting a node into an ordered singly circular linked list

Question: ** Knowing an ordered one-way circular linked list, insert a node and keep the linked list in order, as shown in the following figure. **

image.png

Solution: Before solving this problem, let's look at a simplified version, which is to insert a node into an ordered and loop-free singly linked list, and still ensure its order. The code for this problem is believed to be familiar to most people, and it is generally considered in two cases:

  • If the original linked list is empty or the inserted node has the smallest value, the node is directly inserted and set as the head node.

  • If the original linked list is not empty, find the first node greater than the value of the node, and insert it before the node. If the inserted node has the largest value, it will be inserted at the end.

The implementation code is as follows:

/**

  • Simplified version - Ordered non-circular linked list insert node
    */
    ListNode *sortedListAddNode(ListNode *head, int value)
    { ListNode *node = listNewNode(value); if (!head || head->value >= value) { / /case 1 node->next = head; head = node; } else { //case 2 ListNode *current = head; while (current->next != NULL && current->next->value < value) current = current ->next; node->next = current->next; current->next = node; } return head; } Of ​​course, these two cases can also be handled together, using a secondary pointer. as follows:













/**

  • Simplified version - Ordered non-circular linked list insertion node (two cases are handled together)
    */
    void sortedListAddNodeUnify(ListNode **head, int value)
    { ListNode *node = listNewNode(value); ListNode **current = head; while ( (*current) && (*current)->value < value) { current = &((*current)->next); } node->next = *current; *current = node; }







Next, look at the situation of the circular linked list, in fact, it is necessary to consider the following two points:

1) prev->value ≤ value ≤ current->value:
inserted between prev and current.

2) The value is the maximum or minimum value:
insert it at the junction of the beginning and end, and reset the head value if it is the minimum value.

The code is as follows:
/**

  • 有序循环链表插入结点
    */
    ListNode *sortedLoopListAddNode(ListNode *head, int value)
    {
    ListNode *node = listNewNode(value);
    ListNode *current = head, *prev = NULL;
    do {
    prev = current;
    current = current->next;
    if (value >= prev->value && value <= current->value)
    break;
    } while (current != head);

    prev->next = node;
    node->next = current;

    if (current == head && value < current->value) // Determine whether to set the linked list head
    head = node;

    return head;
    }

3.8 Output the K-th node from the bottom of the linked list

Question: ** Given a simple singly linked list, output the Kth node from the bottom of the linked list. **

Solution 1: If it is the Kth node in the order, you can just traverse it without thinking too much. The novelty of this topic is that it is to output the Kth node from the bottom. An intuitive idea is that, assuming that the length of the linked list is L, the K-th node from the bottom is the L-K+1 node of the order number. If the length of the linked list is 3, the second last node is the second node in the order. In this way, it is necessary to traverse the linked list twice, once to find the length, and once to find the node.

/**

  • The last K node of the linked list - traversal twice algorithm
    */
    ListNode *getLastKthNodeTwice(ListNode *head, int k)
    { int len ​​= listLength(head); if (k > len) return NULL;


    ListNode *current = head;
    int i;
    for (i = 0; i < len-k; i++) //traverse the linked list to find the N-K+1th node
    current = current->next;

    return current;
    }

Solution 2: Of course, a better way is to traverse once and set two pointers p1 and p2. First, p1 and p2 both point to head, and then p2 takes k steps forward, so that there are k nodes between p1 and p2. Finally, p1 and p2 move forward at the same time, and when p2 reaches the end of the linked list, p1 just points to the Kth node from the bottom. code show as below:

/**

  • The last K node of the linked list - traversal algorithm
    */
    ListNode *getLastKthNodeOnce(ListNode *head, int k)
    { ListNode *p1, *p2; p1 = p2 = head;

    for(; k > 0; k–) { if (!p2) // the linked list is not long enough for K return NULL; p2 = p2->next; }



    while (p2) {
    p1 = p1->next;
    p2 = p2->next;
    }
    return p1;
    }

Guess you like

Origin blog.csdn.net/m0_54828003/article/details/126896318