在第一篇文章中为了说明递归如何写,所以对于链表的操作都是用递归来写的,我们发现递归写起来比较简洁,但是执行的过程有点复杂,并且往往在实际的算法中都是要将递归改成循环来做,可以一定程度上减少开销提高性能。下面我们来看看循环如何实现的。
链表的反转
需要验证准确性的话,可以去leetcode上去做这道题,题号为206.这道题还是比较经典的。代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
class Solution { public ListNode reverseList(ListNode head) { ListNode newHead = null; ListNode currentNode = head; while(currentNode != null){ ListNode next = currentNode.next; currentNode.next = newHead; newHead = currentNode; currentNode = next; } return newHead; } }
|
一开始学的时候看的答案就是这个方法,显然是要比递归好的,但是如果不理解的话,光靠背很容易出错,并且也不大背的上,如今重温这道题,其实是很简单的,我们下面用图示来阐述。
主要的思想是用两个指针,其中newHead
指向的是反转成功的链表的头部,currentHead
指向的是还没有反转的链表的头部:
初始状态是newHead
指向null
,currentHead
指向的是第一个元素,一直往后遍历直到newHead
指向最后一个元素为止:
下面展示的是其中某个时间点的指向细节:
理解了上面的图示,程序就呼之欲出了。
删除链表节点
题目为:给一个数值,找到链表中这个等于这个数的所有节点并且删除。效果如下,比如给的数是2,则表示删除链表中所有为2的节点。
这个题目也是非常地经典,面试中经常会看到。我们务必要掌握。
这个其实有两种解题思路,比较简单的是增加一个虚拟的头节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Solution { public ListNode removeElements(ListNode head, int val) { ListNode dummy = new ListNode(0); dummy.next = head; ListNode curr = dummy; while(curr.next != null){ if(curr.next.val == val){ ListNode delNode = curr.next; curr.next = delNode.next; }else{ curr = curr.next; } } return dummy.next; } }
|
这种实现的方式相对来说比较简单。大体的解决思路为:
另一种是比较特殊的处理方式,不需要虚拟的头节点就可以实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Solution { public ListNode removeElements(ListNode head, int val) { while(head != null && head.val == val){ head = head.next; } if(head == null){ return null; } ListNode prev = head; while(prev.next != null){ if(prev.next.val == val){ ListNode delNode = prev.next; prev.next = delNode.next; }else{ prev = prev.next; } } return head; } }
|
没错,代码大体是相似的,特殊的处理在于一开始的节点的值与val相等的处理,所以我们需要先处理一下head以及head的后面连续的都是等于val的节点,直到处理到不为val的节点为止,即把开头相等的节点全部剔除掉,下面再继续循环判断是否相等。
关于链表的题目还有很多,由于链表数据结构比较简单,但是算法并不简单,所以面试中经常会被问道,需要好好准备一下。后面会进行相应的总结。