链表相关问题

链表相关问题

一、快慢指针简单问题

1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点

A.推演过程

1.设置快慢指针指向第一个元素
在这里插入图片描述
2.只要快指针后面的两个元素都不为空,就让快指针每次走两步,慢指针每次走一步
在这里插入图片描述
在这里插入图片描述
3.返回慢指针所指向的节点,这里是C节点,这里仅列出了偶数长度返回上中点的情况,奇数长度同理。

B.代码实现:
public static Node midOrUpMidNode(Node head){
    if (head == null||head.next == null||head.next.next == null){
        return head;
    }
    // 链表有2个有值的点及以上
    Node slow = head.next;      // 慢指针
    Node fast = head.next.next; // 快指针
    while (fast.next!=null&&fast.next.next!=null){
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
}

2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点

A.推演过程

1.将快慢指针指向头节点的下一个节点
在这里插入图片描述
2.快指针每次走两步,慢指针每次走一步
在这里插入图片描述
3.返回慢指针对应的节点,这里注意是认为头节点也算一个节点的,而上一题没算,可能造成理解困难,但是我想多维化思考

B.代码实现
public static Node midOrDownMidNode(Node head){
    if (head==null||head.next==null){
        return head;
    }
    Node slow = head.next;
    Node fast = head.next;
    while (fast.next!=null&&fast.next.next!=null){
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
}

3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个

A.推演过程

1.设置慢指针指向头节点,快指针指向第三个节点
在这里插入图片描述
2.只要快指针后面有两个节点不为空,快指针一次两步,慢指针一次一步
在这里插入图片描述
3.返回slow节点

B.代码实现
public static Node midOrUpMidPreNode(Node head){
    if (head==null||head.next==null||head.next.next==null){
        return head;
    }
    Node slow = head;
    Node fast = head.next.next;
    while (fast.next!=null&&fast.next.next!=null){
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
}

4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个

A.推演过程

1.快指针指向头节点,慢指针指向第二个节点
在这里插入图片描述
2.只要快指针后两个节点不为空,快2慢1
在这里插入图片描述
3.返回慢指针对应的节点

B.代码实现
public static Node midOrDownMidPreNode(Node head){
    if (head == null||head.next == null){
        return null;
    }
    if (head.next.next==null){
        return head;
    }
    Node slow = head;
    Node fast = head.next;
    while (fast.next!=null&&fast.next.next!=null){
        slow = slow.next;
        fast = fast.next.next;
    }
    return slow;
}

二、稍微难一点的问题

1)判断给定头节点的链表是否为回文结构

链表的回文结构:

链表回文结构和普通回文结构略有不同
比如如下这个链表
在这里插入图片描述

这个链表显然是回文结构,但是如果将链表各个节点的值拼接在一起是1202020201,显然对于一个字符串1202020201不是回文结构

方法一:带容器的,用一个栈解决
推理过程

1.准备一个栈
在这里插入图片描述

2.将链表元素依次入栈,但不破坏原结构
在这里插入图片描述

3.依次遍历链表元素,并且栈中弹出一个元素,将他们的值进行比较,如果全部相等,则该链表为回文链表,下图是一次比较过程
在这里插入图片描述

代码实现
public static boolean isPalindrome1(Node head){
    Stack<Node> stack = new Stack<>();
    Node cur = head;
    while (cur!=null){
        stack.push(cur);
        cur=cur.next;
    }
    while (head!=null){
        if (head.value!=stack.pop().value){
            return false;
        }
        head = head.next;
    }
    return true;
}

不难发现本方法是可以进行改进的,改进只需要将后半部分压入栈中即可,结合快慢指针法,可得如下代码,这里希望读者能够将图像画出来,自己推演一下过程,加深对快慢指针的理解:

public static boolean isPalindrome2(Node head){
    if (head==null||head.next==null){
        return true;    // 0个或者1个节点,规定是回文串
    }
    Node right = head.next;
    Node cur = head;
    while (cur.next!=null&&cur.next.next!=null){
        right = right.next;
        cur = cur.next.next;
    }
    Stack<Node> stack = new Stack<>();
    while (right!=null){
        stack.push(right);
        right = right.next;
    }
    while (!stack.isEmpty()){
        if (head.value!=stack.pop().value){
            return false;
        }
        head=head.next;
    }
    return true;
}
方法二:有限变量实现,非常考研编程能力,所以在面试题中频繁出现
推理过程

1.利用快慢指针找到中点,奇数找中点,偶数找上中点
在这里插入图片描述
2.调整链表,考验代码细节能力,调整成如下模样
在这里插入图片描述
这里调整过程如果看不懂,可以对着下面的代码自己画一画,基本上也就能明白
3.向中间移动头尾指针,进行比较,直到有一个指针为空,如果全部相等,则为回文子串,返回true前应该恢复原状,这里也是编程细节处理问题,对着代码痛苦的搞一遍,以后就不用再痛苦了。

代码实现
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;
    }
    // n1  是   中点
    n2 = n1.next;
    n1.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.value != n2.value) {
            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;
}

