q206_反转链表

思路1,双指针法,或者叫迭代法

动画题解
代码

public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while (cur != null) {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }

复杂度分析
时间复杂度:O(n),假设 n 是列表的长度,时间复杂度是 O(n)。
空间复杂度:O(1)。

思路2,递归法

图解
代码

    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode listNode = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return listNode;
    }

递归代码中,最不好理解的是

 head.next.next = head;

查看以上的图解过程,当 1(head) 后面的表完成反转后,2 同时被 1 和 3指向,这是要做的是把2指向1,把1指向null。结合图解,多看几遍,还是比较容易理解的。

复杂度分析
时间复杂度:O(n),假设 n 是列表的长度,那么时间复杂度为 O(n)。
空间复杂度:O(n),由于使用递归,将会使用隐式栈空间。递归深度可能会达到 n 层。

q2_两数相加

思路1,递归

从链表第一位开始相加,并增加一个boolean参数,表示是否加1。当两个链表都为空时,结束递归。
代码

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        return addTwoNumbers2(l1, l2, false);
    }

    public ListNode addTwoNumbers2(ListNode l1, ListNode l2, boolean add1) {
        int val;
        if (l1 == null && l2 == null) {
            if (add1) {
                return new ListNode(1);
            } else {
                return null;
            }
        } else if (l1 == null) {
            val = l2.val + (add1 ? 1 : 0);
        } else if (l2 == null) {
            val = l1.val + (add1 ? 1 : 0);
        } else {
            val = l1.val + l2.val + (add1 ? 1 : 0);
        }
        add1 = val >= 10;
        val = val % 10;
        ListNode res = new ListNode(val);
        res.next = addTwoNumbers2(l1 == null ? null : l1.next, l2 == null ? null : l2.next, add1);
        return res;
    }

执行结果

思路2

迭代,预先指针
过程和图解
小技巧:对于链表问题,返回结果为头结点时,通常需要先初始化一个预先指针 pre,该指针的下一个节点指向真正的头结点head。使用预先指针的目的在于链表初始化时无可用节点值,而且链表构造过程需要指针移动,进而会导致头指针丢失,无法返回结果。
代码

    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode pre = new ListNode(0);
        ListNode tem = pre;
        int tag = 0;
        while (l1 != null || l2 != null) {
            int a = l1 == null ? 0 : l1.val;
            int b = l2 == null ? 0 : l2.val;
            int sum = a + b + tag;
            tag = sum / 10;
            sum %= 10;

            tem.next = new ListNode(sum);
            tem = tem.next;

            if (l1 != null)
                l1 = l1.next;
            if (l2 != null)
                l2 = l2.next;
        }
        if (tag > 0) {
            tem.next = new ListNode(tag);
        }
        return pre.next;
    }

q148_排序链表

思路1

把链表转成list,list排序后,生成新的链表
代码

    public ListNode sortList(ListNode head) {
        List<Integer> list = new ArrayList<>();
        while (head != null) {
            list.add(head.val);
            head = head.next;
        }
        Collections.sort(list);
        ListNode last = null;
        for (int i = list.size() - 1; i >= 0; i--) {
            int v = list.get(i);
            ListNode listNode = new ListNode(v);
            listNode.next = last;
            last = listNode;
        }
        return last;
    }

需要额外的空间

思路2

归并排序

q19_删除链表的倒数第N个节点

思路1

双指针,预先指针
代码

    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode pre = new ListNode(0);
        pre.next = head;
        ListNode right = pre;
        ListNode left = pre;
        for (int i = 0; i <= n; i++) {
            right = right.next;
        }
        while (right != null) {
            right = right.next;
            left = left.next;
        }
        left.next = left.next.next;
        return pre.next;
    }

q61_旋转链表

思路

把单链表转成环,然后再在合适的地方解环
代码

public ListNode rotateRight(ListNode head, int k) {
        if (head == null || head.next == null || k == 0) {
            return head;
        }

        ListNode t = head;
        int len = 1;
        while (t.next != null) {
            t = t.next;
            len++;
        }
        t.next = head;

        k = len - k % len;
        ListNode t2 = head;//新的链尾,t2的next才是新的head,所以要-1
        for (int i = 0; i < k - 1; i++) {
            t2 = t2.next;
        }
        ListNode ans = t2.next;
        t2.next = null;
        return ans;
    }

需要注意的是解环的位置,应该是长度len减去 k%len

q138_复制带随机指针的链表

思路1

map+递归
从链表头开始复制,用map<oldnode,newnode>存数据,当需要复制的节点不存在,构建新的,当节点已经存在,从map获取
代码

public Node copyRandomList(Node head) {
        if (head == null) {
            return null;
        }
        if (map.containsKey(head)) {
            return map.get(head);
        }

        Node node = new Node(head.val);
        map.put(head, node);
        node.next = copyRandomList(head.next);
        node.random = copyRandomList(head.random);
        return node;
    }

思路2

详见图解解法1
优秀图解
代码

 public Node copyRandomList2(Node head) {
        if (head == null) {
            return null;
        }

        //第一步,在每个原节点后面创建一个新节点
        Node tem = head;
        while (tem != null) {
            Node t = new Node(tem.val);
            t.next = tem.next;
            tem.next = t;
            tem = tem.next.next;
        }

        //第二步,设置新节点的随机节点
        tem = head;
        while (tem != null) {
            if (tem.random != null) {
                tem.next.random = tem.random.next;
            }
            tem = tem.next.next;
        }

        //第三步,将两个链表分离
        Node dummy = new Node(-1);
        tem = head;
        Node cur = dummy;
        while (tem != null) {
            cur.next = tem.next;
            cur = cur.next;
            tem.next = cur.next;
            tem = tem.next;
        }
        return dummy.next;
    }