力扣刷题之路-----链表问题
好久没有更新力扣刷题的一些收获了,最近换成了labuladong的算法小抄来学习算法,在此还是只记录自己觉得有难度以及有启发的题目!
涉及题目:
23. 合并K个升序链表
题目仍然是很好理解,在两个链表进行排序的时候还比较简单。多个链表的时候,首先考虑到题目的要求是排好序,那么就需要使用到可以排序的数据结构。使用队列可以很方便地存取节点,且可以进行排序,则考虑使用队列来存储链表节点。使用队列来存储节点数据,根据节点的next获取该节点的下一个节点。
public ListNode mergeKLists(ListNode[] lists) { if(lists.length==0) return null; ListNode head = new ListNode(-1); ListNode p = head; PriorityQueue<ListNode> queue = new PriorityQueue<>(lists.length, new Comparator<ListNode>() { @Override public int compare(ListNode o1, ListNode o2) { return o1.val-o2.val; } }); for (ListNode list :lists){ if(list!=null) queue.add(list); } while (!queue.isEmpty()){ ListNode temp = queue.poll(); p.next = temp; if(temp.next!=null){ queue.add(temp.next); } p=p.next; } return head.next; }
160. 相交链表
如果只用两个指针完成这个题的话,存在一个问题是指针什么时候往后,那个指针往后才能同时到相交的节点。这个题解看不懂,只能背思路了。
把两个链表拼接起来,假设p1和p2两个指针分别在两条链表上前进,让p1遍历完链表1之后开始遍历链表2,让p2遍历完链表2后开始遍历链表1。这样二者相遇的地方就是两个链表相交的地方。图示↓
public ListNode getIntersectionNode(ListNode headA, ListNode headB) { ListNode fastA=headA,fastB=headB; while (fastA!=fastB){ if(fastA==null) fastA=headB; else fastA=fastA.next; if(fastB==null) fastB=headA; else fastB=fastB.next; } return fastA; }
206. 反转链表
链表的反转可以有两种方法实现:迭代和递归。
迭代方法的思想很简单也很容易实现,要对链表进行反转,那么后一个节点的next就要指向前一个节点,此时需要有一个指针来记录前一个节点,同时也需要链表按照原来的顺序前进。原来的头结点反转后是最后一个节点,该节点的next是null,所以刚开始的时候初始化“前节点”为null。
public ListNode reverseList(ListNode head) { ListNode pre = null;//指向前一个节点 ListNode curr = head;//指向当前节点 while (curr!=null){ ListNode next = curr.next; curr.next = pre; pre = curr; curr = next; } return pre; }
递归方法的实现(结合图示看)首先找到最后一个节点,即head.next=null的节点,由于递归的函数参数是head.next,返回最后head时相当于返回的是4,此时将5的next指向4,即head.next.next=head;每次向前返回的时候cur都是5。单纯的文字解释比较困难,此处放一个题解的链接,感觉讲的是比较容易懂的。动画演示+多种解法 206. 反转链表 - 反转链表 - 力扣(LeetCode)
public ListNode reverseList(ListNode head) { if(head==null || head.next==null){ return head; } ListNode cur = reverseList(head.next); head.next.next = head; head.next = null; return cur; }
92. 反转链表 II
当需要反转一部分链表时,需要记录反转部分的前一个节点和反转部分的后一个节点,即图上2节点前的节点和4节点后的节点。当left=1时,相当于反转前right个节点,这个反转的算法使用递归法来写的话比较好些,即
public ListNode reverseList(ListNode head,int n){ if(n==1){ successor = head.next; return head; } ListNode cur = reverseList(head.next,n-1); head.next.next = head; head.next = successor; return cur; }
当n等于1的时候,已经走到了图上4所在的节点,这个时候需要记录4后的节点是什么,使用successor来记录,并且每次都讲head.next赋值为successor,这个赋值和null是一样的,只不过把null替换成了另一节点。
当left != 1时。我们只需要找到开始位置,即忽略前面的多余节点,仍旧按照left=1来计算。图示如下。left不等于1时,需要先向后遍历,将head.next的索引视为1的时候,反转的区间是从left-1开始的,即相对于2节点,left=2;相对于3节点,left=1。由于最后需要将反转链表和原链表连接起来,需要记录开始的前一个节点,即图中的2节点,所以设置head.next满足left=1的条件时,则可以直接按照上边的代码反转。代码如下。
class Solution { ListNode successor = null;//后驱节点 public ListNode reverseBetween(ListNode head, int left, int right) { if(left==1){ return reverseList(head,right); } head.next = reverseBetween(head.next,left-1,right-1); return head; } public ListNode reverseList(ListNode head,int n){ if(n==1){ successor = head.next; return head; } ListNode cur = reverseList(head.next,n-1); head.next.next = head; head.next = successor; return cur; } }
总结:链表的题感觉好难啊 很多题目都比较难转过来弯。多多练习 多写几遍打算背一下了