LeetBook-链表

56 阅读11分钟

链表

链表 - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台

设计链表

解法1

单链表

思路和这本书前面写的思路一致

class MyLinkedList {

    class Node {
        int val;
        Node next;
        Node (int val) {
            this.val = val;
        }
    }

    Node head;
    int size;

    public MyLinkedList() {
        head = null;
        size = 0;
    }

    public int get(int index) {
        if (head == null || index < 0 || index >= size) {
            return -1;
        }
        Node nodej = head;
        for (int i = 0; i < index; i++) {
            nodej = nodej.next;
        }
        return nodej.val;
    }

    public void addAtHead(int val) {
        Node nodeH = new Node(val);
        nodeH.next = head;
        head = nodeH;
        size++;
    }

    public void addAtTail(int val) {
        Node nodeT = new Node(val);
        Node nodei = head;
        if (size == 0) {
            head = nodeT;
            size++;
            return;
        }
        while (nodei.next != null) {
            nodei = nodei.next;
        }
        nodei.next = nodeT;
        nodeT.next = null;
        size++;
    }

    public void addAtIndex(int index, int val) {
        if (index > size) {
            return;
        }
        if (index == size) {
            addAtTail(val);
            return;
        }
        if (index == 0) {
            addAtHead(val);
            return;
        }
        Node nodeZ = new Node(val);
        Node nodek = head;
        for (int i = 0; i < index - 1; i++) {
            nodek = nodek.next;
        }
        nodeZ.next = nodek.next;
        nodek.next = nodeZ;
        size++;
    }

    public void deleteAtIndex(int index) {
        Node nodem = head;
        if (index < 0 || index >= size) {
            return;
        }
        if (index == 0) {
            if (size == 1) {
                head = null;
                size--;
                return;
            }
            head = head.next;
            size--;
            return;
        }
        for (int i = 0; i < index - 1; i++) {
            nodem = nodem.next;
        }
        nodem.next = nodem.next.next;
        size--;
    }
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

环形链表

解法1

双指针(快慢指针)

  1. 快指针步长2,慢指针步长1

  2. 对链表进行循环

    • 如果相遇,则有环
    • 如果快指针碰到末尾,则无环
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null) {
            return false;
        }
        ListNode fast = head;
        ListNode slow = head;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                return true;
            }
        }
        return false;
    }
}

环形链表Ⅱ

解法1

参考:漫画算法P159

环形链表Ⅱ思路.png

  1. 快慢指针先初次相遇(思路同上一题环形链表)
  2. 快指针移动到head节点,然后快慢指针同时以1为步长循环,相遇点即为入环点(具体思路见上图)
/**
 * Definition for singly-linked list.
 * class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode detectCycle(ListNode head) {
        if (head == null) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while (fast.next != null && fast.next.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                fast = head;
                while (fast != slow) {
                    fast = fast.next;
                    slow = slow.next;
                }
                return fast;
            }
        }
        return null;
    }
}

相交链表

解法1

思路:将两个链表首尾相接拼起来遍历,相同节点一定会出现在后面的相同位置

  1. 针对两种拼接方式的链表,取两个头节点,并且设置两个标志(不允许操作原链表),判断是否切换到另一个链表
  2. 当链表走到空并且判断标志表示并未切换时切换到另一个链表继续往后走
  3. 如果碰到两个ListNode相等,则相交,return当前结点
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */
public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode list1 = headA;
        ListNode list2 = headB;
        int isTwo_1 = 0;
        int isTwo_2 = 0; 
        while (list1 != null && list2 != null) {
            if (list1 == list2) {
                return list1;
            }
            list1 = list1.next;
            list2 = list2.next;
            if (isTwo_1 == 0 && list1 == null) {
                list1 = headB;
                isTwo_1++;
            }
            if (isTwo_2 == 0 && list2 == null) {
                list2 = headA;
                isTwo_2++;
            }
        }
        return null;
    }
}

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

解法1

  1. 先循环一次链表求得链表的size
  2. 第二次循环链表到要删除的元素的前一个元素
  3. node的next指向node.next.next
  4. 两种特殊情况:
  • 注意1,当只有一个节点时直接给头节点赋null
  • 注意2,当删除的为头节点时,头节点直接指向下一个元素
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        int size = 1;
        ListNode node = head;
        while (node.next != null) {
            node = node.next;
            size++;
        }
        if (head.next == null) {
            head = null;
            return head;
        }
        if (size == n) {
            head = head.next;
            return head;
        }
        node = head;
        for (int i = 1; i < size - n; i++) {
            node = node.next;
        }
        node.next = node.next.next;
        return head;
    }
}

