上一篇在最后给大家留了拓展题,不知道大家有没有思考完成,其实之前说有巨坑是吓大家了,实际上也没什么,好了,我们继续来看上一篇中的拓展题。
面试题:给定单链表的头结点,删除单链表的倒数第k个结点。
这个题和前面的文章中增加了一个操作,除了找出来这个结点,我们还要删除它。删除一个结点,想必大家必定也知道:要想操作(添加、删除)单链表的某个结点,那我们还得知道这个节点的前一个结点。所以我们要删除倒数第k个结点,就必须要找到倒数第k+1个结点。然后把倒数第k+1个元素的next变量p.next指向p.next.next;
我们找到倒数第k个结点的时候,先让fast走了k-1步,然后再让slow变量和fast变量同步走,这样他们之前一直保持k-1的距离,所以当fast到链表尾节点的时候,slow刚刚指向的是倒数第k个结点。
本题由于我们要知道倒数第k+1个结点,所以得让fast先走k步,待fast指向链表的尾结点的时候,slow正好指向倒数第k+1个结点。
我们简单思考一下临界值:
- 当k=1的时候,删除的值是尾结点。我们让fast先走1步,当fast.next为尾结点的时候,倒数第k+1个结点正好是我们的倒数第二个结点。我们删除slow.next,并让slow.next指向slow.next.next=null,满足条件。
- 当k>len的时候,我们要找的倒数第k个元素不存在,直接出错。
- 当1<k<len的时候,k最大为len-1的时候,fast移动len-1步,直接到达尾结点,此时,slow指向头结点。删除倒数第k个元素,即删除正数第2个结点即可。
- 当 k=len 的时候比较特殊,当fast移动len步的时候,已经指向了fast.next = null ,此时我们其实要删除的是头结点,直接返回head.next即可。
所以我们自然得到了这样的代码。
private static LinkNode delTheSpecifiedReverse(LinkNode head, int k) {
LinkNode slow = head;
LinkNode fast = head;
if (fast == null) {
throw new RuntimeException("your linkNode is null");
}
//先让fast先走k步
for (int i = 0; i < k; i++) {
if (fast == null) {
//说明输入的k已经超过了链表的长度,直接报错
throw new RuntimeException("the value k is too large");
}
fast = fast.next;
}
//fast == null ,说明已经到了尾结点后边的空区域,说明要删除的是头结点
if (fast == null) {
return head.next;
}
while (fast.next != null) {
slow = slow.next;
fast = fast.next;
}
slow.next = slow.next.next;
return head;
}
public static void main(String[] args) {
LinkNode head = new LinkNode(1);
head.next = new LinkNode(2);
head.next.next = new LinkNode(3);
head.next.next.next = new LinkNode(4);
head.next.next.next.next = new LinkNode(5);
// System.out.println("getTheMid2:"+getTheMid2(head));
// head.next.next.next.next.next = head;
// System.out.println("isRingLink:"+isRingLink(head));
// System.out.println("getSpecifiedNodeReverse:" + getSpecifiedNodeReverse(head, 3));
// System.out.println("getSpecifiedNodeReverse:" + getSpecifiedNodeReverse(null, 1));
// System.out.println("未删除前链表为:");
LinkNode old=head;
while (old != null) {
System.out.print(old.data + "->");
old = old.next;
}
System.out.println("");
LinkNode node = delTheSpecifiedReverse(head, 3);
System.out.println("删除后链表为:");
while (node != null) {
System.out.print(node.data + "->");
node = node.next;
}
}
输出结果为:
1->2->3->4->5->
删除后链表为:
1->2->4->5->
OK,我们现在已经解决了上篇文章中留下的拓展题,现在我们来看看我们的链表都还有怎样的考法。
面试题:定义一个单链表,输入一个链表的头结点,反转该链表,并输出反转后链表的头结点。为了方便,我们链表的data采用整型。
这是一个反转链表的经典题,我们来屡一下思路:一个结点包含下一个结点的引用,反转的意思是就是要把原来指向下一结点的引用指向上一个结点。我们可以分为下面的步骤:
- 找到当前要反转的结点的下一个结点,并用变量保存,因为下一次要反转的是它,如果我们不保存的话,一定会因为前面已经反转,导致无法通过遍历得到这个结点;
- 然后让当前结点的next引用指向上一个结点,上一个结点初试null,因为头结点的反转后变成尾结点;
- 当前要反转的结点变成下一个要比较元素的上一个结点,用变量保存;
- 当前要比较的结点赋值为之前保存的未反转前的下一个结点;
- 当前反转的结点为null的时候,保存的上一个结点即为反转后的链表头结点。
用代码实现就是:
private static LinkNode reverseLink(LinkNode head) {
//上一个结点
LinkNode nodePre = null;
LinkNode next = null;
LinkNode node = head;
while (node!=null){
//先用next保存下一个要反转的结点,不然会导致链表断裂.
next=node.next;
//在把现在结点的next引用指向上一个结点
node.next=nodePre;
//把当前结点赋值给nodePre变量,以便于下一次赋值
nodePre=node;
//向后遍历
node=next;
}
return nodePre;
}
public static void main(String[] args) {
LinkNode head = new LinkNode(1);
head.next = new LinkNode(2);
head.next.next = new LinkNode(3);
head.next.next.next = new LinkNode(4);
head.next.next.next.next = new LinkNode(5);
// System.out.println("getTheMid2:"+getTheMid2(head));
// head.next.next.next.next.next = head;
// System.out.println("isRingLink:"+isRingLink(head));
// System.out.println("getSpecifiedNodeReverse:" + getSpecifiedNodeReverse(head, 3));
// System.out.println("getSpecifiedNodeReverse:" + getSpecifiedNodeReverse(null, 1));
// System.out.println("未删除前链表为:");
// LinkNode old = head;
// while (old != null) {
// System.out.print(old.data + "->");
// old = old.next;
// }
// System.out.println("");
// LinkNode node = delTheSpecifiedReverse(head, 3);
// System.out.println("删除后链表为:");
// while (node != null) {
// System.out.print(node.data + "->");
// node = node.next;
// }
LinkNode node=reverseLink(head);
while (node!=null){
System.out.print(node.data+"->");
node=node.next;
}
}
输出结果为:5->4->3->2->1->
链表可以考的可是真多,相信不是小伙伴都和我一样,云里雾里了,今天就先说到这里了。
本来来自nanchen的分享,喜欢的去关注一下公众号,文章地址