链表

链表

1. 反转单向链表

反转单向列表,需要两个辅助指针pre和cur
在这里插入图片描述

public class Solution_ReverseList {

    public static class ListNode {
        int val;
        ListNode next;
        ListNode(int x) { val = x; }
    }

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

    public static void printList(ListNode head){
        if(head == null){
            return;
        }
        System.out.print(head.val);
        while(head.next != null){
            head = head.next;
            System.out.print(head.val);
        }
        System.out.println();
    }

    public static void main(String[] args) {
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        ListNode node4 = new ListNode(4);
        ListNode node5 = new ListNode(5);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        node5.next = null;
        ListNode head = node1;
        printList(head);
        head = reverseList(head);
        printList(head);
    }
}

2. 反转双向链表

反转双向链表,也需要两个辅助指针,与反转单向链表类似

public class Solution_ReverseList2 {

    public static class ListNode {
        int val;
        ListNode next;
        ListNode pre;
        ListNode(int x) { val = x; }
    }

    public static ListNode reverseList2(ListNode head) {
        if(head == null || head.next == null){
            return null;
        }
        ListNode cur = head.next;
        ListNode lastCur = null;
        head.next = null;
        while(cur != null){
            head.pre = cur;
            head.next = lastCur;
            lastCur = head;
            head = cur;
            cur = cur.next;
        }
        head.next = lastCur;
        head.pre = null;
        return head;
    }

    public static void printList(ListNode head){
        if(head.next == null){
            System.out.print(head.val + " ");
            return;
        }
        System.out.print(head.val + " ");
        while(head.next != null){
            head = head.next;
            System.out.print(head.val + " ");
        }
        System.out.println("---");
    }

    public static void main(String[] args) {
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        node1.pre = null;
        node1.next = node2;
        node2.pre = node1;
        node2.next = node3;
        node3.pre = node2;
        node3.next = null;
        ListNode head = node1;
        printList(head);
        head = reverseList2(head);
        printList(head);
    }
}

3. 打印两个有序链表的公共部分

题目:给定两个有序链表的头指针head1和head2,打印两个链表的公共部分。

public class Solution_PrintCommon {

    public static class ListNode{
        int val;
        ListNode next;
        public ListNode(int val){
            this.val = val;
            next = null;
        }

    }

    public static void printCommon(ListNode head1, ListNode head2){
        while(head1 != null && head2 != null){
            if(head1.val < head2.val){
                head1 = head1.next;
            }else if(head1.val > head2.val){
                head2 = head2.next;
            }else{
                System.out.print(head1.val + " ");
                head1 = head1.next;
                head2 = head2.next;
            }
        }
    }

    public static void main(String[] args) {
        ...
    }
}

4. *判断一个链表是否为回文结构

示例1:

输入: 1->2
输出: false

示例2:

输入: 1->2->2->1
输出: true

要求:时间复杂度O(N),空间复杂度O(1)

思路:如果不要求时间复杂度为O(1),可以用一个栈来辅助,先遍历一遍,遍历到的元素压入栈。遍历完之后依次从栈中弹出元素,与链表比对。
为了保证空间复杂度为O(1),先找到链表的中间位置,然后将后半部分逆序。最后从两端开始往中间比对。
最后别忘了将后半部分恢复正常
在这里插入图片描述

public class Solution_IsPalindrome {

    public static class ListNode{
        int val;
        ListNode next;
        public ListNode(int val){
            this.val = val;
            this.next = null;
        }
    }

    public static boolean isPalindrome(ListNode head){
        if(head == null || head.next == null){
            return true;
        }
        //记录原始头结点
        ListNode first = head;
        //使用两个指针,mid一次向后跳1,quick一次向后跳2。
        // 当quick跳到链表终点,mid刚好跳到链表的中间部分。
        ListNode mid = head;
        ListNode quick = head;
        while(quick.next != null && quick.next.next != null){
            mid = mid.next;
            quick = quick.next.next;
        }

        //反转后半部分的链表,返回最后一个元素节点。
        head = mid;
        ListNode head2 = reverse(head);

        //从两端开始比较
        head = first;
        boolean result = compare(head, head2);

        //将后半部分反转会初始的样子
        reverse(head2);

        return  result;
    }

    //反转链表
    public static ListNode reverse(ListNode head){
        if(head == null || head.next == null){
            return head;
        }
        ListNode pre = head;
        ListNode cur = head.next;
        head.next = null;
        while(cur != null){
            head = cur;
            cur = cur.next;
            head.next = pre;
            pre = head;
        }
        return head;
    }

