203 Remove Linked List Elements
思路:考虑1 : 可能有多个节点符合;考虑2:命中节点是head;考虑3:命中节点是尾节点;考虑4:命中节点是中间的普通节点。
学习1:在linkedList 运用递归的思路还是很常见的,我还没有形成习惯。不断缩小问题规模。
public ListNode removeElementsV2(ListNode head, int val) {if(head==null) return null;head.next = removeElementsV2(head.next,val);if(head.val == val){head = head.next;}return head;}
学习2:因为第一版有head节点的特殊处理,可以采取的措施是:加一个假的head或者是dummy节点。代码漂亮多了。
public ListNode removeElementsV3(ListNode head, int val) {ListNode dummy = new ListNode(-1);dummy .next = head;ListNode node = head;ListNode preNode = dummy ;while(node!=null){if(node.val==val){preNode.next = node.next;}else{preNode = node;}node = node.next;}return dummy .next;}
代码
160 Intersection of Two Linked Lists
思路:两个链表相交的起点。方法一:暴力搜索。listA的每个节点与listB的每个节点比较。时间复杂度O(m*n)。方法二:将listA的每个节点放入map中,遍历listB的每个节点与map比较。时间复杂度O(n),空间复杂度O(n)。这两个都不符合要求。
学习:难点是能够观察到:如果listA和listB有交集,那么从交集点开始之后所有的node是相等的,那么长度也是相同的。所以如果有长度差异,一定是在列表的前端。正如demo中所示。可以分别计算两个列表的长度lenA,lenB。长的列表先移动|lenA-lenB|步;之后两个指针一起移动直到所指节点相同。
代码
19 Remove Nth Node From End of List
思路:从尾部开始计算,删除第n个节点。
如果是从头部开始计算,删除第n个节点就比较好处理了。那就把问题转换一下。从尾部开始的第n个节点=从头部开始的第几个节点。
经过画图举例知道 应该是从头部开始第(len-n+1)节点,len是list长度。
假设要删除的节点nodeA ,应该是nodeA的上一个节点.next = nodeA.next。需要处理nodeA的上一个节点不存在的情况,这里加入一个dummy节点。
思路2:使用两个指针first、second。first先走n+1个节点(因为有dummy节点),然后second再走。当first到达队尾的时候,second距离队尾还有n个节点。
学习网页
代码
23 Merge k Sorted Lists
合并k个已经排序好的列表。这个题目有多种解决思路。参考文章。
思路1:把所有数据放在一个List,然后排序list。最后再创建ListNode。时间复杂度O(NlogN)O(NlogN),N是所有节点的总数。所有数据放入List需要O(n);排序需要O(NlogN)O(NlogN),创建节点需要O(N)。空间复杂度O(N)。
思路2:每次比较k个头节点,选择最小的节点。然后继续比较头节点。时间复杂度:O(kN)。k是数组长度,N是节点总数。每次比较需要比较k-1次,有N次比较。
思路3:对思路2的改进。每次把头结点的值放入优先队列中,取得最小值。时间复杂度:(O(Nlogk))(O(Nlogk))。插入和弹出优先队列需要耗时O(logk)O(logk),获得最小值耗时O(1)。
思路4:把k个队列合并问题转为2个队列合并问题。可以第0个和第1个合并得到result;result再继续和第2个、第3个队列合并。时间复杂度:O(kN)O(kN)。N是两个list的长度。k个队列需要合并k-1次。
思路5:对思路4的改进。使用分治法(Divide and conquer)。合并0,1队列得到0队列;合并2,3队列得到2队列….;第二层 合并0,2队列得到0队列,合并4,6队列得到4队列;第三层合并0,4队列得到0队列…..一直到结束。时间复杂度:O(Nlogk)O(Nlogk)。N是两个队列的节点总数。因为会合并logk次。
代码
25 Reverse Nodes in k-Group
思路:将整个列表分成几个长度为k的子链表;对每一个子链表反转。长度不为k的子链表保持不动。
思路1:找到每一个子链表开始、结束下标,然后调用 92 Reverse Linked List II 。
这样会有一些重复的跳过m个节点的操作。可以进一步优化。从head开始数,数够了k步就从head开始反转长度为k的子链表;接着再从当前节点开始数。够k个就反转。不够就退出。
学习:自己反转的代码写的有点凌乱。再次学习了递归思想。
如果要反转链表1->2->3->4->5中1到3的部分得到:3->2->1->4->5。左侧从左向右是一种思路,右侧从右向左也是一种方法。
public ListNode reverseKGroupV2(ListNode head, int k) {ListNode currentNode = head;int count =0;while(currentNode!=null && count!=k){count++;currentNode = currentNode.next;}if(count == k ){currentNode = reverseKGroupV2(currentNode,k);while(count>0){ListNode tmp = head.next;head.next = currentNode;currentNode = head;head = tmp;count--;}head = currentNode;}return head;}
代码
61 Rotate List
思路:因为k可能大于len(链表长度)。所以1 计算列表长度;2 k=k % len;3 将指针移动到len-k的位置,这个位置是开始rotate节点的上一个节点。4 反转。
代码
82 Remove Duplicates from Sorted List II
思路:留下链表中没有重复出现的元素。比较简单。直观地解决就可以。
代码
86 Partition List
思路:用两个指针smallHead,biggerHead分别指向小于x的节点列表和大于等于x的节点列表。最后将两个列表合并返回。
代码
143 Reorder List
思路:1 找到一半节点;2 反转第二部分链表;3合并两个链表。重要的是代码怎么写。
代码
147 Insertion Sort List
思路:处理位置i,从0到i-1是已经排序好的。我写代码的时候还是受数组排序的思维影响比较重。在学习代码中,别人把排序好的作为一个list处理。相对来说要比较好。
代码