解法2

快慢指针

  1. 快慢指针初始化均指向head
  2. 快指针先移动
  3. 当快指针和慢指针之间的距离等于n(要删除倒数第n个结点)时,快慢指针同时移动到最后
  4. 移除慢指针的next元素
  5. 判断两个特殊情况:
  • 当只有一个结点时,头节点指向null,然后返回
  • 当删除的为头节点时,头节点直接指向下一个元素
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode node = head;
        ListNode slow = head;
        int count = 0;
        if (head.next == null) {
            head = null;
            return head;
        }
        while (node.next != null) {
            node = node.next;
            count++;
            if (count <= n) {
                continue;
            }
            slow = slow.next;
        }
        if (count + 1 == n) {
            head = head.next;
            return head;
        }
        slow.next = slow.next.next;
        return head;
    }
}

反转链表

解法1

  1. node初始指向头节点
  2. 当node的next不为空时,将node的每一个next节点链到head,并让head指向新来的节点
  3. 当node移动到最后,链表反转完成
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode node = head;
        ListNode i = null;
        while (node.next != null) {
            i = node.next;
            node.next = node.next.next;
            i.next = head;
            head = i;
        }
        return head;
    }
}

移除链表元素

解法1

  1. 遍历整个链表
  2. 如果碰到节点值等于val,则节点的next指向next的next
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return head;
        }
        ListNode node = head;
        while (node.next != null) {
            if (node.next.val == val) {
                node.next = node.next.next;
                if (node.next == null) {
                    break;
                }
                continue;
            }
            node = node.next;
        }
        if (head.val == val) {
            head = head.next;
        }
        return head;
    }
}

解法2

解法1的优化

  1. 新增一个自定义节点为头节点,可以不用考虑特殊情况
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        ListNode h = new ListNode(0, head);
        ListNode node = h;
        while (node.next != null) {
            if (node.next.val == val) {
                node.next = node.next.next;
                if (node.next == null) {
                    break;
                }
                continue;
            }
            node = node.next;
        }
        head = h.next;
        return head;
    }
}

奇偶链表

解法1

  1. 初始化两个节点one指向head,two指向head.next
  2. 遍历链表
  3. 奇数节点连到one指向的节点,偶数节点连到two指向的节点
  4. 循环结束后,将two连到one之后
  5. return
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode oddEvenList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode one = head;
        ListNode two = head.next;
        ListNode node1 = one;
        ListNode node2 = two;
        while (node2.next != null && node2.next.next !=null) {
            node1.next = node2.next;
            node2.next = node2.next.next;
            node1 = node1.next;
            node2 = node2.next;
        }
        if (node2.next != null) {
            node1.next = node2.next;
            node1 = node1.next;
            node2.next = null;
        }
        node1.next = two;
        return one;
    }
}

回文链表

解法1

暴力循环,丢到数组判断

  1. 循环链表取出所有值丢到数组里
  2. 数组双指针前后循环判断是否有不相等的
  3. 如果有不相等的返回false,否则返回true
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        int count = 0;
        ListNode node = head;
        int[] nums = new int[999999];
        while (node.next != null) {
            nums[count] = node.val;
            count++;
            node = node.next;
        }
        nums[count] = node.val;
        int left = 0;
        int right = count;
        while (left < right) {
            if (nums[left] != nums[right]) {
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

解法2

使用栈

  1. 遍历一次链表,将链表中的元素放到一个栈中
  2. 逐一取出栈中的元素,并且遍历链表,如果碰到不相等的值,返回false
  3. 否则返回true
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        Stack<Integer> stack = new Stack();
        ListNode node = head;
        while (node != null) {
            stack.push(node.val);
            node = node.next;
        }
        while (head != null) {
            if (head.val != stack.pop()) {
                return false;
            }
            head = head.next;
        }
        return true;
    }
}

解法3