2)将单向链表按某值划分成左边小、中间相等、右边大的形式

方法一、利用数组做Partition(复习一下快排就懂)
推理过程
  1. 将链表放入等长的数组中
  2. 利用Partition算法进行分区
  3. 将数组中的节点串成链表返回
    注:如果Partition有困难的话,可以先学习一下数据结构有关快速排序的内容
代码实现
	public static Node listPartition1(Node head,int pivot){
        if (head == null){
            return head;
        }
        Node cur = head;
        int i = 0;
        while (cur!=null){
            i++;
            cur = cur.next;
        }
        Node[] nodeArr = new Node[i];  // 建立一个等长的数组
        i = 0;
        cur = head;
        for (i = 0; i != nodeArr.length; i++) {
            nodeArr[i] = cur;
            cur = cur.next;
        }
        arrPartition(nodeArr,pivot);
        for (i = 1; i!=nodeArr.length ; i++) {
            nodeArr[i-1].next = nodeArr[i];
        }
        nodeArr[i-1].next = null;
        return nodeArr[0];
    }
    
    public static void arrPartition(Node[] nodeArr,int pivot){
        int small = -1;
        int big = nodeArr.length;
        int index = 0;
        while (index!=big){
            if (nodeArr[index].value<pivot){
                swap(nodeArr,++small,index++);
            }else if (nodeArr[index].value==pivot){
                index++;
            }else {
                swap(nodeArr,--big,index);
            }
        }
    }
    
    public static void swap(Node[] nodeArr,int a,int b){
        Node tmp = nodeArr[a];
        nodeArr[a] = nodeArr[b];
        nodeArr[b] = tmp;
    }
方法二、利用有限变量,考验编程能力
推理过程

1.分三个区,小于指定值pivot区,等于pivot区,大于pivot区,分别设置三个区域的头尾指针(sh,st,eh,et,mh,mt)如下图
在这里插入图片描述

2.遍历单链表按照各个节点值的大小与pivot的关系将其插入指定的队列,结果如下图
在这里插入图片描述

3.将st指向eh,将et指向mh
在这里插入图片描述

代码实现
	public static Node listPartition2(Node head, int pivot) {
        Node sH = null;
        Node sT = null;
        Node eH = null;
        Node eT = null;
        Node mH = null;
        Node mT = null;
        Node next = null;
        while (head != null) {
            next = head.next;
            head.next = null;
            if (head.value < pivot) {
                if (sH == null) {
                    sH = head;
                    sT = head;
                } else {
                    sT.next = head;
                    sT = head;
                }
            } else if (head.value == pivot) {
                if (eH == null) {
                    eH = head;
                    eT = head;
                } else {
                    eT.next = head;
                    eT = head;
                }
            } else {
                if (mH == null) {
                    mH = head;
                    mT = head;
                } else {
                    mT.next = head;
                    mT = head;
                }
            }
            head = next;
        }
        if (sT != null) {
            sT.next = eH;
            eT = eT == null ? sT : eT;
        }
        if (eT != null) {
            eT.next = mH;
        }
        return sH != null ? sH : (eH != null ? eH : mH);
    }

3)一种特殊的单链表节点类描述如下

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

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