    //比较两个链表是否相等
    public static boolean compare(ListNode head, ListNode head2){
        while(head != null && head2 != null){
            if(head.val == head2.val){
                head = head.next;
                head2 = head2.next;
            }else{
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) {
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        ListNode node4 = new ListNode(4);
        ListNode node5 = new ListNode(3);
        ListNode node6 = new ListNode(2);
        ListNode node7 = new ListNode(1);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        node5.next = node6;
        node6.next = node7;
        node7.next = null;
        ListNode head = node1;
        boolean result = isPalindrome(head);
        System.out.println(result);
    }
}

5. *将链表按照某值划分成左边小,中间相等,右边大的形式

5.1 不考虑稳定性

解题思路:仿照荷兰国旗的partition操作, 直接将链表中的值保存到一个数组中,然后按照荷兰国旗的划分方式,划分完之后再用链表穿起来。但是这种方式不保证稳定性,而且额外空间复杂度为O(N)。

5.2 考虑稳定性

解题思路: 将链表划分成三个子链表,然后合并。

步骤:

  1. 建立三个子链表,分别用于存放小于、等于、大于K的节点。
  2. 为每个子链表建立两个辅助结点引用,头结点引用和为结点引用。头结点引用永远指向链表中第一个节点,尾结点引用永远指向最后一个。
  3. 遍历链表,遇到每个结点,都将其加到对应子链表的后面。
  4. 遍历完毕后,三个子链表头尾相连,即划分完毕。

额外空间复杂度为O(1),时间复杂度为O(N)。

public class Solution_PartitionByNum {

    public static class ListNode{
        int val;
        ListNode next;
        public ListNode(int val){
            this.val = val;
        }
    }

    public static ListNode partition(ListNode head, int k){
        if(head == null || head.next == null){
            return head;
        }
        //小于区域子链表的两个首尾结点引用
        ListNode less = null;
        ListNode lessEnd = null;
        //等于区域子链表的两个首尾结点引用
        ListNode equal = null;
        ListNode equalEnd = null;
        //大于区域子链表的两个首尾结点引用
        ListNode more = null;
        ListNode moreEnd = null;
        ListNode headNext = null;
        while(head != null){
            headNext = head.next;
            head.next = null;
            if(head.val < k){
                //第一个进入子链的情况与后进入的不同
                //第一个进入子链,同时给该子链 头结点引用 和 尾结点引用 赋值
                //之后进入子链的,只需更新 尾结点引用
                if(less == null){
                    less = head;
                    lessEnd = head;
                }else{
                    lessEnd.next = head;
                    lessEnd = lessEnd.next;
                }
            }else if(head.val == k){
                if(equal == null){
                    equal = head;
                    equalEnd = head;
                }else{
                    equalEnd.next = head;
                    equalEnd = equal.next;
                }
            }else{
                if(more == null){
                    more = head;
                    moreEnd = more;
                }else{
                    moreEnd.next = head;
                    moreEnd = moreEnd.next;
                }
            }
            head = headNext;
        }
        //连接三个子链
        lessEnd.next = equal;
        equalEnd.next = more;
        return less;
    }


    public static void main(String[] args) {
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(7);
        ListNode node3 = new ListNode(4);
        ListNode node4 = new ListNode(5);
        ListNode node5 = new ListNode(3);
        ListNode node6 = new ListNode(6);
        ListNode node7 = new ListNode(2);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        node5.next = node6;
        node6.next = node7;
        ListNode head = partition(node1,5);
        while(head != null){
            System.out.print(head.val + " ");
            head = head.next;
        }
    }
}

6. *复制含有随机指针节点的链表

leetcode-复杂链表的复制

题目: 一种特殊的单链表节点类描述如下

class Node {
  int value;
  Node next;
  Node rand;
  Node(int val) {
  value = val;
  }
}

rand指针是单链表节点结构中新增的指针, rand可能指向链表中的任意一个节点, 也可能指向null。 给定一个由Node节点类型组成的无环单链表的头节点head, 请实现一个函数完成这个链表的复制, 并返回复制的新链表的头节点。

思路:
遍历链表,对于链表中的每一个节点A,新建一个节点为A’,此时A'nextrand都为空。
遍历完之后,将这些新建的节点一一连接起来,关键是得利用原链表之间的节点关系将新节点一一连接起来。
即使知道A.nextB,为了将A'B‘连接起来,那么怎么把A'B'都准确无误地找到?那就需要使用一个结构将AA'绑在一起。

6.1 解法一:空间复杂度为O(N)

思路:使用哈希表,时间复杂度和空间复杂度都为O(N)

步骤:

  1. 先遍历一遍链表,链表中的每一个元素对应一个哈希表,其中的key为链表中的元素,value为该元素的复制。
    例:如果node1为原链表中的节点,则map.get(node1)为复制的新链表中的对应的节点。
  2. 再遍历一遍链表,将哈希表中对应的value节点连接起来。
public static Node function(Node head){
    Node copyNode  = null;
    if(head == null)
        return null;
    if(head.next == null){//如果只有头结点,头结点的random指针有可能指向他自己
        copyNode = new Node(head.val);
        if(head.random == null){
            copyNode.random = null;
        }else {
            copyNode.random = copyNode;
        }
        return copyNode;
    }
	Node cur = head;
	HashMap<Node, Node> map = new HashMap<>();
	while(cur != null){
		map.put(cur, new Node(cur.value));
		cur = cur.next;
	}
	cur = head;
    //将每个新建的节点连接起来。
	while(cur != null){
		map.get(cur).next = map.get(cur.next);
		map.get(cur).rand = map.get(cur.rand);
		cur = cur.next;
	}
    //最后返回的是复制链表的头部
	return map.get(head);
}

6.2 解法二:额外空间复杂度为O(1)

思路:利用链表的结构,将每一个节点的复制节点插在原节点的后面,这样就能轻松找到复制后的节点了。

步骤:

  1. 遍历链表,复制遇到的每一个节点为copyNode,并将其插入原节点后。
  2. 遍历链表,根据原链表中的rand指针,将每一个copyNoderand指针赋值。
  3. 拆分链表,将所有copyNode抽离出来,并使用next指针连接在一起。拆分链表不会破坏rand指针。
    在这里插入图片描述
public static Node function(Node head){
    Node copyNode  = null;
    if(head == null)
        return null;
    if(head.next == null){//如果只有头结点,头结点的random指针有可能指向他自己
        copyNode = new Node(head.val);
        if(head.random == null){
            copyNode.random = null;
        }else {
            copyNode.random = copyNode;
        }
        return copyNode;
    }
	Node cur = head;
	Node curNext = null;
	//遍历链表,复制遇到的每一个节点,并将其插入原节点后面
	while(cur != null){
		curNext = cur.next;
		cur.next = new Node(cur.value);
		cur.next.next = curNext;
		cur = curNext;
	}

	//遍历链表,将上一步新建节点的rand指针连接起来。
	cur = head;
	while(cur != null){
		copyNode = cur.next;
		copyNode.rand = cur.rand != null ? cur.rand.next : null;
		cur = cur.next.next;
	}

	//将链表拆成两条链
	cur = head;
	Node head2 = head.next;
	while(cur != null){
		curNext = cur.next.next;
		copyNode = cur.next;
		cur.next = curNext;
		if(curNext != null){
			copyNode.next = curNext.next;
		}else{
			copyNode.next = null;
		}
		cur = curNext;
	}
	return head2;
}

7. 两链表相交的一系列问题

题目: 在本题中,单链表可能有环,也可能无环。给定两个单链表的头节点 head1和head2,这两个链表可能相交,也可能不相交。请实现一个函数, 如果两个链表相交,请返回相交的第一个节点;如果不相交,返回null 即可。

要求: 如果链表1的长度为N,链表2的长度为M,时间复杂度请达到 O(N+M),额外空间复杂度请达到O(1)。

7.1 判断单链表是否有环

leetcode - 环形链表:只需判断是否有环

leetcode - 环形链表II:返回入环的第一个节点

要求:如果有环,返回第一个入环的节点,如果无环返回空

方法一:使用哈希表判断,如哈希表之前,先判断该节点是否已在哈希表中

方法二:使用一个快指针(一次走两步),一个慢指针,都从头节点出发。两个指针相遇后,快指针回到头节点处,变成一次走一步,当和慢指针再次相遇,就是入环的第一个节点。

//判断链表是否有环
public class Solution_HasCycle {

    public static class ListNode{
        int val;
        ListNode next;
        public ListNode(int val){
            this.val = val;
            this.next = null;
        }
    }

    //返回入环的第一个节点
    public static ListNode hasCycle(ListNode head){

        if(head == null || head.next == null){
            return null;
        }
        //定义两个指针,一个快,每次加2,一个慢,每次加1
        ListNode quick = head;
        ListNode slow = head;

        while(quick != null && quick.next != null){
            quick = quick.next.next;
            slow = slow.next;
            if(quick == slow){//快慢指针相遇,说明有环
                break;
            }
        }
        //如果快指针为空,说明没有环
        if(quick == null || quick.next == null){
            return null;
        }else{
            quick = head;
            while(true){
                if(slow == quick){
                    return slow;
                }
                quick = quick.next;
                slow = slow.next;
            }
        }
    }

    //不返回节点,返回是否有环
    public static boolean hasCycle2(ListNode head){

        if(head == null || head.next == null){
            return false;
        }
        //定义两个指针,一个快,每次加2,一个慢,每次加1
        ListNode quick = head;
        ListNode slow = head;

        while(quick != null && quick.next != null){
            quick = quick.next.next;
            slow = slow.next;
            if(quick == slow){//快慢指针相遇,说明有环
                return true;
            }
        }
        return false;
    }
}

7.2 判断两个无环单链表相交的第一个节点

方法一:使用哈希表,先将第一个链表放入哈希表中去,然后遍历第二条链表,判断第二条链表中的节点是否在哈希表中。

方法二:遍历两条链表,分别得到链表的长度和链表的最后一个节点。如果两条链表的最后一个节点相等,则两条链表相交。假设第二条链表比第一条链表长(n-m),那么第二条链表从头开始遍历n-m个长度,然后第一条链表从头开始遍历,总会遇到相同的节点。

方法二的代码:

leetcode - 相交链表

public class Solution_Intersection {

    public static class ListNode{
        int val;
        ListNode next;
        public ListNode(int val){
            this.val = val;
            this.next = null;
        }
    }

    public static ListNode getIntersection(ListNode headA, ListNode headB){
        //两链表的长度
        int lenA = 0, lenB = 0;
        //定义两链表的遍历指针和最后一个节点
        ListNode curA = headA;
        ListNode lastA = null;
        ListNode curB = headB;
        ListNode lastB = null;
        ListNode result = null;
        while(curA != null){
            lenA++;
            if(curA.next == null){
                lastA = curA;
            }
            curA = curA.next;
        }
        while(curB != null){
            lenB++;
            if(curB.next == null){
                lastB = curB;
            }
            curB = curB.next;
        }
        //如果两个链表的最后一个节点相同,则肯定相交
        if(lastA == lastB){
            curA = headA;
            curB = headB;
            //两链表的长度差
            int len = 0;
            if(lenA > lenB){
                result = search(lenA, lenB, curA, curB);
            }else{
                result = search(lenB, lenA, curB, curA);
            }
        }else{
            return null;
        }
        return result;
    }

    public static ListNode search(int lenA, int lenB, ListNode curA, ListNode curB){
        int len = lenA - lenB;
        while(len > 0){
            curA = curA.next;
            len--;
        }
        while(curA != null && curB != null){
            if(curA == curB){
                return curA;
            }else{
                curA = curA.next;
                curB = curB.next;
            }
        }
        return curA;
    }

    public static void main(String[] args) {
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        ListNode node4 = new ListNode(4);
        ListNode node5 = new ListNode(5);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        node5.next = null;
        ListNode node6 = new ListNode(6);
        node6.next = node5;
        ListNode result = getIntersection(node1, node6);
        System.out.println(result.val);
    }
}

7.3 ***判断两个有环单链表相交的第一个节点

有环链表和无环链表不可能相交

两个有环链表的相交情况有以下三种:

  1. 两个有环链表不相交

    分别找出两个有环链表的入环节点,然后从入环节点遍历第二个环,如果再次遍历到入环节点,没有遇到与第一个链表的入环节点相等的环,则两个链表不相交。

  2. 两个有环链表在环外相交

    在环外相交,说明入环节点相同。将环截去,入环节点当做两条链表的最后一个节点,则问题转换成两个无环单链表的相交问题。

  3. 两个有环链表在环内相交

    入环节点不同。让第一条有环链表遍历,直到遍历到有节点与第二条有环链表的入环节点相同,则证明两个链表相交。返回任意一条链表的入环节点都行。

求两条链表(链表可能有环,也可能无环)相交的第一个节点的代码如下
如果一直两个链表都有环,可将判断链表是否有环的5行if语句删除,不删也行。

//两个有环链表如果相交的话,返回第一个相交的节点,不相交返回null
public class Solution_IntersectionCycle {

    public static class ListNode{
        int val;
        ListNode next;
        public ListNode(int val){
            this.val = val;
            this.next = null;
        }
    }

    public static ListNode function(ListNode head1, ListNode head2){

        //先判断每一个链表的入环节点first
        ListNode first1 = hasCycle(head1);
        ListNode first2 = hasCycle(head2);

        if(first1 == null && first2 == null){//两个无环链表
            return getIntersection(head1, head2);
        }else if(first1 == null || first2 == null){//一个无环,一个有环,不相交
            return null;
        }

        //创建两个迭代指针
        ListNode cur1 = head1;
        ListNode cur2 = head2;

        if(first1 == first2){//入环节点相同
            //入环节点相同,则肯定在环外相交。可以将环截去,
            // 转换成无环链表寻找第一个相交节点

            //先将环截去,用以下两个指针分别记录入环节点的下一个位置,
            // 以备将环添加上
            ListNode temp1 = first1.next;
            ListNode temp2 = first2.next;
            first1.next = null;
            first2.next = null;

            ListNode result = getIntersection(cur1, cur2);
            first1.next = temp1;
            first2.next = temp2;
            return result;

        }else{//入环节点不同,可能相交,也可能不相交
            //从first1下一个节点开始遍历,
            // 如果再遍历到first1,没有发现与first2相等的节点,则不相交
            // 如果还没转到first1,发现了与first2相等的节点,返回first1或first2都行
            cur1 = first1.next;
            while(true){
                if(cur1 == first2){
                    return cur1;
                }else if(cur1 == first1){
                    return null;
                }else{
                    cur1 = cur1.next;
                }
            }
        }
    }

    //返回入环的第一个节点
    public static ListNode hasCycle(ListNode head){

        if(head == null || head.next == null){
            return null;
        }
        //定义两个指针,一个快,每次加2,一个慢,每次加1
        ListNode quick = head;
        ListNode slow = head;

        while(quick != null && quick.next != null){
            quick = quick.next.next;
            slow = slow.next;
            if(quick == slow){//快慢指针相遇,说明有环
                break;
            }
        }
        //如果快指针为空,说明没有环
        if(quick == null || quick.next == null){
            return null;
        }else{
            quick = head;
            while(true){
                if(slow == quick){
                    return slow;
                }
                quick = quick.next;
                slow = slow.next;
            }
        }
    }

    //判断两个无环单链表相交的第一个节点
    public static ListNode getIntersection(ListNode headA, ListNode headB){
        //两链表的长度
        int lenA = 0, lenB = 0;
        //定义两链表的遍历指针和最后一个节点
        ListNode curA = headA;
        ListNode lastA = null;
        ListNode curB = headB;
        ListNode lastB = null;
        ListNode result = null;
        while(curA != null){
            lenA++;
            if(curA.next == null){
                lastA = curA;
            }
            curA = curA.next;
        }
        while(curB != null){
            lenB++;
            if(curB.next == null){
                lastB = curB;
            }
            curB = curB.next;
        }
        //如果两个链表的最后一个节点相同,则肯定相交
        if(lastA == lastB){
            curA = headA;
            curB = headB;
            //两链表的长度差
            int len = 0;
            if(lenA > lenB){
                result = search(lenA, lenB, curA, curB);
            }else{
                result = search(lenB, lenA, curB, curA);
            }
        }else{
            return null;
        }
        return result;
    }

    public static ListNode search(int lenA, int lenB, ListNode curA, ListNode curB){
        int len = lenA - lenB;
        while(len > 0){
            curA = curA.next;
            len--;
        }
        while(curA != null && curB != null){
            if(curA == curB){
                return curA;
            }else{
                curA = curA.next;
                curB = curB.next;
            }
        }
        return curA;
    }

    public static ListNode test1(){//两个有环链表不相交的情况
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        ListNode node4 = new ListNode(4);
        ListNode node5 = new ListNode(5);
        ListNode node6 = new ListNode(6);
        node1.next = node2;
        node2.next = node3;
        node3.next = node2;
        node4.next = node5;
        node5.next = node6;
        node6.next = node5;
        return function(node1, node4);
    }

    public static ListNode test2(){//两个有环链表相较于环外
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        ListNode node4 = new ListNode(4);
        ListNode node5 = new ListNode(5);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node2;
        node5.next = node2;
        return function(node1, node5);
    }

    public static ListNode test3(){//两个有环链表相较于环内
        ListNode node1 = new ListNode(1);
        ListNode node2 = new ListNode(2);
        ListNode node3 = new ListNode(3);
        ListNode node4 = new ListNode(4);
        ListNode node5 = new ListNode(5);
        ListNode node6 = new ListNode(6);
        node1.next = node2;
        node2.next = node3;
        node3.next = node4;
        node4.next = node5;
        node5.next = node2;
        node6.next = node4;
        return function(node1, node6);
    }

    public static void main(String[] args) {
        ListNode result = test3();
        if(result == null){
            System.out.println("不相交");
        }else{
            System.out.println("相交于" + result.val);
        }
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值