preface

If data structures are the foundation of algorithms, arrays and linked lists are the foundation of data structures. Because complex array nodes such as stacks, stacks, pairs, graphs, etc. can basically be represented by arrays and linked lists, it is very important to master the basic operations of arrays and linked lists.

Today we will look at the basic operation of the linked list and its common solution ideas in the interview. This article will explain the core knowledge of the linked list from the following points

  1. What is a linked list and what are its advantages and disadvantages
  2. The representation and basic operation of linked lists
  3. In the interview linked list common solution idea – flip
  4. Interview linked list common solution ideas – fast or slow pointer

What is a linked list

I believe we have begun to can’t wait to use the linked list to solve the problem, but before we start we still want to review the definition of the linked list, as well as its advantages and disadvantages, sharpening the knife does not mistakenly chop firewood work!

Definition of linked list

A linked list is a discontinuous, nonsequential storage structure on a physical storage unit. It consists of nodes connected by Pointers, each of which contains data and Pointers.

The discontinuity of a list, the sequence of an array, let’s look at how integer arrays 1,2,3,4 are represented in memory. Okay



Can see every element of the array is adjacent to the distribution of continuously, this call continuity, at the same time due to the size of the array elements take up is the same, in Java int type fixed size is 4 bytes, so if the starting address of the array is 100, as a result of these elements in memory is continuous, adjacent to the distribution, size, too, It is easy to locate any element in the array. For example, the third element in the array starts at 100 + 2 * 4 = 108. This is called orderliness. The search time is O(1), which is very efficient!

So what are linked lists represented in memory

You can see the location of the distribution of each node in the continuous, between nodes and by the hands together, so if we want to find such as a value of 3 nodes, only through 1 from the beginning to the end node traversal search, if the element is less good, if too many elements, such as more than ten thousand), for every element, to find, from the very beginning The time complexity is O(n), which is not as big as O(1) in the array.

In addition to finding a linked list is not as good as an array, there is another advantage that makes arrays better than linked lists. Here we introduce the principle of program locality.

We know that THE CPU runs at a very fast speed. If the CPU has to fetch data from the memory for each operation, it is undoubtedly time-consuming. Therefore, there are often multiple levels of cache integrated between the CPU and memory. So if you can load the data in memory into the L1,L2, and L3 cache in advance, then the next CPU fetch can be directly fetched from these caches to speed up the CPU execution. Under what circumstances will the data in memory be loaded into the L1,L2, and L3 cache in advance? The answer is that when an element is used, elements near that element’s address are preloaded into the cache

1, 2, 3, and 4 are used in cache L1,L2, and L3. When the first element (1) is used, the CPU adds 2, 3, and 4 to cache L1,L2, and L3 in advance, because the CPU believes that 1 is used and the next element (2, 3, and 4) is likely to be used. If the CPU uses 2, 3, and 4 again, it can just fetch it from L1,L2, and L3, which can improve performance

Voice-over: If one CPU hour is one second, it takes 3 seconds to read from L1, 11 seconds from L2, 25 seconds from L3, and 1 minute and 40 seconds from memory, so program locality can greatly improve CPU performance

As for linked list, each node of linked list is randomly distributed in memory, but connected by Pointers, so the addresses of these nodes are not adjacent, naturally, it cannot use the principle of program locality to load in advance into L1,L2,L3 cache to improve program performance.

Voiceover: The principle of program locality is a very important principle in the computer. We do not expand it here. We are advised to consult relevant information for a detailed understanding

As mentioned above, the non-sequential, non-sequential nature of linked lists really puts them at a performance disadvantage compared to arrays. When should you use linked lists? Consider the following

  • Large memory space allocation

Due to the continuity of the array space, if you want to allocate 500 MB space for the array, the 500 MB space must be continuous and unused, so the array has strict requirements on the allocation of memory space. If there are too many memory fragments, allocating large contiguous space may lead to failure. Linked lists are more appropriate in this case because they are discontinuous.

  • Elements are frequently deleted and inserted

If it involves frequent delete and insert elements, use the list will be much more efficiently, for array, if you want to insert an element between the elements, need to move the rest of the elements one by one in the future (see chart), thought that the new elements make room (in the same way, if is to remove the need to be deleted element to the element after the move forward one by one), the efficiency is low.



(Insert 5 between 1 and 2, need to move 2, 3 and 4 back at the same time)

However, it is relatively easy to insert and delete the linked list. It only needs to change the position of the pointer, and other elements do not need to be moved (as shown in the figure: Taking insert as an example).