方法一:用容器的方法,哈希表实现
推理过程

0.假设复制如下链表,曲线为rand指针
在这里插入图片描述

1.准备一个哈希表,然后遍历链表,哈希表的key存放原节点,哈希表的value存放新的copy节点
在这里插入图片描述
然后再次遍历链表,我们由A的的源节点找到next和rand然后参照哈希表对新A进行连线以此类推,便可实现复制功能,如果不太清楚,可以尝试看看代码

代码实现
public static Node copyListWithRand1(Node head){
    HashMap<Node,Node> map = new HashMap<>();
    Node cur = head;
    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);
}
方法二:利用有限的变量实现,考验编程能力
推理过程

在这里插入图片描述
1.遍历链表将自己复制一份插在自己后面,暂不不设置rand指针
在这里插入图片描述
3.再次遍历链表设置新节点的rand指针,想一想为什么这个时候可以做到这一步?
在这里插入图片描述
3.实现分离链表,得到复制的链表

代码实现
public static Node copyListWithRand2(Node head) {
	if (head == null) {
		return null;
	}
	Node cur = head;
	Node next = null;
	// copy node and link to every node
	// 1 -> 2
	// 1 -> 1' -> 2
	while (cur != null) {
		// cur 老 next 老的下一个
		next = cur.next;
		cur.next = new Node(cur.value);
		cur.next.next = next;
		cur = next;
	}
	cur = head;
	Node curCopy = null;
	// set copy node rand
	// 1 -> 1' -> 2 -> 2'
	while (cur != null) {
		// cur 老
		// cur.next 新 copy
		next = cur.next.next;
		curCopy = cur.next;
		curCopy.rand = cur.rand != null ? cur.rand.next : null;
		cur = next;
	}
	// head head.next
	Node res = head.next;
	cur = head;
	// split
	while (cur != null) {
		next = cur.next.next;
		curCopy = cur.next;
		cur.next = next;
		curCopy.next = next != null ? next.next : null;
		cur = next;
	}
	return res;
}

4)给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的第一个节点。如果不相交,返回null,要求如果两个链表长度之和为N,时间复杂度请达到O(N),空间复杂度O(1)

推理过程
	实现这个较为复杂的问题,我们往往要进行问题的拆解,所以我们首先要实现一个子函数完成如下功能
给定一个单链表,如果有环返回入环的第一个节点,如果无环返回null

问题推理过程如下:
1.给定一个单链表,我们设置快慢指针,往后走,如果快指针先为空,一定无环,返回null,如果快慢指针相遇,则一定有环,将快指针回到头节点处,快慢指针同时以一步往后走,则下一次相遇位置就是入环节点,返回即可
第一步代码如下

//找到链表第一个入环节点,如果无环返回null
    public static Node getLoopNode(Node head){
        if (head==null||head.next==null||head.next.next==null){
            return null;
        }
        Node slow = head.next;
        Node fast = head.next.next;
        while (slow!=fast){
            if (fast.next==null||fast.next.next==null){
                return null;
            }
            fast=fast.next.next;
            slow=slow.next;
        }
        fast=head;
        while (slow!=fast){
            slow=slow.next;
            fast=fast.next;
        }
        return slow;
    }

2.分类讨论,当链表1和链表二都无环时:
遍历两个链表求各自长度,判断null,然后做差,设置两个初始分别指向两链表头部的指针,让长链表指针先走差值然后长短链表同时走,知道为空或者第一次相等时停止,返回相等节点。
代码如下

//如果两个链表都无环,返回第一个相交节点,如果无,则返回null
    public static Node noLoop(Node head1, Node head2){
        if (head1==null||head2==null){
            return null;
        }
        Node cur1 = head1;
        Node cur2 = head2;
        int n=0;
        while (cur1.next!=null){
            n++;
            cur1=cur1.next;
        }
        while (cur2.next!=null){
            n--;
            cur2=cur2.next;
        }
        if (cur1!=cur2){
            return null;
        }
        
        cur1=n>0?head1:head2;
        cur2=cur1==head1?head2:head1;
        n=Math.abs(n);
        while (n!=0){
            n--;
            cur1=cur1.next;
        }
        while (cur1!=cur2){
            cur1=cur1.next;
            cur2=cur2.next;
        }
        return cur1;
    }