快慢指针加反转链表

  1. 快指针步长2,慢指针步长1遍历链表
  2. 当快指针到末尾时,翻转慢指针后面的链表
  3. 快指针移动到开头
  4. 快慢指针同时以步长1进行遍历,快指针遍历前半链表,慢指针遍历后半链表
  5. 如果有值不相等,返回false,否则返回true
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        ListNode fast = head;
        ListNode slow = head;
        while (fast != null && fast.next != null) {
            slow = slow.next;
            fast = fast.next.next;
        }
        if (fast != null) {
            slow = slow.next;
        }
        slow = reverseList(slow);
        fast = head;
        while (slow != null) {
            if (slow.val != fast.val) {
                return false;
            }
            slow = slow.next;
            fast = fast.next;
        }
        return true;
    }
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        ListNode node = head;
        ListNode i = null;
        while (node.next != null) {
            i = node.next;
            node.next = node.next.next;
            i.next = head;
            head = i;
        }
        return head;
    }
}

解法4

  1. 第一次遍历,拿到链表长度,并求得中间长度
  2. 第二次遍历前半部分,将nodeM节点指针指向中间节点
  3. 第三次遍历后半部分,将指针顺序调转(即前半个链表正常,后半个链表头节点为原来的末尾,反向到原来链表的中间)
  4. 两个链表分别遍历,如果碰到不相等返回false,否则返回true
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public boolean isPalindrome(ListNode head) {
        int count = 0;
        ListNode node = head;
        while (node != null) {
            count++;
            node = node.next;
        }
        ListNode nodeM = head;
        int mid = count / 2;
        for (int i = 0; i < mid; i++) {
            nodeM = nodeM.next;
        }
        ListNode tail = null;
        while (nodeM != null) {
            ListNode next = nodeM.next;
            nodeM.next = tail;
            tail = nodeM;
            nodeM = next;
        }
        while (head != null && tail != null) {
            if (head.val != tail.val) {
                return false;
            }
            head = head.next;
            tail = tail.next;
        }
        return true;
    }
}

合并两个有序链表

解法1

  1. 取出两个链表头节点中小的那个作为新链表的头节点
  2. 同时遍历两个链表,将两个链表头节点小的那个连接到新链表的末尾,并将原头节点指向头节点的next
  3. 当一个链表循环至空时,将另一个链表剩余部分直接连到新链表的末尾
  • 注意:当传入某个链表为空时直接返回另一个链表
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        if (list1 == null) {
            return list2;
        }
        if (list2 == null) {
            return list1;
        }
        ListNode list1_n = null;
        ListNode list2_n = null;
        ListNode head = null;
        if (list1.val > list2.val) {
            head = list2;
            list2 = list2.next;
        } else {
            head = list1;
            list1 = list1.next;
        }
        ListNode temp = head;
        while (list1 != null && list2 != null) {
            if (list1.val > list2.val) {
                list2_n = list2.next;
                temp.next = list2;
                temp = temp.next;
                list2 = list2_n;
            } else {
                list1_n = list1.next;
                temp.next = list1;
                temp = temp.next;
                list1 = list1_n;
            }
        }
        if (list1 != null) {
            temp.next = list1;
        } else {
            temp.next = list2;
        }
        return head;
    }
}

两数相加

解法1

  1. 假设最终结果为list1,通过遍历将list2的结点的值全部加到list1上,并设置一个flag判断是否进位
  2. 如果list1长度小于list2,则循环到末尾后,将next指向list2指向的结点,然后返回
  3. 注意:
  • 最高位进位要注意判断
  • 一个链表循环完成后连续进位要注意判断
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode result = l1;
        boolean ad = false;
        while (l1 != null && l2 != null) {
            l1.val += l2.val;
            if (ad) {
                l1.val += 1;
                ad = false;
            }
            if (l1.val >= 10) {
                ad = true;
                l1.val -= 10;
            }
            if (l1.next == null && l2.next == null && ad) {
                l1.next = new ListNode(1);
                return result;
            }
            if (l1.next == null) {
                l1.next = l2.next;
                l1 = l1.next;
                break;
            }
            l1 = l1.next;
            l2 = l2.next;
        }
        while (l1 != null) {
            if (ad) {
                l1.val += 1;
                ad = false;
            }
            if (l1.val >= 10) {
                ad = true;
                l1.val -= 10;
            }
            if (l1.next == null && ad) {
                l1.next = new ListNode(1);
                ad = false;
            }
            l1 = l1.next;
        }
        return result;
    }
}

扁平化多级双向链表

解法1

