One-way list inversion

Keywords: Junit Java Google Mobile

The inversion of one-way list is an interview question with very high frequency. I once suffered a loss on this question, but it doesn't matter. Let's study it.

First of all, the one-way characteristics of linked list:

1. Only forward can a node be found.

2. The next node address of the last node (which we usually call the next pointer) is null.

3. A linked list may be an empty list, that is, its first node element is null.

4. Link lists usually have several methods such as add, insert, delete, contains, etc.

The inversion of one-way linked list is a very challenging problem. I checked a lot of information on the Internet. The simplest is recursive inversion, but I personally think this method is not good because every recursive invocation of methods or functions needs to open up a new function stack. The system performance overhead is too large, and if the linked list is long, it may lead to stack overflow, so we should use loops. The way we find the loops on the Internet is too complicated. Let's make it simple:

First look at the definition of linked list (MyLinkedList.java):

import com.google.gson.Gson;

/**
 * Singly linked list
 * Created by lidawei on 2017/2/10.
 */
public class MyLinkedList<T> {
    private Node<T> root;
    private int position;
    private int size;

    /**
     * constructor
     */
    public MyLinkedList() {}

    /**
     * Additive elements
     * @param data Specified node data
     */
    public void add(T data) {
        Node<T> node = new Node<>();
        node.data = data;
        if (root == null) {
            root = node;
            size++;
            return;
        }

        Node<T> current = root;
        while (current.next != null) {
            current = current.next;
        }
        current.next = node;
        size++;
    }

    /**
     * Insertion element
     * @param index Inserted into the specified location, the data in this location moves backwards, and the index position starts at 0.
     * @param data Specified node data
     */
    public void insert(int index, T data) {
        checkRange(index);
        Node<T> previous = root, current = root;
        while (index != position) {
            position++;
            previous = current;
            current = current.next;
        }

        Node<T> obj = new Node<>();
        obj.data = data;

        previous.next = obj;
        obj.next = current;
        size++;
    }

    /**
     * Delete elements
     * @param index Specify the index location, where the data is deleted and the subsequent elements are filled forward in turn
     */
    public void delete(int index) {
        checkRange(index);
        Node<T> previous = root, current = root;
        int pos = 0;
        while (current.next != null) {
            if (pos == index) {
                break;
            }
            pos++;
            previous = current;
            current = current.next;
        }
        previous.next = current.next;
        size--;
    }

    /**
     * Does it contain the specified data?
     * @param data Node data
     * @return Return true when included, otherwise false
     */
    public boolean contains(T data) {
        Node<T> current = root;
        while (current != null) {
            if (current.data.equals(data))
                return true;
            else
                current = current.next;
        }
        return false;
    }

    /**
     * Get the list length
     * @return Returns an integer representing the number of linked list nodes
     */
    public int size() {
        return size;
    }

    private void checkRange(int index) {
        if (index < 0 ||index >= size) {
            throw new IllegalArgumentException("Index values have crossed the border!");
        }
    }

    /**
     * Inversion Link List, this operation is in situ inversion, that is, will modify the existing link structure, will not create a new list.
     */
    public void reverse() {
        if (root == null || root.next == null)   return;

        Node<T> previous = root;
        Node<T> current = previous.next;
        Node<T> temp;
        while (current != null) {
            temp = current.next;
            current.next = previous;
            previous = current;
            current = temp;
        }
        root.next = null;
        root = previous;
    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer(size * 64);
        sb.append("[");
        Node<T> current = root;
        Gson gson = new Gson();
        while (current != null) {
            sb.append(gson.toJson(current.data));
            sb.append(",");
            current = current.next;
        }
        int pos = sb.lastIndexOf(",");
        if (pos != -1) {
            sb.deleteCharAt(pos);
        }
        sb.append("]");

        return sb.toString();
    }

    private class Node<T> {
        Node next;
        T data;

        @Override
        public String toString() {
            Gson gson = new Gson();
            return gson.toJson(this);
        }
    }
}
I have written the reverse method in the definition. The implementation of this method is very simple. If you understand the following figure, you will understand it completely.


Be careful:

In the graph, the following nodes are saved, the update pointer and the mobile operation pointer are all in the loop, and the condition of the loop is of course current!= null.


Let's go through the diagrams and the code, and you'll see right away. Okay, here's my test case (which covers all the methods):

import org.junit.Assert;
import org.junit.Test;

public class MyLinkedListTest {
    @Test
    public void givenCharactorWhenInsertThenIncreaseOne() {
        MyLinkedList<Character> list = new MyLinkedList<>();
        list.add('a');
        list.add('b');
        list.add('c');
        System.out.println(list);

        list.insert(2, 'e');

        System.out.println(list);
    }

    @Test
    public void givenIntegerWhenFindThenRight() {
        MyLinkedList<Integer> list = new MyLinkedList<>();
        list.add(11);
        list.add(22);
        Assert.assertTrue(list.contains(11));
        Assert.assertTrue(list.contains(22));
    }

    @Test
    public void givenIntegerWhenDeleteThenShrinkOne() {
        MyLinkedList<Integer> list = new MyLinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        System.out.println(list);

        list.delete(2);

        System.out.println(list);
    }

    @Test
    public void givenDoubleWhenDeleteThenCheckSize() {
        MyLinkedList<Double> list = new MyLinkedList<>();
        list.add(1.0);
        list.add(2.1);
        list.add(3.2);
        System.out.println(list);

        Assert.assertTrue(list.size() == 3);
    }

    @Test
    public void givenIntegerWhenReverseThenRight() {
        MyLinkedList<Integer> list = new MyLinkedList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        System.out.println(list);
        System.out.println("Reverse...");

        list.reverse();

        System.out.println(list);
    }

    @Test
    public void givenStringWhenReverseThenRight() {
        MyLinkedList<String> list = new MyLinkedList<>();
        list.add("abc");
        list.add("def");
        list.add("hij");
        list.add("klmn");
        list.add("zzz");
        System.out.println(list);
        System.out.println("Reverse...");

        list.reverse();

        System.out.println(list);
    }
}

Operation results:



Ha-ha... Is it simple?!!!

Posted by baranwalpk on Tue, 26 Mar 2019 12:12:29 -0700