3.分类讨论:一个链表无环一个链表有环这种情况是不存在的所以不必讨论,至于为什么不存在可以画图试一试。
4.分类讨论:两个链表均有环的情况:
总共只有三种结构的可能:
(一)两个链表不相交各自独立
(二)两链表相交入环节点是同一个,方法其实同2.,区分出长短链表,长链表先走差值步,最终同步移动知道第一次相等返回交点
(三)两链表相交入环节点不同
代码如下:

//两个有环节点,返回第一个相交节点,如果不相交,返回null
    public static Node boothLoop(Node head1, Node loop1, Node head2, Node loop2) {
        Node cur1 = null;
        Node cur2 = null;
        if (loop1 == loop2) {
            cur1 = head1;
            cur2 = head2;
            int n = 0;
            while (cur1 != loop1) {
                n++;
                cur1 = cur1.next;
            }
            while (cur2 != loop2) {
                n--;
                cur2 = cur2.next;
            }
            cur1 = n > 0 ? head1 : head2;
            cur2 = cur1 == head1 ? head2 : head1;
            n = Math.abs(n);
            while (n != 0) {
                n--;
                cur1 = cur1.next;
            }
            while (cur1 != cur2) {
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            return cur1;
        } else {
            cur1 = loop1.next;
            while (cur1 != loop1) {
                if (cur1 == loop2) {
                    return loop1;
                }
                cur1 = cur1.next;
            }
            return null;
        }
    }

总体代码实现
	 public static class Node {
        public int value;
        public Node next;

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

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

    //找到链表第一个入环节点,如果无环返回null
    public static Node getLoopNode(Node head) {
        if (head == null || head.next == null || head.next.next == null) {
            return null;
        }
        Node slow = head.next;
        Node fast = head.next.next;
        while (slow != fast) {
            if (fast.next == null || fast.next.next == null) {
                return null;
            }
            fast = fast.next.next;
            slow = slow.next;
        }
        fast = head;
        while (slow != fast) {
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }

    //如果两个链表都无环,返回第一个相交节点,如果无,则返回null
    public static Node noLoop(Node head1, Node head2) {
        if (head1 == null || head2 == null) {
            return null;
        }
        Node cur1 = head1;
        Node cur2 = head2;
        int n = 0;
        while (cur1.next != null) {
            n++;
            cur1 = cur1.next;
        }
        while (cur2.next != null) {
            n--;
            cur2 = cur2.next;
        }
        if (cur1 != cur2) {
            return null;
        }

        cur1 = n > 0 ? head1 : head2;
        cur2 = cur1 == head1 ? head2 : head1;
        n = Math.abs(n);
        while (n != 0) {
            n--;
            cur1 = cur1.next;
        }
        while (cur1 != cur2) {
            cur1 = cur1.next;
            cur2 = cur2.next;
        }
        return cur1;
    }

    //两个有环节点,返回第一个相交节点,如果不相交,返回null
    public static Node boothLoop(Node head1, Node loop1, Node head2, Node loop2) {
        Node cur1 = null;
        Node cur2 = null;
        if (loop1 == loop2) {
            cur1 = head1;
            cur2 = head2;
            int n = 0;
            while (cur1 != loop1) {
                n++;
                cur1 = cur1.next;
            }
            while (cur2 != loop2) {
                n--;
                cur2 = cur2.next;
            }
            cur1 = n > 0 ? head1 : head2;
            cur2 = cur1 == head1 ? head2 : head1;
            n = Math.abs(n);
            while (n != 0) {
                n--;
                cur1 = cur1.next;
            }
            while (cur1 != cur2) {
                cur1 = cur1.next;
                cur2 = cur2.next;
            }
            return cur1;
        } else {
            cur1 = loop1.next;
            while (cur1 != loop1) {
                if (cur1 == loop2) {
                    return loop1;
                }
                cur1 = cur1.next;
            }
            return null;
        }


    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

leglk

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值