【补番】左神算法系列——链表类问题

1.7 链表题目

1.7.0 链表生成器

自己写了一个链表生成器,包括一个可以按照格式打印的链表结点类。
【使用方法】调用链表生成器,传入一个数组,即可按照数组数据生成链表

//可以根据一个数组生成对应数值的链表,并且可以根据链表的样子打印出来
public static class Node{
        int val;
        Node next;

        public Node(int val) {
            this.val = val;
        }

        @Override
        public String toString() {
            StringBuilder result = new StringBuilder();
            Node temp = this;
            while(temp!=null){
                result.append(temp.next==null?temp.val:temp.val+" -> ");
                temp = temp.next;
            }
            return result.toString();
        }
    }

    public static Node linkGenerator(int[] nums){
        Node head = new Node(nums[0]);
        Node temp = head;
        for (int i = 1; i < nums.length; i++) {
            temp.next = new Node(nums[i]);
            temp = temp.next;
        }
        return head;
    }

使用方法:

	public static void main(String[] args) {
        Node head = linkGenerator(new int[]{1,2,3,4,5,5,4,3,2,1});
        System.out.println(head);
        //1 -> 2 -> 3 -> 4 -> 5 -> 5 -> 4 -> 3 -> 2 -> 1
    }

1.7.1 找到链表中的相同节点

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

算法:外部排序(Merge过程)

1.7.2 判断一个链表是不是回文结构

题目:

在这里插入图片描述

算法1:

遍历链表,将node加入栈中,再遍历栈,和原始链表比对,如果相等,说明是回文链表

算法1增强版:

  • 快慢指针:当快指针走到结尾的时候,慢指针走到中点的下一个位置
  • 将慢指针和之后的节点压栈
  • 弹栈,和链表的前半部分比较

算法2:

  • 快慢指针:当快指针走到结尾的时候,慢指针走到中点
  • 逆序从慢指针开始的后半部分
  • 新建两个指针从头和尾遍历,到终点node为止
  • 如果都相等,返回true,否则返回false
  • 最后再将后半部分恢复回来

public class Palindrome {
    public static void main(String[] args) {
        Node head = linkGenerator(new int[]{1,2,3,4,5,5,4,3,2,1});
        System.out.println(head);
        System.out.println(isPalindrome3(head));
    }

    //空间复杂度为n
    public static boolean isPalindrome1(Node head){
        Stack<Integer> stack = new Stack<>();
        Node pointer = head;
        while (pointer!=null){
            stack.push(pointer.val);
            pointer = pointer.next;
        }
        pointer = head;
        while (!stack.isEmpty()){
            if (stack.pop()!=pointer.val){
                return false;
            }
            pointer = pointer.next;
        }
        return true;
    }

    //空间复杂度为n/2
    public static boolean isPalindrome2(Node head){
        if (head == null || head.next == null){
            return true;
        }
        Node slow = head.next;//注意这里慢指针的初始位置
        Node fast = head;

        //当循环终止时,fast在最后一位(奇数),或者在倒数第二位(偶数)
        //slow在中间位置的下一位(奇数)1->2->3->(2)->1,或者在中间相等两数的第二个数(偶数)1->2->3->(3)->2->1
        while(fast.next!=null&&fast.next.next!=null){
            slow = slow.next;
            fast = fast.next.next;
        }

        //将此时从slow开始的链表压栈
        Stack<Integer> stack = new Stack<>();
        while (slow!=null){
            stack.push(slow.val);
            slow = slow.next;
        }

        while(!stack.isEmpty()){
            if (head.val != stack.pop()){
                return false;
            }
            head = head.next;
        }
        return true;
    }

    //空间复杂度为O(1)
    public static boolean isPalindrome3(Node head){
        if (head == null || head.next == null){
            return true;
        }

        Node n1 = head;//慢指针
        Node n2 = head;//快指针
        //慢指针移动到中点,或者中间偏左的位置(偶数)
        while(n2.next!=null&&n2.next.next!=null){
            n1 = n1.next;
            n2 = n2.next.next;
        }
        //开始反转后半部分的链表
        n2 = n1.next;
        n1.next = null;//mid.next = null
        Node n3 = null;
        while(n2!=null){
            n3 = n2.next;
            n2.next = n1;
            n1 = n2;
            n2 = n3;
        }

        //两个节点分别从两端遍历,查看每个节点的相等情况
        n3 = n1;
        n2 = head;
        boolean res = true;
        while (n1!=null&&n2!=null){
            if (n1.val!=n2.val){
                res = false;
                break;
            }
            n1 = n1.next;
            n2 = n2.next;
        }

        //再将后半部分的链表反转回来
        n1 = n3.next;
        n3.next = null;
        while(n1!=null){
            n2 = n1.next;
            n1.next = n3;
            n3 = n1;
            n1 = n2;
        }
        return res;
    }
}