To sum up: if the data is mainly look-up, it seldom involves adding and deleting, choose array, if the data involves frequent insertion and deletion, or the space required to allocate elements is too large, tend to choose linked list.

Having said so many theories, I believe readers should have a more profound understanding of the difference between array and linked list, especially the principle of program locality, is not a lot of horizons _, if the interview asked the difference between array and linked list can answer the principle of program locality, will be a very big bright spot!

Now let’s look at the representation of linked lists and how to solve problems

It needs to be noted that some codes, such as printing linked lists, are not shown in the article due to space, I put all relevant codes in the article in github, if you need, you can visit my Github address: github.com/allentofigh… Download and run (wechat does not support external chain, it is recommended that you copy and then open the browser to download and run), all the code in the article has been implemented in Java and run through

Representation of linked lists

Because of the nature of linked lists (queries and deletes start at the beginning), we can only define the head node in the list, and we can also define an additional variable to represent the length of the list if we want to use it frequently.

It is important to note that the definition of this header is particular. Generally speaking, there are two ways to define a header. One is to directly use an element node as a header, as follows

One uses a virtual node as its head, often referred to as a sentinel, as follows

What’s the advantage of defining this sentinel, assuming we don’t define this sentinel, and we see how the list and the basic operations that add elements are defined

/** * nodes in the linked list, data represents the value of the node, and next is a reference to the next node */
class Node {
    int data;// Node array field, value
    Node next = null;// A reference to the next node
    public Node(int data) {
        this.data = data; }}/** ** linked list */
public class LinkedList {
    int length = 0; // The length of the list is optional
    Node head = null; / / head node

    public void addNode(int val) {
        if (head == null) {
            head = new Node(val);
        } else {
            Node tmp = head;
            while(tmp.next ! =null) {
                tmp = tmp.next;
            }
            tmp.next = newNode(val); }}}Copy the code

See what’s wrong? Look at the code below

There are two questions:

  1. Every element inserted has to be nulled against the head node. If a linked list has many elements that need to be inserted, it will need to be nulled many times, which is not very efficient
  2. The insertion logic of the header node is not uniform with that of other nodes (one node needs to be nulled and then inserted, and the other node does not need nulled), which is not so reasonable from the perspective of program logic (because nodes and nodes are level, adding logic should be the same).

Both problems can be solved if sentinel nodes are defined. Let’s look at the linked list definition using sentinel nodes

public class LinkedList {
    int length = 0; // The length of the list is optional
    Node head = new Node(0); // The sentinel node
    public void addNode(int val) {
        Node tmp = head;
        while(tmp.next ! =null) {
            tmp = tmp.next;
        }
        tmp.next = new Node(val);
        length++
    }
}
Copy the code

As you can see, the linked list that defines sentinel nodes is logically much clearer. Instead of nulling the head node every time an element is inserted, it also unifies the add logic for each node.

So the linked list that we’re going to use in the rest of the problem sets is going to be the sentinel node.

Having done so much preliminary preparation work, we finally started our dinner: linked list solution common routine – flip!

Linked list common solution routine – flip

A friendly

Given arrays 1, 2, 3, and 4, construct a linked list head –> 4—->3—->2—->1

See clearly, is the reverse construction of the linked list! We all know how to construct a list in reverse order, as shown in the figure (insert 1,2 as an example). The addNode method defined in the preceding code is called continuously for each element.

The head insertion method is relatively simple. Directly enter the code and complete the logic directly according to the above steps of the GIF as follows

public class LinkedList {
    int length = 0; // The length of the list is optional
    Node head = new Node(0); // The sentinel node
    
     / / head interpolation
    public void headInsert(int val) {
        // create a new node
        Node newNode = new Node(val);
        // 2. The new node points to the node after the head node
        newNode.next = head.next;
        // 3. The header points to the new node
        head.next = newNode;
        length++
    }