使用递归

  1. 遍历链表
  2. 如果碰到有子节点的结点,则记录这个结点的next,然后递归调用原函数,将返回的头节点和当前节点连到一起,子节点置空
  3. 如果循环到node.next为空,并且记录的next有值,则连上记录的next继续执行
  4. 如果node为空,则返回头节点
/*
// Definition for a Node.
class Node {
    public int val;
    public Node prev;
    public Node next;
    public Node child;
};
*/

class Solution {
    public Node flatten(Node head) {
        Node node = head;
        Node next = null;
        while (node != null) {
            if (node.child != null) {
                next = node.next;
                node.next = flatten(node.child);
                node.next.prev = node;
                node.child = null;
            }
            if (node.next == null && next != null) {
                node.next = next;
                next.prev = node;
                next = null;
            }
            node = node.next;
        }
        return head;
    }
}

解法2

在解法1的基础上优化,碰到子节点不进行递归,而是将后面的链表放入栈中,接着循环子节点的链表,结束后取出

  • 通俗说就是将每一个链在分叉处的尾巴切掉,然后拉直,再反向将尾巴接在最后
  1. 对链表进行循环
  2. 碰到子节点不为空的链表结点,将链表后面的链表头节点放入栈中
  3. 将结点的next指向子节点,结点的child置为空
  4. 继续循环,如果再次碰到某个结点存在子节点,重复上述操作,一直到循环到链表结束
  5. 依次取出栈中的结点连到当前链表的末尾
/*
// Definition for a Node.
class Node {
    public int val;
    public Node prev;
    public Node next;
    public Node child;
};
*/class Solution {
    public Node flatten(Node head) {
        if (head == null) {
            return head;
        }
        Node node = head;
        Node save = null;
        Stack<Node> stack = new Stack<>();
        while (node != null) {
            if (node.child != null) {
                if (node.next != null) {
                    stack.push(node.next);
                }
                node.next = node.child;
                node.next.prev = node;
                node.child = null;
            }
            save = node;
            node = node.next;
        }
        while (!stack.isEmpty()) {
            save.next = stack.pop();
            save.next.prev = save;
            while (save.next != null) {
                save = save.next;
            }
        }
        return head;
    }
}

复制带随机指针的链表

解法1

  1. 遍历链表
  2. 将链表每个结点的值取出并且new新的结点与之对应,放入HashMap中(旧结点作为key,新节点作为value)
  3. 第二次遍历旧链表
  4. 每一个新节点的next和random都对应-->从HashMap中取出的旧结点(key)的next和random结点(旧结点(key))对应的新节点(value)
  5. 返回HashMap中旧的头节点(key)对应的新的头节点(value)
/*
// Definition for a Node.
class Node {
    int val;
    Node next;
    Node random;

    public Node(int val) {
        this.val = val;
        this.next = null;
        this.random = null;
    }
}
*/

class Solution {
    public Node copyRandomList(Node head) {
        HashMap<Node, Node> map = new HashMap<>();
        Node old = head;
        Node xin = null;
        while(old != null) {
            xin = new Node(old.val);
            map.put(old, xin);
            old = old.next;
        }
        old = head;
        xin = map.get(old);
        while (old != null) {
            xin.next = map.get(old.next);
            xin.random = map.get(old.random);
            old = old.next;
            xin = xin.next;
        }
        return map.get(head);
    }
}

旋转链表

解法1

  1. 遍历链表得到链表长度
  2. 如果要循环的次数等于0或者要旋转的次数%链表长度等于0,直接返回原头节点(旋转之后的链表和原链表一致)
  3. 要旋转的次数%链表长度=要旋转的次数(move)
  4. 快慢指针,快指针比慢指针快move个节点
  5. 快指针到末尾,从慢指针位置截断,把快慢指针之间的链表连到原来的头节点前面,作为新的头
  6. 返回新的头节点
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode rotateRight(ListNode head, int k) {
        if (head == null) {
            return head;
        }
        int size = 0;
        ListNode fast = head;
        while (fast != null) {
            fast = fast.next;
            size++;
        }
        if (k == 0 || k % size == 0) {
            return head;
        }
        int move = k % size;
        ListNode slow = head;
        fast = head;
        while (move > 0) {
            move--;
            fast = fast.next;
        }
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }
        ListNode newhead = slow.next;
        slow.next = null;
        fast.next = head;
        return newhead;
    }
}