1.7.3 链表的荷兰国旗问题

要求:给定一个链表和一个数字,将比这个数字小的节点放在左边,比这个数字大的节点放在右边,和这个数字相等的节点放在中间

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


1.7.4 复制含有随机指针节点的链表

题目:在链表的结点中,除了val,next之外,还有一个类型为Node的随机指针,它可能指向链表中的任何一个节点,也可能指向null

在这里插入图片描述

算法1:

使用HashMap :

  • 将原链表中的节点作为key,将copy后的节点作为value,放入HashMap中。
  • 重建新的链表
  • 通过映射关系,重建rand关系:1’和 2’之间的关系可以通过1和2之间的关系得到

需要额外空间

代码:


public class DeepCopy {

    public static void main(String[] args) {
        Node head = new Node(1);
        head.next = new Node(2);
        head.next.next = new Node(3);
        head.next.rand = head;
        head.next.next.rand = null;
    }

    //需要额外的存储空间
    public static Node copyListWithRand1(Node head){
        HashMap<Node, Node> map = new HashMap<>();
        Node cur = head;
        while(cur!=null){
            map.put(cur, new Node(cur.val));
            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);
    }
}
class Node{
    int val;
    Node next;
    Node rand;
    public Node(int val) {
        this.val = val;
    }
}

算法2:不用HashMap

空间复杂度为O(1)

  • 通过第一次遍历,将原来的链表:
    在这里插入图片描述

​ 每个copy节点都连接在原节点的后面

  • 当确定rand或者next关系的时候,首先通过原来链表的rand关系找到(1找到2),因为2’就在2的下一位,所以1’可以直接找到2’(通过这种结构关系,巧妙地避免了HashMap的使用)
  • 最后,分离混合的链表(在这里面重新确定next关系)

代码:

//不需要额外的存储空间
    public static Node copyListWithRand2(Node head){
        Node cur = head;
        Node next = null;
        //构造出 1 -> 1 -> 2 -> 2 -> 3 -> 3 这种结构
        while(cur!=null){
            next = cur.next;
            cur.next = new Node(cur.val);
            cur.next.next = next;
            cur = next;
        }
        cur = head;
        Node curCopy = null;
        //设置 “复制Node”的rand指针
        while(cur!=null){
            next = cur.next.next;
            curCopy = cur.next;
            curCopy.rand = cur.rand == null? null:cur.rand.next;
            cur = next;
        }

        Node res = head.next;
        cur = head;
        //分离混合在一起的链表
        while(cur!=null){
            next = cur.next.next;
            curCopy = cur.next;
            cur.next = next;
            curCopy.next = next==null?null:next.next;
            cur = next;
        }
        return res;
    }

1.7.5 两个链表相交的一系列问题

题目:在这里插入图片描述
算法:

因为是单链表,所以右图的情况是不可能的

在这里插入图片描述

  • 首先判断两个链表是否有环(用下面问题的方法)

  • 分别求出他们的入环节点loop1和loop2

  • 如果没有环(loop1 == null ,loop2 == null),通过遍历每个节点,维护两个变量:len和endNode

    1. 如果endNode1!=endNode2,两个链表不相交;否则相交
    2. 如果相交,则比较len
      ( 如果len1-len2 = 10,则先让链表1遍历10个node,然后两个链表一起遍历,相遇的节点就是交叉的第一个节点)
  • 如果两个一个链表有环一个链表无环,不可能相交(loop1,loop2中有一个为null)

  • 如果两个链表都有环(loop!=null, loop2!=null):

    1. 各自成环,不相交
    2. 先相交,再共享一个环(loop1 == loop2)
    3. 从环上两个地方切入