    public static void main(String[] args) {
        LinkedList linkedList = new LinkedList();
        int[] arr = {1.2.3.4};
        // create a linked list using a header insertion method
        for (int i = 0; i < arr.length; i++) {
            linkedList.headInsert(arr[i]);
        }
        // Print the list, will print 4-->3-->2-->1linkedList.printList(); }}Copy the code

A profound

Given a head pointer and a node pointer to a one-way linked list, define a function to delete that node in O(1).



As shown in the figure, how to delete 2 from the node given a value of 2.

We know that if you’re given a node and you want to delete its successor, it’s very easy to just point the pointer to the successor

As shown in the figure: given node 2, delete its successor node 3, and set the next pointer of node 2 to the successor node 4 of 3.

But given node 2, how do you delete node 2 itself? Notice they don’t say that you can’t change the value of node 2, so there’s a very clever way to do this: switch the cat! We find node 3 through node 2, and then assign the value of node 3 to node 2, and then the value of node 2 becomes 3. At this point, the problem turns into a relatively simple requirement like the one above, that is, remove node 3 according to node 2. Look at the picture

Note, however, that this technique only works if the node to be deleted is an intermediate node. If the node to be deleted is a final node, it is still necessary to find the predecessor of the final node and delete the final node

/** * deletes the specified node *@param deletedNode
 */
public void removeSelectedNode(Node deletedNode) {
    // If this node is the tail, we still need to traverse the preceding node to the tail, and then delete the tail
    if (deletedNode.next == null) {
        Node tmp = head;
        while(tmp.next ! = deletedNode) { tmp = tmp.next; }// Delete the parent node from the parent node
        tmp.next = null;
    } else {
        Node nextNode = deletedNode.next;
        // Assign the value of the successor of the deleted node to the deleted node
        deletedNode.data = nextNode.data;
        // Delete the nextNode
        deletedNode.next = nextNode.next;
        nextNode.next = null; }}Copy the code

Getting started to advanced: list flipping

Next we will focus on the flip of the linked list, the flip of the linked list can derive a lot of deformation, is a very popular test in the interview, basically test the flip of the linked list! So master the flip of the linked list is a required course!

Given a list head — >4 — >3 — >2 — >1, flip it to head — >1 — >2 — >3 — >4. Since flipping a list is so common and important, we will explain in detail how to use recursive and non-recursive methods to solve the problem

  • Recursive flip

About recursive wrote three articles before, if not read before, it is strongly recommended that click here to see, summarizes the common problem solving recursive routines, gives the recursive common four steps of problem solving, if watching for the following recursion formula in solving problems will be more profound, there is no need to do, we directly set of recursive way:

First we need to see if the flipped list conforms to recursion: problems can be broken down into subproblems that have the same idea of solution, and subproblems… , until the final subproblem can no longer be decomposed.

To flip the head — >4 — >3 — >2 — >1 list, analyze 4 — >3 — >2 — >1 without considering the head node. After careful observation, we find that we only need to flip 3 — >2 — >1 to 3<—-2<—-1, and then point 3 to 4 (as shown below).

Figure: There are three main steps to flip a linked list

As long as the function of the flip function is defined according to the above steps, since the subproblem has the same solution idea as the original problem, the subproblem after splitting can continuously call the flip function to achieve the purpose.

Note that step 1 above has reduced the size of the problem (as shown below) from flipping the entire list to flipping only part of the list! The problem and the sub-problem are from a node start to flip, with the same solution, in addition, when reduced to only flip a node, is obviously the termination condition, in line with the recursion condition! 3 — >2 — >1, 2 — >1 continue to call the recursive function defined!

Since the condition of recursion is met, we can use the recursion four-step to solve the problem. Don’t forget this step)

From the above analysis, the function of this recursive function is obviously to reverse the list at the beginning of a node, and then return the new header

/** * reverses the list of nodes that start */
public Node invertLinkedList(Node node) {}Copy the code

2. Search for recursion formula The steps of flipping linked lists have been drawn in detail above. The recursion steps are summarized as follows

