可以认为无论Hash表存储多少数据,增删改查的时间复杂度都是常数级别O(1),但是常数时间比较大
如果Hash表存储是Key是基本类型比如:Interge,Double,String…那么Hash表内部传递的过程是按值传递的,在Hash表中所占的空间就是这个基本数据类型需要的空间(Hash表会拷贝一份值)
如果Key存储的不是基本类型,那么内部的传递过程是按照内存地址(引用)传递的,所占的空间一律是8字节
增删改查的时间复杂度是O(logN)
非基础类型的数在加入顺序表的时候,都需要把对应的比较器也一起传递过去
双指针
快慢指针
class Solution { public boolean isPalindrome (ListNode head) { ListNode slow = head, fast = head; int count = 0 ; while (slow != null ){ count++; slow = slow.next; } if (count == 1 ){ return true ; } if (count == 2 ){ return head.val == head.next.val; } slow = head; if (count % 2 == 0 ){ fast = fast.next.next; while (fast != null ){ fast = fast.next.next; slow = slow.next; } }else { while (fast.next != null ){ fast = fast.next.next; slow = slow.next; } } ListNode last = reverse(slow); fast = last; slow = head; while (slow != null && fast != null ){ if (slow.val != fast.val){ return false ; } slow = slow.next; fast = fast.next; } reverse(last); return true ; } public static ListNode reverse (ListNode head) { ListNode pre = null ; ListNode next = head.next; while (head.next != null ){ head.next = pre; pre = head; head = next; next = head.next; } head.next = pre; return head; } }
笔试做法:把单链表每一个节点放到数组里面去,然后在数组里面利用快排的Partition,完成后重新组合为一个数组
活用链表的性质:使用六个变量,大于区域的头和尾,等于区域的头和尾,小于区域的头和尾,遍历一遍链表,把各个节点分配给各个区域的头和尾巴,最后把个个区域连接起来即可(最后连接的时候需要注意边界条件,因为三个区域可能有2个或者1个为空区域,空指针如果调用方法会报错)
要梳理一下这种逻辑
这道题一开始我还没理解,难点在于如何找到新链表节点的next指针指向的新节点和rand指针指向的新节点(可以使用HashMap)分别存储老节点和对应的克隆节点,之后依次复制就可以了
不用HashMap的方式:
每次复制的新节点,可以连接在老节点的next指针上,第一轮先把全部的节点复制完毕,之后的一轮循环用于设置新节点的rand指针,根据老节点的rand指针,我们可以找到与之对应的新节点,这个新节点就在老节点rand指针指向的节点的下一个节点,最后一轮循环,把新老节点分离组成新老链表(省去HashMap记录节点的位置,用节点本身去记录了位置信息)
首先需要判断链表是否有环
第一种方法:遍历链表的同时把遍历的节点送入HashMap,送入HashMap之前判断HashMap中是否存在这个节点,如果存在的,那么遍历结束,这个节点就是入环的第一个节点,如果遍历到最后也没有发现重复的节点,那么就不存在环(这种方法实现容易,但是额外空间复杂度较高)
第二种方法:使用快慢指针,如果有环,慢指针走的慢,快指针走的快,那么快慢指针一定会相遇(而且在环内快指针走的圈数不会超过2圈)
假设慢指针入环的时候快指针与满指针之间的距离为 L ,快指针的速度是慢指针的N倍,慢指针速度为 1,那么快指针与满指针相遇的方程为(L + T )= N * T – > T = L / (N - 1) (在环中,L 最大是一圈,假设L取最大) 那么 快指针运动的圈数Y就是Y = N / (N - 1)[N = 2 , Y = 2, N = 3, Y = 1.5 , 当 N 取 + inf Y = 1 ],Ymax = 2,是个常数圈,不会耗费太多时间
当快慢指针相遇后,把快指针置于头节点(把快指针速度设置为和慢指针相同),下一次快慢指针相遇的节点,一定是入环的节点。
这个结论可以通过数学证明来解释。假设链表头节点到环入口节点的距离为A,环的长度为B,快慢指针第一次相遇的位置到环入口节点的距离为C。根据快指针速度是慢指针速度的两倍,第一次相遇时,快指针在环内至少比慢指针多走了一个完整的环的长度B。 因此,我们可以得到以下公式: 慢指针走的距离:A + C 快指针走的距离:A + C + nB(n为快指针在环内走的圈数) 因为快指针速度是慢指针速度的两倍,所以快指针走的距离是慢指针走的距离的两倍,即 2(A + C) = A + C + nB 化简可得 A = (n-1)B + (B - C) 这个公式说明,链表头节点到环入口节点的距离A等于快指针多走n-1圈的长度(n为正整数),再加上慢指针和快指针在环内相遇点到环入口的距离差。 当快指针回到头节点时,慢指针在环内已经走了(n-1)圈加上(B-C)的距离。此时,快指针和慢指针的距离为A,即链表头节点到环入口节点的距离。因此,下一次快慢指针相遇的位置一定是环入口节点。 因此,通过这种方法,我们可以在O(B)的时间复杂度内找到环入口节点,其中B是环的长度。
如果两个链表都是无环链表
首先遍历找到两个链表的尾节点,并记录两个链表的长度,如果链表的尾节点不同,那么不相交,如果相同,那么相交,进入下一步
长链表先走差值步(链表长度之差),之后两个链表一起走,相遇的第一个节点就是相交节点
如果一个是有环链表,一个是无环链表,这两个链表不可能相交
如果两个都是有环链表,分为以上三种情况
如果两个入环节点相同,就是第二种情况,这种情况下可以转换为两个无环链表的方式,把入环节点看作尾节点
情况 1 3 可以抽象为一种情况,让一个入环节点继续往下,如果在回到自己的过程中遇到了另外一个入环节点,就是相交,如果没有遇到另外一个入环节点,就是不相交。
创建链表数组 /** * * @param lists 一个二维整形数组,根据二维整形数组创建链表数组 * @return */ public static ListNode[] generateLists(int[][] lists){ ListNode[] list = new ListNode[lists.length]; for (int i = 0; i < lists.length; i++) { ListNode head = null; if(lists[i].length > 0){ head = new ListNode(lists[i][0], null);; } ListNode tmp = head; for (int j = 1; j < lists[i].length; j++) { head.next = new ListNode(lists[i][j], null); head = head.next; } list[i] = tmp; } return list; }
检查链表是否有环 /** * 检查链表是否有环 * @param head 链表头节点 * @return 有环则返回第一个环节点,无环则返回null */ public static ListNode isLoop(ListNode head){ ListNode fast = head, slow = head; if(head == null head.next == null){ return null; } fast = fast.next.next; slow = slow.next; while (fast != slow){ if(fast == null fast.next == null){ return null; } fast = fast.next.next; slow = slow.next; } fast = head; while (fast != slow ){ fast = fast.next; slow = slow.next; } return fast; }
LeetCode 合并 K 个升序链表 解法一:两个两个处理 public ListNode mergeKLists(ListNode[] lists) { if(lists.length == 0){ return null; } ListNode head = lists[0]; for (int i = 1; i < lists.length; i++){ head = mergeTwoList(head, lists[i]); } return head; } public static ListNode mergeTwoList(ListNode head1, ListNode head2){ ListNode head = new ListNode(); ListNode tmp = head; while(head1 != null && head2 != null){ tmp.next = head1.val > head2.val ? head2 : head1; tmp = tmp.next; if(head1 == tmp){ head1 = head1.next; }else{ head2 = head2.next; } } if(head1 == null){ tmp.next = head2; }else{ tmp.next = head1; } return head.next; }
解法二:优先队列 用有限队列因为底层的小根堆不具有稳定性,所以建立的链表可能成环,需要把每个节点的next指针都设置为null,避免成环
public ListNode mergeKLists(ListNode[] lists) { PriorityQueue<ListNode> queue = new PriorityQueue<ListNode>(new ListNodeComparator()); ListNode head; for (int i = 0; i < lists.length; i++) { head = lists[i]; while (head != null){ queue.add(head); ListNode tmp = head; head = head.next; tmp.next = null; } } head = queue.poll(); ListNode tmp = head; while (!queue.isEmpty()){ tmp.next = queue.poll(); tmp = tmp.next; } return head; } class ListNodeComparator implements Comparator<ListNode> { @Override public int compare(ListNode t1, ListNode t2) { return t1.val - t2.val; } }