内容来源于自己的刷题笔记,对一些题目进行方法总结,用 java 语言实现。
第二章 链表问题
1. 打印两个有序链表的公共部分:
-
题目描述:
给定两个有序链表的头指针 head1 和 head2,打印两个链表的公共部分。
-
解题思路:
因为是有序链表,所以从两个链表的头部开始进行如下判断:
- 如果 head1 的值小于 head2,则 head1 往下移动
- 如果 head2 的值小于 head1,则 head2 往下移动
- 如果 head1 的值与 head2 的值相等,则打印这个值,然后 head1 和 head2 都往下移动
- head1 或者 head2 有任何一个移动到 null,则整个过程停止
-
代码实现:
public class PrintCommonPart { private class Node{ public int value; public Node next; public Node(int value){ this.value = value; } } public void printCommonPart(Node head1,Node head2){ while (head1 != null && head2 != null){ if (head1.value < head2.value){ head1 = head1.next; }else if (head1.value > head2.value){ head2 = head2.next; }else { System.out.println(head1.value+" "); head1 = head1.next; head2 = head2.next; } } } }
2. 在单链表和双链表中删除倒数第 k 个节点:
-
题目描述:
分别实现两个函数,一个可以删除单链表中倒数第 k 个节点,另一个可以删除双链表中倒数第 k 个节点。
-
解题思路:
遍历两次,第一次遍历先得出链表的长度,如果长度小于倒数,就返回一个空链表;否则进行第二次遍历,找到被删除的节点,如果是双向链表,要多一步判断,将后续的链表指向上一个节点。
-
代码实现:
public class RemoveNode { private class Node{ public int value; public Node next; public Node(int value){ this.value = value; } } public Node removeLastKrhNode(Node head,int lastKth){ if (lastKth < 1){ return null; } Node preHead = new Node(0); preHead.next = head; int i = 0; //计算链表长度 while (preHead.next != null){ i++; preHead = preHead.next; } if (i < lastKth){ return null; }else{ Node cur = new Node(0); Node result = new Node(0); cur.next = head; result.next = head; //计算到达删除节点需要移动的次数 int k = i - lastKth + 1; if (k == 1) return head.next; //找到删除的上一个节点 while (k != 1){ cur = cur.next; k--; } cur.next = cur.next.next; return result.next; } } private class DoubleNode{ public int value; public DoubleNode last; public DoubleNode next; public DoubleNode(int value){ this.value = value; } } public DoubleNode removeLastKthNode(DoubleNode head,int lastKth){ if (lastKth < 1){ return null; } DoubleNode preHead = new DoubleNode(0); preHead.next = head; int i = 0; //计算链表长度 while (preHead.next != null){ i++; preHead = preHead.next; } if (i < lastKth){ return null; }else{ DoubleNode cur = new DoubleNode(0); DoubleNode result = new DoubleNode(0); cur.next = head; result.next = head; //计算到达删除节点需要移动的次数 int k = i - lastKth + 1; if (k == 1) return head; //找到删除的上一个节点 while (k != 1){ cur = cur.next; k--; } DoubleNode temp = cur.next.next; cur.next = temp; if (temp != null){ temp.last = cur; } return head; } } }
3. 删除链表的中间节点和 a/b 处的节点:
-
题目描述:
给定链表的头节点 head,实现删除链表的中间节点的函数。
例如:
不删除任何节点;
1 -> 2,删除节点1;
1 -> 2 -> 3,删除节点2;
1 -> 2 -> 3 -> 4,删除节点2;
1 -> 2 -> 3 -> 4 -> 5,删除节点3;
进阶:
给定链表的头节点 head,整数 a 和 b,实现删除位于 a/b 处节点的函数。
例如:
链表:1 -> 2 -> 3 -> 4 -> 5,假设 a/b 的值为 r。
如果 r 等于0,不删除任何节点;
如果 r 在区间(0,1/5]上,删除节点1;
如果 r 在区间(1/5,2/5]上,删除节点2;
如果 r 在区间(2/5,3/5]上,删除节点3;
如果 r 在区间(3/5,4/5]上,删除节点4;
如果 r 在区间(4/5,1]上,删除节点5;
如果 r 大于1,不删除任何节点。
-
解题思路:
如果链表为空或者是长度为1,不需要删除节点,直接返回头节点;如果链表的长度为2,将头节点删除即可;当链表的长度到达3或4,应该删除第二个节点;当链表的长度为5或6,删除第三个节点…也就是当链表长度增加2,要删除的节点就向后移动1。
进阶:首要问题就是如何根据链表长度n,以及a和b的值确定该删除的节点是哪一个节点,观察上面的式子,发现当 r * n 向上取整时,就是删除节点的序号。
-
代码实现:
public class RemoveNode { private class Node{ public int value; public Node next; public Node(int value){ this.value = value; } } public Node removeMidNode(Node head){ if (head == null || head.next == null){ return head; } if (head.next.next == null){ return head.next; } Node pre = head; Node cur = head.next.next; while (cur.next != null || cur.next.next != null){ pre = pre.next; cur = cur.next.next; } pre.next = pre.next.next; return head; } public Node removeByRatio(Node head,int a,int b){ if (a < 1 || a > b){ return head; } int n = 0; Node cur = head; while (cur != null){ n++; cur = cur.next; } n = (int) Math.ceil((double)(a * n)/(double)b); if (n == 1){ head = head.next; } if (n > 1){ cur = head; while (--n != 1){ cur = cur.next; } } return head; } }
4. 反转单向和双向链表:
-
题目描述:
分别实现反转单向链表和反转双向链表的函数。
-
解题思路:
由于需要反转,那么需要一个来存储已经插入的节点,还有一个来存储还未插入的节点,于是定义两个新节点进行操作。
-
代码实现:
public class ReverseList { private class Node{ public int value; public Node next; public Node(int value){ this.value = value; } } public Node reverseList(Node head){ Node pre = null; Node next = null; while (head != null){ next = head.next; head.next = pre; pre = head; head = next; } return pre; } private class DoubleNode{ public int value; public DoubleNode last; public DoubleNode next; public DoubleNode(int value){ this.value = value; } } public DoubleNode reverseList(DoubleNode head){ DoubleNode pre = null; DoubleNode next = null; while (head != null){ next = head.next; head.next = pre; head.last = next; pre = head; head = next; } return pre; } }
5. 反转部分单向链表:
-
题目描述:
给定一个单向链表的头节点 head,以及两个整数 from 和 to,在单向链表上把第 from 个节点到第 to 个节点这一部分进行反转。
-
解题思路:
- 先判断是否满足 1 ≤ f r o m ≤ t o ≤ N 1\le from \le to \le N 1≤from≤to≤N,如果不满足,则直接返回原来的头节点
- 找到第 from-1 个节点 fPre 和第 to+1 个节点 tPos,把反转的部分先反转,然后正确的连接 fPre 和 tPos
- 如果 fPre 为 null,说明反转部分是包含头节点的,则返回新的头节点,也就是没反转之前反转部分的最后一个节点,也是反转之后反转部分的第一个节点;如果 fPre 不为 null,则返回旧的头节点
-
代码实现:
public class ReverseSomeList { private class Node{ public int value; public Node next; public Node(int value){ this.value = value; } } public Node reverseSomeList(Node head,int from,int to){ int len = 0; Node node1 = head; Node fPre = null; Node tPos = null; while (node1 != null){ len++; fPre = (len == from - 1)? node1 : fPre; tPos = (len == to + 1)? node1 : tPos; node1 = node1.next; } node1 = (fPre == null)? head:fPre.next; Node node2 = node1.next; node1.next = tPos; Node next = null; while (node2 != null){ next = node2.next; node2.next = node1; node1 = node2; node2 = node2.next; } if (fPre != null){ fPre.next = node1; return head; } return node1; } }