  • Invert (node->next) invert(node->next) invert(node->next) invert(node->next) invert(node->next) invert(node->next) invert(node->next) invert(node->next) invert(node->next) invert(node->next) invert(node->next) invert(node->next) invert(node->next) invert
  • The next node (3) of node points to node, and the next node of node is set to null (to avoid a loop), which becomes 4< — 3< — 2< — 1
  • Return the new head, because the new head has changed from 4 to 1, and the head needs to be reset

InvertLinkedList = invertLinkedList = invertLinkedList = invertLinkedList = invertLinkedList = invertLinkedList

/** * Recursively reverses the list of nodes that start */
public Node invertLinkedList(Node node) {
    if (node.next == null) {
        return node;
    }

    // Step 1: Flip the linked list behind node
    Node newHead = invertLinkedList(node.next);

    // Step 2: Set the successor node of the original node to node (4), and set the successor node to null (to prevent ring formation).
    node.next.next = node;
    node.next = null;

    // Step 3: Return the flipped header
    return newHead;
}

public static void main(String[] args) {
    LinkedList linkedList = new LinkedList();
    int[] arr = {4.3.2.1};
    for (int i = 0; i < arr.length; i++) {
        linkedList.addNode(arr[i]);
    }
    Node newHead = linkedList.invertLinkedList(linkedList.head.next);
    // Do not forget to set the successor of the header after the flip!
    linkedList.head.next = newHead;
    linkedList.printList();      Print 1,2,3,4
}
Copy the code

Voiceover: Do not forget to reset the following node of head.

InvertLinkedList = invertLinkedList; invertLinkedList = invertLinkedList; invertLinkedList = invertLinkedList; invertLinkedList = invertLinkedList; invertLinkedList = invertLinkedList; I pressed n stacks, so the space complexity is order n.

Recursive function to understand must be from the function, from the function of the function, the recursive functions defined clearly understand, defined later, due to problems with split up the solution of the subproblem with the same train of thought, so the child the functions can be defined as long as the last call, never were unfolding subproblems, which is recursive common pitfalls. Take a closer look at the functions of the function, in fact, as shown below. (Look at the code, is it clear to understand _)

  • Non-recursive flipped list (iterative solution)

We know that recursion is more likely to cause stack overflow, so if there are other algorithms with similar or better time/space complexity, the non-recursive solution should be preferred. Let’s see how to use iteration to flip linked lists. The main idea is as follows

Step 1: Define two nodes: pre, cur, where cur is the successor node of Pre. If it is defined for the first time, the pointer pointing to cur of Pre needs to be removed; otherwise, because the linked list will be flipped later, cur will point to Pre, so a loop is made (as follows), which needs to be noted

Step 2: Now that you know cur and pre, it’s easy to flip. Just point cur to Pre, then set cur to pre, and the successor to cur to cur. Repeat the process all the way forward

Note: As with recursion, the successor of head changes from 4 to 1 after iteration. Remember to reset this.

Know the idea of the problem, the implementation of the code is much easier, directly on the code

/** * iterates over */
public void iterationInvertLinkedList(a) {
    / / step 1
    Node pre = head.next;
    Node cur = pre.next;
    pre.next = null;

    while(cur ! =null) {
        / * * * it is important to note that in cur to pre must keep cur subsequent nodes first, before or after cur pointing to the pre will never find you can't get to the subsequent nodes * cur subsequent nodes to flip the * /
        Node next = cur.next;
        cur.next = pre;
        pre = cur;
        cur = next;
    }
    // Pre is the successor of the header
    head.next = pre;
}
Copy the code

Using iterative ideas to do because of the cycle n times, obviously the time complexity is O(n), and because there is no extra space to use, also did not call recursive function like recursion constantly stack, so the space complexity is O(1), compared to recursion, obviously should use the way of iteration to deal with!

It took a lot of effort to figure out what flipped lists are, and if you look at the next couple of variations of flipped lists, you’ll see that it was worth the effort to talk about flipped lists.

Next, let’s look at the transformations of list flipping

Given the head of a linked list and two integers from and to, reverse the from and to part of the list. For example, given the following list, from = 2, to = 4 head – >5 – >4 – >3 – >2 – >1 Invert the list to become head – >5 – >2 – >3 – >4 – >1

With the idea of flipping the whole list before, it is relatively easy to flip part of the list now. The main steps are as follows:

