1 链表反转
例1:LeetCode 206。本题虽然简单但却是众多公司的面试问题。反转前后的图示如下:
在反转的过程中主要是依据指针之间的移动,如下图所示:
class Solution { public ListNode reverseList(ListNode head) { ListNode prev = null; while (head != null) { //1 每次修改前先把head.next备份否则head修改后找不到head.next ListNode nextTemp = head.next; //2 修改head.next temp用来保存的是上次头节点的信息 head.next = prev; //3 temp进行更新保存此次头节点的信息 prev = head; //4 继续进行遍历 head = nextTemp; } //返回新链表的头结点 return prev; } } //递归版本的实现 class Solution { public ListNode reverseList(ListNode head) { //递归的终止条件 if (head == null || head.next == null) return head; //递归处理的操作是从最后一个元素开始的 ListNode p = reverseList(head.next); head.next.next = head; head.next = null; return p; } }
与此类似的有题目:LeetCode:92
注: 对于链表操作的一些基础问题:LeetCode 83、86、328、2、445
2 设立链表的虚拟的头结点
例1:LeetCode 203。对于删除一个节点其图示如下,设待删除的节点为3。
在上面的删除逻辑中对于最后一个元素也是成立的,因为最后一个元素的下一个为NULL。但问题在于第一个元素不成立,因为cur需要指在待删除节点的前一个节点。
class Solution { public ListNode removeElements(ListNode head, int val) { //1 先处理头部要删除的元素。对于头结点要特殊处理,有可能传入的是空链表因此要判断一下,也有可能下一个头结点仍然为空因此需要进行while循环 while(head != null && head.val == val){ ListNode delNode = head; head = head.next; delNode.next = null; } //2 处理中间要删除的元素,有可能在上一步操作中已经把链表删除完了,因此在这一步应该判断一下链表是否为空了 if (head == null) { return head; } //此时head肯定不是要删除的节点了 ListNode prev = head; //找到要删除节点的前一个节点,这也正是前面为什么要头结点特殊处理一下的原因,头结点前面无节点了 while (prev.next != null) { if (prev.next.val == val) { ListNode delNode = prev.next; prev.next = delNode.next; delNode.next = null; }else { //用prev进行遍历链表 prev = prev.next; } } //返回删除后的链表 return head; } }
上面的代码是没有设立虚拟头节点的情况,我们完全可以自己设立一个虚拟的头结点:
class Solution { public ListNode removeElements(ListNode head, int val) { // 定义一个虚拟的头结点,其值为多少并不重要 ListNode dummyHead = new ListNode(0); // 让定义的虚拟头结点在链表的首部 dummyHead.next = head; ListNode prev = dummyHead; //找到要删除节点的前一个节点,首元素的前面是虚拟头结点 while (prev.next != null) { if (prev.next.val == val) { ListNode delNode = prev.next; prev.next = delNode.next; delNode.next = null; }else { //用prev进行遍历链表 prev = prev.next; } } //返回删除后的链表 return dummyHead.next; } }
与此类似题目有:LeetCode 82、21。
3 链表进阶
例1:LeetCode 24。采用下图分析,我们需要把2指向1,1指向3,但是1是首元素在遍历的时候用prev即前一个节点肯定是不行的,因此这里还需要堆首元素特殊处理或者是设立一个虚拟的头节点。
因为需要交换所以要定义两个指针分别指向需要交换的两个元素,因为还有指向下一个元素,所以需要定义另外一个节点指向交换后需要指向的元素。
步骤顺序为:把2指向1,然后1指向next,然后p指向2,交换图如下:
交换完后需要对这些指针进行移动,重复上面的算法即可,在移动时是先把p移动到node1处,然后这些指针以p为基准进行定位的。
class Solution { public ListNode swapPairs(ListNode head) { // 先设置好虚拟头结点 ListNode dummyHead = new ListNode(0); dummyHead.next = head; ListNode prev = dummyHead; // 当结点不为空时才进行交换 while (prev.next != null && prev.next.next != null){ // node1根据prev定位, ListNode node1 = prev.next; ListNode node2 = node1.next; ListNode next = node2.next; // 进行交换 node2.next = node1; node1.next = next; prev.next = node2; //移动prev prev = node1; } return dummyHead.next; } }
在这道题目中也可以不用设置next指针,但这样优化性能提升并不是很明显,可能在代码理解上也会有点困难,因此建议在链表中一开始可以把所有用到的节点都单独保存下来。与此类题目有:25、147、148。
例2:LeetCode 237。在本题中是直接给出了待的删除节点,按照前面的思路当要删除节点时我们需要找到待删除节点的前一个节点,然后修改前一个节点的next,在这样的情况下已经不太可能找到前一个节点了(单向链表)。在本题中其图示如下,需要删除节点3,可以考虑把节点4复制到3处,这样只需要把第二4删除即可。
class Solution { public void deleteNode(ListNode node) { node.val = node.next.val; node.next = node.next.next; //一般情况下我们还需要把下一个节点赋值为null,这里并没有,也算小bug不过这道题目的重点在于删除节点的新思路 } }
4 双指针
例1:LeetCode 19。可以先遍历一遍计算链表的长度,然后再遍历一遍删除倒数第n个节点,但这样的话我们就是遍历了两遍链表,那么是否可以只遍历一遍链表?假设要删除倒数第二个元素即4,那么我们需要找到4前面的一个节点,对于头节点则建立一个虚拟的头结点,这样便可以删除了。此时p是指向了待删除元素的前一个元素,而q是指向空的。
因为p和q之间的长度是固定的,那么完全可以利用这个性质,一开始让p指向虚拟头结点,然后固定间隔放q,同时移动p、q这样当q指向空时,p便指向了我们想要指向的位置。
class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { // 设置一个虚拟的头结点 ListNode dummyHead = new ListNode(0); dummyHead.next = head; // 初始化p q的位置 ListNode p = dummyHead; ListNode q = dummyHead; for (int i = 0; i < n+1; i++) { q = q.next; } //移动p q while (q != null){ p = p.next; q = q.next; } ListNode delNode = p.next; p.next = delNode.next; delNode = null; return dummyHead.next; } }
与此类似题目:LeetCode 61、143、234。
0