在这里插入图片描述

public static Node getIntersectNode(Node head1, Node head2){
        if (head1 == null || head2 == null){
            return null;
        }
        if (head1 == head2){
            return head1;
        }
        Node loop1 = getLoopNode(head1);
        Node loop2 = getLoopNode(head2);
        if (loop1 == null && loop2 == null){
            return noLoop(head1, head2);
        }else if(loop1 != null&&loop2 != null){
            return bothLoop(head1, head2, loop1, loop2);
        }else {
            return null;
        }
    }

    public static Node getLoopNode(Node head){
        if (head == null || head.next == null || head.next.next == null){
            return null;
        }
        //两个只针的位置要错开
        Node fast = head.next.next;
        Node slow = head.next;
        //第一次重合
        while(fast != slow){
            if (fast.next == null||fast.next.next==null){
                return null;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        //重合后,快指针放回head
        fast = head;
        //寻找第二次重合
        while (fast!=slow){
            fast = fast.next;
            slow = slow.next;
        }
        //返回当前重合节点
        return fast;
    }

    public static Node noLoop(Node head1, Node head2){
        if (head1 == null || head2 == null){
            return null;
        }
        //遍历head1
        int len1 = 0;
        Node endNode1 = null;
        Node cur = head1;
        while(cur != null){
            if (cur.next == null){
                endNode1 = cur;
            }
            len1++;
            cur = cur.next;
        }
        //遍历head2
        int len2 = 0;
        Node endNode2 = null;
        cur = head2;
        while(cur != null){
            if (cur.next == null){
                endNode2 = cur;
            }
            len2++;
            cur = cur.next;
        }
        //结尾节点不相等,肯定不相交
        if (endNode1!=endNode2){
            return null;
        }
        //结尾节点相等
        //长的链表先遍历sub步
        Node longerHead = len1>len2?head1:head2;
        Node shorterHead = len1>len2?head2:head1;
        int sub = len1>len2?len1-len2:len2-len1;

        for (int i = 0; i < sub; i++) {
            longerHead = longerHead.next;
        }
        while(longerHead!=shorterHead){

            longerHead = longerHead.next;
            shorterHead = shorterHead.next;
        }
        return longerHead;
    }

    public static Node bothLoop(Node head1, Node head2, Node loop1, Node loop2){

        if (loop1 == loop2){
            int len1 = 0;
            Node cur1 = head1;
            while(cur1 != loop1){
                len1++;
                cur1 = cur1.next;
            }

            //遍历head2
            int len2 = 0;
            Node cur2 = head2;
            while(cur2 != loop1){
                len2++;
                cur2 = cur2.next;
            }
            //结尾节点相等
            //长的链表先遍历sub步
            Node longerHead = len1>len2?head1:head2;
            Node shorterHead = len1>len2?head2:head1;
            int sub = len1>len2?len1-len2:len2-len1;

            for (int i = 0; i < sub; i++) {
                longerHead = longerHead.next;
            }
            while(longerHead!=shorterHead){

                longerHead = longerHead.next;
                shorterHead = shorterHead.next;
            }
            return longerHead;
        }

        Node cur = loop1.next;
        while (cur!=loop1){
            if (cur == loop2){
                return loop2;
            }
            cur = cur.next;
        }
        return null;
    }


    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }

1.7.6 判断一个单链表是否有环(若有环,将环的起点返回)

算法1:快慢指针

  • 如果两个指针重合,说明这个链表有环;如果其中一个指针遇到了null,则必然无环。

  • 当两个指针重合后,快指针回到起点,速度变为1步每次,则两个指针一定会在环的起点相遇

  • 注意:快慢指针的起点:

    1. 要么都在起点(推荐)

       		Node fast = head;
              Node slow = head;
              while(true){
                  fast = fast.next.next;
                  slow = slow.next;
                  if (fast == slow){
                      break;
                  }
              }
      
    2. 要么在起点的之后两个位置

      		Node fast = head.next.next;
              Node slow = head.next;
              while(fast != slow){
                  fast = fast.next.next;
                  slow = slow.next;
              }
      
    3. 否则:在寻找环入口的时候可能会陷入死循环

算法2:HashMap:遍历链表,每次将node存进表中,如果第一次出现了重复的node,就返回这个node

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值