  1. Select from-1, from-1, from-1, from-1, from-1, from-1, from-1, from-1, from-1, from-1, from-1 The from and to nodes may also pass the tail, which are not flipped.
  2. Invert nodes from to
  3. Point from-1 to to, and point FROM to + 1

Knowing the above ideas, the code is simple, according to the above steps 1,2,3 implementation, notes are also written in detail, look at the following code (from to to the node of the inversion we use iterative inversion, of course, the use of recursion is also possible, limited to space does not expand, we can try).

/** * iterate over nodes from to */
public void iterationInvertLinkedList(int fromIndex, int toIndex) throws Exception {

    Node fromPre = null;            / / the from 1 nodes
    Node from = null;               / / the from nodes
    Node to = null;                 / / to node
    Node toNext = null;             / / to + 1 nodes

    // Select from-1, from, to, to+1
    Node tmp = head.next;
    int curIndex = 1;      // The index of the header is 1
    while(tmp ! =null) {
        if (curIndex == fromIndex-1) {
            fromPre = tmp;
        } else if (curIndex == fromIndex) {
            from = tmp;
        } else if (curIndex == toIndex) {
            to = tmp;
        } else if (curIndex == toIndex+1) {
            toNext = tmp;
        }
        tmp = tmp.next;
        curIndex++;
    }

    if (from == null || to == null) {
        // If from or to exceeds the end node, it is not flipped
        throw new Exception("Not eligible");
    }

    // Step 2: The following uses loop iteration to reverse nodes from from to
    Node pre = from;
    Node cur = pre.next;
    while(cur ! = toNext) { Node next = cur.next; cur.next = pre; pre = cur; cur = next; }// Step 3: Point from-1 to TO (if you start with head's successor, reset head's successor), and point from to to + 1
    if(fromPre ! =null) {
        fromPre.next = to;
    } else {
        head.next = to;
    }
    from.next = toNext;
}
Copy the code

Deformation problem 2: give a linked list, each k nodes in a group of flipped, and return the flipped list. K is a positive integer whose value is less than or equal to the length of the list. If the total number of nodes is not an integer multiple of k, the last remaining nodes are kept in the same order.

Example: given the linked list: the head – > 1 – > 2 – > 3 – > when k = 2, 4 – > 5, shall return: the head – > 4 – > 2 – > 1 – > 3 – > 5 when k = 3, shall return: the head – > 3 – > 2 – > 1 – > 4 – > 5:

  • Your algorithm can only use extra space of constants.
  • You can’t just change the values inside a node, you need to actually swap nodes.

This problem is the original problem of LeetCode, which is at the hard level. If you understand this problem, it should be basically no problem to flip the linked list. With the previous foundation of flipping the linked list, I believe this problem is not difficult.

As long as we can figure out how to flip a list of K nodes, we’re done.

Next, let’s take the following linked list as an example



Let’s see how to flip lists of three (k = 3 in this case)

  • First of all, we need to record three successive nodes of this section of the linked list, defined as startKPre, and then define a step, which is traversed twice from the head node (1) of this section to find the start and end nodes of this section of the linked list, as shown below

  • After startK and endK are found, the list of startK and endK is flipped according to the previous iterative flipping method

  • StartKPre points to endK and startK points to endKNext, which completes a set of K nodes.

If you know how to flip a group of K nodes, then you just need to flip the linked list of k nodes repeatedly

/** * flip the linked list in groups of k *@param k
 */
public void iterationInvertLinkedListEveryK(int k) {
    Node tmp = head.next;
    int step = 0;               // count to find the first and last nodes

    Node startK = null;         // a list of k headers
    Node startKPre = head;      // there are k precursors to a set of list headers
    Node endK;                  // a list of k endpoints
    while(tmp ! =null) {
        // the next node of TMP, because the subsequent nodes of TMP will change due to rollover
        Node tmpNext = tmp.next;
        if (step == 0) {
            // k heads of a list interval
            startK = tmp;
            step++;
        } else if (step == k-1) {
            // The endK of a list interval is found, and the list is iterated over
            endK = tmp;
            Node pre = startK;
            Node cur = startK.next;
            if (cur == null) {
                break;
            }
            Node endKNext = endK.next;
            while(cur ! = endKNext) { Node next = cur.next; cur.next = pre; pre = cur; cur = next; }EndK and startK are the first and last nodes in a list of k
            startKPre.next = endK;
            startK.next = endKNext;

            // Start the next group of k flips
            startKPre = startK;
            step = 0;
        } else{ step++; } tmp = tmpNext; }}Copy the code

So what is the time complexity? If I loop through the list n times, and I flip it once for every k nodes, I can think of it as a total of n flips, so the time complexity is O(2n), minus the constant term, which is O(n). Note: The time complexity of the list is O(k * n). In fact, the list is not flipped every time the list circulates, but only for every k nodes of the loop

Deformation 3: Deformation 2 is for the sequence of K group flip, so how to reverse k group flip

For example, given the following linked list, head – >1 – >2 – >3 – >4 – >5 inverts k in a group, and the linked list becomes head – >1 – >3 – >2 – >5 – >4 when k = 2

This question is a bytedance interview question, it is really quite abnormal, k sequence of a group of flip has been classified as hard level, reverse k group of flip is super hard level, but in fact, with the previous knowledge, it should not be difficult, just a little deformation, just do the following deformation of the linked list

Every step of the code is actually using the function we implemented before, so every step we did before is foreshadowed oh! To solve the ultimate bytedance interview question!

/** * invert the linked list in groups of k *@param k
 */
public void reverseIterationInvertLinkedListEveryK(int k) {
    // Flip the list first
    iterationInvertLinkedList();
    // invert linked lists in groups of k
    iterationInvertLinkedListEveryK(k);
    // Flip the list again
    iterationInvertLinkedList();
}
Copy the code

Thus, it is very important to master basic list flipping! The problem is to do the corresponding deformation on this basis

Linked list problem solving tool – fast and slow pointer

Fast and slow pointer in the interview probability is also very large, but also be sure to master a key point, the following summarizes the market common fast and slow pointer problem solving skills, I believe that after reading this kind of problem can be handy. The following details how to use fast and slow Pointers to solve the following two categories of problems

  1. Find/delete the KTH node
  2. Related solutions to linked list ring problems

Find/delete the KTH node

One of the tests

LeetCode 876: Given a non-empty single linked list with a head, return the middle of the list. If there are two intermediate nodes, the second intermediate node is returned.

Method a

In order to know the middle node of the list, we need to know the length of the list. Remember that the sentinel node can store the length of the list, so that we can find the middle node by traversing the length of the list directly from the next node of the head / 2 times. Why is the middle node a linked list of length over 2? Let’s analyze it carefully

  1. If the list length is odd: head — >1 — >2 — >3 — >4 — >5, start at 1 and iterate 5/2 = 2 times until you reach 3, which is indeed an intermediate node
  2. If the list length is even: head — >1 — >2 — >3 — >4 — >5 — >6, start at 1 and iterate 6/2 = 3 times to 4, 4 is really the second node of the intermediate node

Voice-over: draw more pictures, for example, can see the essence of things!

This is the easiest way to write the code

public Node findMiddleNode(a) {
    Node tmp = head.next;
    int middleLength = length / 2;
    while (middleLength > 0) {
        tmp = tmp.next;
        middleLength--;
    }
    return tmp;
}
Copy the code

Method 2

If there is no length defined in the sentinel node, you need to iterate through the list to get the length of the list (defined as length), and then iterate over the length / 2 from the beginning node for the intermediate node

public Node findMiddleNodeWithoutHead(a) {
    Node tmp = head.next;
    int length = 1;
    // Select the length of the list
    while(tmp.next ! =null) {
        tmp = tmp.next;
        length++;
    }

    // Go through the list again to get the middle node
    tmp = head.next;
    int middleLength = length / 2;
    while (middleLength > 0) {
        tmp = tmp.next;
        middleLength--;
    }
    return tmp;
}
Copy the code

Method three

Solution two is not so efficient because you have to go through the list twice, but can you just go through the list once and get the middle node?

The slow pointer goes one step, and the fast pointer goes two steps. 3. Repeat step 2 repeatedly, when to stop, depending on whether the length of the list is odd or even

  • If the list length is odd,slow is the middle node when fast.next = null

  • If the list length is even,slow is the middle node when fast = null



    According to the above analysis, when fast = null or fast. Next = null, slow node is the intermediate node we require at this time. Otherwise, step 2 will be repeated constantly, and the code implementation will be simple after knowing the idea
/** * find the middle node * using the fast and slow pointer lookup@return* /
public Node findMiddleNodeWithSlowFastPointer(a) {
    Node slow = head.next;
    Node fast = head.next;
    while(fast ! =null&& fast.next ! =null) {
        // Fast pointer takes two steps
        fast = fast.next.next;
        // Slow the pointer one step
        slow = slow.next;
    }
    // Slow is the sentry node
    return slow;
}
Copy the code

Now, with that in mind, let’s make it a little harder. Let’s look at the following problem

Input a linked list, output the penultimate KTH node in the list. For example, the linked list is head — >1 — >2 — >3 — >4 — >5. Find the third to last node (the node with the value of 3)

Analysis: We know that if the first k node demand order is relatively simple, since the head traversing k times can, if the requirements of reverse k node, the conventional approach is to first order traverse the list again, to get the chain length of the table, and then to traverse the list length – k times can, so to traverse the list twice, not so efficient, How do I go through it once, or do I use the fast and slow pointer solution we talked about

  1. Start by having both fast and slow Pointers point to the next node of head
  2. The fast pointer moves forward k minus 1, to the KTH node first
  3. The fast and slow Pointers take a step backwards at the same time, and repeat the process until the fast Pointers reach the end node, at which point the slow node is the KTH node we are looking for in reverse order

Note: The critical case needs to be noted: k is greater than the length of the linked list, such an exception should be thrown

public Node findKthToTail(int k) throws Exception {
    Node slow = head.next;
    Node fast = head.next;

    // Move the fast pointer to the KTH node
    int tmpK = k - 1;
    while (tmpK > 0&& fast ! =null) {
        fast = fast.next;
        tmpK--;
    }
    // Critical condition: k is greater than the list length
    if (fast == null) {
        throw new Exception("There is no abnormality at node K.");
    }
    // Slow and FAST move backwards at the same time until FAST reaches the end
    while(fast.next ! =null) {
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}
Copy the code

Now that we know how to find the KTH node in reverse order, what about this problem

Given a single linked list, design an algorithm to rotate the list K positions to the right. Example: given the head – > 1 – > 2 – > 3 – > 4 – > 5 – > NULL, K = 3, right hand is the head after 4 – > 5 – > 3 – > – > 1 – > 2 – > NULL

This problem is actually a deformation of the inverted K position, the main idea is as follows

  • Find the penultimate K+1 node, and the next node is the penultimate K node
  • Set the next-to-last node of K+1 to null
  • The successor of head is set as the penultimate KTH node above, and the successor of the original tail is set as the successor of the original head

public void reversedKthToTail(int k) throws Exception {
    // Directly call the implemented method to find k nodes in reverse order, here is k+1
    Node KPreNode = findKthToTail(k+1);
    // the KTH node
    Node kNode = KPreNode.next;
    Node headNext = head.next;

    KPreNode.next = null;
    head.next = kNode;

    // find the end node
    Node tmp = kNode;
    while(tmp.next ! =null) {
        tmp = tmp.next;
    }
    // Set the successor of the end node to the successor of the original head
    tmp.next = headNext;
}
Copy the code

With the foundation of the above two questions, I believe that the following question is not difficult, limited to space, not to expand here, you can try

Enter a linked list and delete the last KTH node in the list

The second test

Determining whether two singly linked lists intersect and finding the first intersection requires space complexity O(1). As shown, if two lists intersect, 5 is the first point where the two lists intersect

Voice-over: If there is no limit of space complexity O(1), there are actually several solutions, one is to traverse linked list 1, put all the nodes of linked list 1 into a set, traverse linked list 2 again, each time a node is traversed, determine whether the node is in the set, if the node is found in the set, This node is the first node that the linked list intersects

Analysis: First of all, due to the nature of the linked list itself, if a node intersects, then all nodes after the intersecting node are shared by the two linked lists, that is to say, the length of the two linked lists is mainly different from the length of the node before the intersecting node, so we have the following ideas

1. If the length of the linked list is not defined, we first traverse the two linked lists to get two linked list lengths, assuming L1, L2 (L1 >= L2), define P1, P2 pointer to each head node of the linked list, and then P1 goes l1-L2 first. This step ensures that the Pointers to P1 and P2 are as close as the intersecting nodes (if any).

2. Then P1 and P2 are traversed backwards, one step at a time, to judge whether the corresponding nodes are equal. If they are equal, they are the intersecting nodes of the two linked lists

public static Node detectCommonNode(LinkedList list1, LinkedList list2) {
    int length1 = 0;        // The length of list1
    int length2 = 0;        // the length of the list2

    Node p1 = list1.head;
    Node p2 = list2.head;

    while(p1.next ! =null) {
        length1++;
        p1 = p1.next;
    }

    while(p2.next ! =null) {
        length2++;
        p2 = p2.next;
    }

    p1 = list1.head;
    p2 = list2.head;

    / / p1 and p2 forward | length1 - length2 | step
    if (length1 >= length2) {
        int diffLength = length1-length2;
        while (diffLength > 0) { p1 = p1.next; diffLength--; }}else {
        int diffLength = length2-length1;
        while (diffLength > 0) { p2 = p2.next; diffLength--; }}// if p1 and p2 are equal, they are the first intersecting node
    while(p1 ! =null&& p2.next ! =null) {
        p1 = p1.next;
        p2 = p2.next;
        if (p1.data == p2.data) {
            // return p1 or p2
            returnp1; }}// No intersection node, return null pointer
    return null;
}
Copy the code

The advanced

Now let’s look at how to use the fast or slow pointer to determine whether a linked list has a ring. This is the most common use of the fast or slow pointer

Check whether the linked list has a ring, if so, find the entry position of the ring (2 in the figure below), requiring space complexity O(1).

First, we need to see what can you find if there is a ring chain table, if from the head node traversal, the traversing the pointer will be in a circle of the ring above, so we can define how a pointer, pointer to slow step, quick pointer take two steps, because finally in the process of traverse speed pointer will have been around in a circle, And the step length of the fast and slow Pointers is different each time, so they will meet in the process of constantly circling in the inside, just like a 5000-meter race, one of the fast runners and one of the fast runners will catch up with the slow runners (i.e. the ring).

Don’t get it? So let’s just do a quick proof

1, if the fast pointer is one node away from the slow pointer, it is traversed again, the slow pointer takes one step, the fast pointer takes two steps, and meets

2. If the fast pointer differs from the slow pointer by two nodes, it is traversed again. The slow pointer takes one step, the fast pointer takes two steps, and the difference is one node

3, if the difference between the fast pointer and the slow pointer is N nodes (N greater than 2), then the next traversal because the slow pointer takes one step, the fast pointer takes two steps, so the difference of N+1-2 = n-1 nodes, found that the difference of nodes from N to n-1, reduced! When N is reduced to 2, that is, it turns to the situation of step 2 above. Thus, if there is a ring, the fast and slow Pointers will meet!

** VOiceover: If the slow pointer takes one step and the fast pointer takes more than two steps instead of two, what will happen? We can consider **

/** * if there is a ring, return the fast and slow pointer to the node, otherwise return the null pointer */
public Node detectCrossNode(a) {
    Node slow = head;
    Node fast = head;

    while(fast ! =null&& fast.next ! =null) {
        fast = fast.next.next;
        slow = slow.next;
        
        if (fast == null) {
            return null;
        }

        if (slow == fast) {
            returnslow; }}return null;
}
Copy the code

Determine why a ring should return the intersecting node instead of returning true or false. Because there is also a requirement in the question, judging the entrance of the ring, just for this preparation, let’s see how to find the entrance of the ring, need some analytical skills

Assuming that 7 in the figure above is the node where fast and slow Pointers meet, it is not difficult to see that the slow pointer takes L+S steps, while the fast pointer moves faster than the slow pointer. In addition to taking L+S steps, the fast pointer also goes around n times in the ring, so the fast pointer takes L+S+nR steps (R is the length of the ring in the figure). In addition, we know that every time it goes through, The slow pointer takes one step and the fast pointer takes two steps, so the fast pointer goes twice as far as the slow pointer, that is, 2 (L+S) = L+S+nR, that is, L+S = nR

  • When n = 1, then L+S = R, then the distance from encounter point 7 to ring entry point 2 is r-S = L, which is just the ring entry node, and the distance between head and ring entry point 2 is also L, so as long as a pointer is defined at the head node, Another pointer is defined at the encounter point (7), and the two Pointers are traversed at the same time. Each step must meet at the entry position 2 of the ring
  • When n is greater than 1, L + S = nR, so L = nR-S, nR-S. If you set one pointer to head (defined as P1) and set another pointer to 7 (defined as P2) and iterate over and over again, when P2 goes nR-S (the entry position of the ring), P1 also happens to go here (p1 takes nR-S = L step at the entrance of the ring), that is, the two meet!

To find the entry node, define two Pointers, one pointing to head and one pointing to the intersection of the fast and slow points, and then both Pointers iterate (one step at the same time). When they point to the same node, they are the entry node of the ring

public Node getRingEntryNode(a) {
        // Get the fast and slow Pointers to meet the node
        Node crossNode = detectCrossNode();

        // If there is no encounter point, there is no ring
        if (crossNode == null) {
            return null;
        }

        // Define two Pointers, one to the header and one to the intersection
        Node tmp1 = head;
        Node tmp2 = crossNode;

        // The point where the two meet is the entry node of the ring
        while(tmp1.data ! = tmp2.data) { tmp1 = tmp1.next; tmp2 = tmp2.next; }return tmp1;
    }
Copy the code

Question: knowing the entry node of the ring, how to find the length of the ring?

conclusion

In detail in this paper, the essential difference between a linked list and array believe that everybody on the difference between the two should have a more profound understanding of, especially the program locality principle, I believe you should be able to shine at the moment, then turn through the list of unit 1 introduction, believe that after the list turn what should not be too difficult for us, After that, it introduces another important problem solving skill of linked list: fast and slow pointer, these two kinds of high frequency questions are interviews, we must master! It is suggested that you implement the code in person, so that the impression will be more profound! Some ideas seem to be such a thing, but the real operation will still have a lot of pits, paper come zhongjue shallow, must know this to practice!