【算法数据结构体系篇class09】:链表问题:快慢指针、回文结构、复制、中点,分区、相交
一、链表解题的方法论
1)对于笔试,不用太在乎空间复杂度,一切为了时间复杂度
2)对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法
二、链表常用数据结构和技巧
1)使用容器(哈希表、数组等)
2)快慢指针
三、快慢指针
1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点
2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点
3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个
4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个
代码演示:
package class09;import java.util.ArrayList;public class LinkedListMid {public static class Node{public int value;public Node next;public Node(int value){this.value = value;}}/**链表求各种中点问题:* 1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点** 2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点** 3)输入链表头节点,奇数长度返回中点前一个,偶数长度返回上中点前一个** 4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个*///1)输入链表头节点,奇数长度返回中点,偶数长度返回上中点 比如[2->3->5->6] 返回3节点public static Node midOrUpMidNode(Node head){//特殊情况:如果节点个数小于3个if(head == null || head.next == null || head.next.next == null){return head;}//节点3个及以上Node slow = head.next;Node fast = head.next.next;while(fast.next != null && fast.next.next != null){slow = slow.next;fast = fast.next.next;}return slow;}//2)输入链表头节点,奇数长度返回中点,偶数长度返回下中点 比如[2->3->5->6] 返回5节点public static Node midOrDownMidNode(Node head){//如果头节点空 或者只有头节点 就返回头节点if(head == null || head.next ==null){return head;}//快慢指针都同步来到头节点的下节点。这样进行指针前移,当前到边界时 slow就能确保在符合题意的位置Node slow = head.next;Node fast = head.next;while(fast.next != null && fast.next.next != null){slow =slow.next;fast = fast.next.next;}return slow;}//3)输入链表头节点,奇数长度返回中点前一个, 偶数长度返回上中点前一个 比如[2->3->5] 返回2节点 比如[2->3->5->6] 返回2节点public static Node midOrUpMidPreNode(Node head){//假如空节点,只有头节点 只有两个节点 这些情况下,都是返回溢出的节点,统一返回nullif(head == null || head.next == null || head.next.next ==null){return null;}//有三个及以上节点时Node slow = head;Node fast = head.next.next;while(fast.next != null && fast.next.next != null){slow = slow.next;fast = fast.next.next;}return slow;}//4)输入链表头节点,奇数长度返回中点前一个,偶数长度返回下中点前一个 比如[2->3->5] 返回2节点 比如[2->3->5->6] 返回3节点public static Node midOrDownMidPreNode(Node head){//当空 只有头节点 那么就是返回溢出的节点 直接返回Nullif(head == null || head.next == null){return null;}Node slow = head;Node fast = head.next;while(fast.next != null && fast.next.next != null){slow = slow.next;fast = fast.next.next;}return slow;}public static Node right1(Node head) {if (head == null) {return null;}Node cur = head;ArrayList<Node> arr = new ArrayList<>();while (cur != null) {arr.add(cur);cur = cur.next;}return arr.get((arr.size() - 1) / 2);}public static Node right2(Node head) {if (head == null) {return null;}Node cur = head;ArrayList<Node> arr = new ArrayList<>();while (cur != null) {arr.add(cur);cur = cur.next;}return arr.get(arr.size() / 2);}public static Node right3(Node head) {if (head == null || head.next == null || head.next.next == null) {return null;}Node cur = head;ArrayList<Node> arr = new ArrayList<>();while (cur != null) {arr.add(cur);cur = cur.next;}return arr.get((arr.size() - 3) / 2);}public static Node right4(Node head) {if (head == null || head.next == null) {return null;}Node cur = head;ArrayList<Node> arr = new ArrayList<>();while (cur != null) {arr.add(cur);cur = cur.next;}return arr.get((arr.size() - 2) / 2);}public static void main(String[] args) {Node test = null;test = new Node(0);test.next = new Node(1);test.next.next = new Node(2);test.next.next.next = new Node(3);test.next.next.next.next = new Node(4);test.next.next.next.next.next = new Node(5);test.next.next.next.next.next.next = new Node(6);test.next.next.next.next.next.next.next = new Node(7);test.next.next.next.next.next.next.next.next = new Node(8);Node ans1 = null;Node ans2 = null;ans1 = midOrUpMidNode(test);ans2 = right1(test);System.out.println(ans1 != null ? ans1.value : "无");System.out.println(ans2 != null ? ans2.value : "无");ans1 = midOrDownMidNode(test);ans2 = right2(test);System.out.println(ans1 != null ? ans1.value : "无");System.out.println(ans2 != null ? ans2.value : "无");ans1 = midOrUpMidPreNode(test);ans2 = right3(test);System.out.println(ans1 != null ? ans1.value : "无");System.out.println(ans2 != null ? ans2.value : "无");ans1 = midOrDownMidPreNode(test);ans2 = right4(test);System.out.println(ans1 != null ? ans1.value : "无");System.out.println(ans2 != null ? ans2.value : "无");}
}
四、给定一个单链表的头节点head,请判断该链表是否为回文结构。
题意:比如 12321 12344321从左往右读 跟从右往左读是一样的
核心思路:
1)栈方法特别简单(笔试用)
2)改原链表顺序的方法就需要注意边界了(面试用)
代码演示:
package class09;import java.util.Stack;/*** 给定一个单链表的头节点head,请判断该链表是否为回文结构。* 比如 12321 12344321从左往右读 跟从右往左读是一样的*/
public class IsPalindromeList {public static class Node {public int value;public Node next;public Node(int v) {value = v;}}//额外空间O(N) 用入栈出栈, 出栈顺序即为反序public static boolean isPalindrome1(Node head) {if (head == null || head.next == null) return true;//定义一个栈存链表元素Stack<Node> stack = new Stack<>();Node cur = head;while (cur != null) {stack.push(cur);cur = cur.next;}cur = head;while (cur != null) {if (stack.pop().value != cur.value) {return false;}cur = cur.next;}return true;}//额外空间O(N/2) 用入栈出栈, 入栈后一半元素,然后与前一半比较 出栈顺序即为反序public static boolean isPalindrome2(Node head) {if (head == null || head.next == null) return true;//使用快慢指针,让慢指针落到中点,如果是偶数个 那么就落到下中点,12344321 即落到第二个4 ,1111 第三个1, 11,第二个1//如果时奇数个,那么就落在中点下个节点 1234321 即落到第二个3//这两种不管怎么落,就从当前指针往后的元素入栈,最后依次出栈与头节点比较。 只要是回文结构都是相等的。Node slow = head.next;Node fast = head;while (fast.next != null && fast.next.next != null) {slow = slow.next;fast = fast.next.next;}//后半段元素入栈Stack<Node> stack = new Stack<>();while (slow != null) {stack.push(slow);slow = slow.next;}//利用快指针内存重定向头节点fast = head;while (!stack.isEmpty()) {if (stack.pop().value != fast.value) {return false;}fast = fast.next;}return true;}//额外空间O(1) 反转原链表后半段,与前半段对比,最后再把后半段链表还原,即再反转public static boolean isPalindrome3(Node head) {if (head == null || head.next == null) return true;Node n1 = head;Node n2 = head;//1.快慢指针找到中点,如果是偶数 那么中点就落在了中点上节点 12344321 落在第一个4while (n2.next != null && n2.next.next != null) {n1 = n1.next;n2 = n2.next.next;}//2. 定义n2重定向在n1中点下节点 12344321 落在第二个4 1234321 落在第二个3n2 = n1.next;//3.将上中点的next指针指向null, 这样做是为了与后半链表循环比较时有跳出循环的条件。n1.next = null;Node n3 = null;//辅助变量保存下个节点while (n2 != null) {n3 = n2.next;n2.next = n1;n1 = n2;n2 = n3;}//4.跳出循环时n1则落在最后一个节点 1->2->3->4 <- 4<-3<-2<-1 最后的1,将n2从定向在head节点 开始头尾比较相等n2 = head;//5.注意要保存n1节点在n3 最后还需要将其顺序反转回原链表顺序n3 = n1;//6.遍历n1 n2 头尾节点 如果是回文结构那么肯定相等,否则返回false 因为此时第一个4.next=null。判断到中点则会跳出循环while (n1 != null && n2 != null) {if (n1.value != n2.value) return false;n1 = n1.next;n2 = n2.next;}//7.此时如果符合,那么最后就是将后面 44321反转 在利用前面指针变量进行反转//可以提前先将下个指向保存,然后最后一个元素n3指向为空,形成4 <- 4<-3<-2 1n1 = n3.next; //curn3.next = null; //prewhile(n1 != null){n2 = n1.next; //利用n2保存下一个元素n1.next = n3;n3 = n1;n1 = n2;}return true;}public static void printLinkedList(Node node) {System.out.print("Linked List: ");while (node != null) {System.out.print(node.value + " ");node = node.next;}System.out.println();}public static void main(String[] args) {Node head = null;printLinkedList(head);System.out.print(isPalindrome1(head) + " | ");System.out.print(isPalindrome2(head) + " | ");System.out.println(isPalindrome3(head) + " | ");printLinkedList(head);System.out.println("=========================");head = new Node(1);printLinkedList(head);System.out.print(isPalindrome1(head) + " | ");System.out.print(isPalindrome2(head) + " | ");System.out.println(isPalindrome3(head) + " | ");printLinkedList(head);System.out.println("=========================");head = new Node(1);head.next = new Node(2);printLinkedList(head);System.out.print(isPalindrome1(head) + " | ");System.out.print(isPalindrome2(head) + " | ");System.out.println(isPalindrome3(head) + " | ");printLinkedList(head);System.out.println("=========================");head = new Node(1);head.next = new Node(1);printLinkedList(head);System.out.print(isPalindrome1(head) + " | ");System.out.print(isPalindrome2(head) + " | ");System.out.println(isPalindrome3(head) + " | ");printLinkedList(head);System.out.println("=========================");head = new Node(1);head.next = new Node(2);head.next.next = new Node(3);printLinkedList(head);System.out.print(isPalindrome1(head) + " | ");System.out.print(isPalindrome2(head) + " | ");System.out.println(isPalindrome3(head) + " | ");printLinkedList(head);System.out.println("=========================");head = new Node(1);head.next = new Node(2);head.next.next = new Node(1);printLinkedList(head);System.out.print(isPalindrome1(head) + " | ");System.out.print(isPalindrome2(head) + " | ");System.out.println(isPalindrome3(head) + " | ");printLinkedList(head);System.out.println("=========================");head = new Node(1);head.next = new Node(2);head.next.next = new Node(3);head.next.next.next = new Node(1);printLinkedList(head);System.out.print(isPalindrome1(head) + " | ");System.out.print(isPalindrome2(head) + " | ");System.out.println(isPalindrome3(head) + " | ");printLinkedList(head);System.out.println("=========================");head = new Node(1);head.next = new Node(2);head.next.next = new Node(2);head.next.next.next = new Node(1);printLinkedList(head);System.out.print(isPalindrome1(head) + " | ");System.out.print(isPalindrome2(head) + " | ");System.out.println(isPalindrome3(head) + " | ");printLinkedList(head);System.out.println("=========================");head = new Node(1);head.next = new Node(2);head.next.next = new Node(3);head.next.next.next = new Node(2);head.next.next.next.next = new Node(1);printLinkedList(head);System.out.print(isPalindrome1(head) + " | ");System.out.print(isPalindrome2(head) + " | ");System.out.println(isPalindrome3(head) + " | ");printLinkedList(head);System.out.println("=========================");}}
五、将单向链表按某值划分成左边小、中间相等、右边大的形式
核心思路:
1)把链表放入数组里,在数组上做partition(笔试用)
2)分成小、中、大三部分,再把各个部分之间串起来(面试用)
package class09;import class08.TrieTree;/*** 将单向链表按某值划分成左边小、中间相等、右边大的形式** 1)把链表放入数组里,在数组上做partition** 2)分成小、中、大三部分,再把各个部分之间串起来*/
public class SmallerEqualBigger {public static class Node{public int value;public Node next;public Node(int v) {value = v;}}//1)把链表放入数组里,在数组上做partition 额外空间复杂度O(N)//数组做好分区操作,就遍历节点 依次重新指向下节点,注意最后一个节点要指向NULLpublic static Node listPartition1(Node head, int pivot){if(head == null) return head;//定义辅助变量遍历链表 判断链表大小Node cur = head;int size = 0;while(cur != null){size++;cur = cur.next;}cur = head; //重新指向头节点//定义数组存放节点Node[]nodeArr = new Node[size];for (int i = 0;i<nodeArr.length;i++){nodeArr[i] = cur;cur = cur.next;}//进行partition分区操作,按pivot值做划分partition(nodeArr,pivot);//节点重新定于指向,按照排好序的数组for(int i =1;i<nodeArr.length;i++){nodeArr[i-1].next = nodeArr[i];}//最后的节点的next指向注意要指向nullnodeArr[nodeArr.length-1].next = null;return nodeArr[0];}public static void partition(Node[] nodeArr, int pivot) {int less = -1;//注意大于区边界,是从最右元素+1的边界开始,因为这里划分值不是最后一个元素,需要一起判断int big = nodeArr.length;int index = 0;//开始分区交换,直到下标索引遇到右侧的大于区左边界big就停止while(index < big){if(nodeArr[index].value < pivot){swap(nodeArr,++less,index++);}else if(nodeArr[index].value == pivot){index++;}else {swap(nodeArr,--big,index);}}}public static void swap(Node[] arr,int i,int j){Node temp = arr[i];arr[i] = arr[j];arr[j] = temp;}//2)分成小、中、大三部分,再把各个部分之间串起来 额外复杂度三个区间有限变量 O(1)根据题意分成三个区间,定义小于区[sH,ST]、等于区[eH,eT]、大于区[mH,mT],每个区间都有头尾//最好分别把对应的值填入三个区间链表,再将小于区尾 -> 等于区头 -> 大于区头 指向起来。public static Node listPartition2(Node head, int pivot){if(head == null) return null;Node sH = null;Node sT = null;Node eH = null;Node eT = null;Node mH = null;Node mT = null;Node next = null; //辅助变量保存下个指向节点//开始将节点放入到三个链表中while (head != null){next = head.next; //保存下个节点head.next = null; //清空指向节点 赋值为null,后面后依次进行重新排序指向if(head.value < pivot){//当前值小于划分值,就进入小于区 假如小于区没有头节点 说明是第一个节点 直接赋值头尾if(sH==null){sH = head;sT = head;}else{ //如果存在区间有值,那么就接在后面,保持原链表的稳定性 顺序次序不变sT.next = head;sT = head;}//等于就放入等于区间链表}else if(head.value == pivot){if(eH == null){eH = head;eT = head;}else{eT.next = head;eT = head;}}else{//大于就放入大于区间链表if(mH == null){mH = head;mT = head;}else{mT.next = head;mT = head;}}//当前节点判断放哪个链表区间后,就接着指向下个节点head = next;}//放完三个链表区间后,要进行三个链表的指向,注意边界判断:// 假设小于区头节点不为空,那么就表示右小于区值,尾部指向等于区头部,此时等于区是否为空,都不影响if(sH != null){sT.next = eH;//接下来就是要连接大于区间,但是需要判断是小于区连接还是等于区,有可能等于区为空,那就是小于区连接大于区//如果等于区头不为空,那么eT还是eT,保持不变进行连接大于区的头,假如为空,那么就需要赋值成小于区的尾部,进行连接大于区的头eT = eT != null? eT : sT; //谁连接大于区头 谁就是eT}// 下一步,一定是需要用eT 去接 大于区域的头// 有等于区域,eT -> 等于区域的尾结点// 无等于区域,eT -> 小于区域的尾结点// eT 尽量不为空的尾巴节点if(eT != null){eT.next = mH; //非空则将eT指向大于区头}//连接完成,但是返回要判断头是否为空//如果小于区头非空 那么就返回sH,如果空,//就判断等于区头是否空,如果非空,那么就返回eH,如果空,//就返回大于区头mHreturn sH != null? sH:(eH!=null)?eH:mH;}public static void printLinkedList(Node node) {System.out.print("Linked List: ");while (node != null) {System.out.print(node.value + " ");node = node.next;}System.out.println();}public static void main(String[] args) {Node head1 = new Node(7);head1.next = new Node(9);head1.next.next = new Node(1);head1.next.next.next = new Node(8);head1.next.next.next.next = new Node(5);head1.next.next.next.next.next = new Node(2);head1.next.next.next.next.next.next = new Node(5);printLinkedList(head1);// head1 = listPartition1(head1, 4);head1 = listPartition2(head1, 5);printLinkedList(head1);Node head2 = new Node(7);head2.next = new Node(9);head2.next.next = new Node(1);head2.next.next.next = new Node(8);head2.next.next.next.next = new Node(5);head2.next.next.next.next.next = new Node(2);head2.next.next.next.next.next.next = new Node(5);printLinkedList(head2);head2 = listPartition1(head2, 5);printLinkedList(head2);}
}
六、138. 复制带随机指针的链表
138. 复制带随机指针的链表



核心思路:
1.通过哈希表保存节点,老节点为key,新节点为value 然后依次进行赋值next和random指针
这里特殊点在于random是随机指向,第一个可以指向最后一个,所以需要先获取出全部元素 再进行赋值
2.不使用哈希表的方式,减少空间复杂度,可以将复制的节点,依次放到对应节点的nex指针下 比如:
1->2->3->4 => 1-> copy1 -> 2 -> copy2 -> 3 -> copy3 -> 4 -> copy4,然后调整random指针,最后就将新老节点next指向分离出来 恢复原链表 返回复制链表头节点
package class09;import java.util.HashMap;/*** 一种特殊的单链表节点类描述如下* class Node {* int value;* Node next;* Node rand;* Node(int val) { value = val; }* }* rand指针是单链表节点结构中新增的指针,rand可能指向链表中的任意一个节点,也可能指向null。* 给定一个由Node节点类型组成的无环单链表的头节点 head,请实现一个函数完成这个链表的复制,并返回复制的新链表的头节点。* 【要求】* 时间复杂度O(N),额外空间复杂度O(1)*/
public class CopyListWithRandom {public static class Node{public int val;public Node next;public Node random;public Node(int v){val = v;next = null;random = null;}}//通过哈希表保存节点,老节点为key,新节点为value 然后依次进行赋值next和random指针//这里特殊点在于random是随机指向,第一个可以指向最后一个,所以需要先获取出全部元素 再进行赋值public static Node copyRandomList1(Node head){if(head == null)return null;//定义哈希表,保存节点,key对应的就是一个复制的节点HashMap<Node,Node> map = new HashMap<>();Node cur = head;while (cur != null){//每次进行遍历赋值新生成一个对应节点map.put(cur,new Node(cur.val));cur = cur.next;}//节点生成好之后就是赋值next random指针。//map.get(node).next 新节点 next=》 map.get(node.next) node.next表示老节点的next。 通过map获取得到新节点nextcur = head;while(cur != null){//新节点cur map.get(cur)的next指向 就是对应老节点cur的next指向,cur.next。 其指向对应的新节点就是map.get(cur.next)map.get(cur).next = map.get(cur.next);map.get(cur).random = map.get(cur.random);cur = cur.next;}return map.get(head);}//不使用哈希表的方式,减少空间复杂度,可以将复制的节点,依次放到对应节点的nex指针下 比如://1->2->3->4 => 1-> copy1 -> 2 -> copy2 -> 3 -> copy3 -> 4 -> copy4public static Node copyRandomList2(Node head){if(head == null) return null;Node cur = head;Node next = null;//将复制节点,都依次放到对应老节点的next 1-> copy1 -> 2 -> copy2 -> 3 -> copy3 -> 4 -> copy4while(cur != null){// 1-->2 把copy1放到两者中间 1 -> copy1 >2next = cur.next;cur.next = new Node(cur.val);cur.next.next = next;cur = next;}cur = head;//先将节点的random指针调整好while (cur != null){//1-> copy1 ->2 注意保存原节点的next 2next = cur.next.next;//cur.next.random新节点的random指向 就是cur.random老节点的指向一致,对应的是再其next,cur.random.nextcur.next.random = cur.random != null?cur.random.next:null;cur = next;}//最后分离出新老节点next指向cur = head;//新节点的头节点Node ans = head.next;//新节点遍历指针Node newNode = null;while(cur != null){//新节点的nextnext = cur.next.next;//当前对应的新节点newNode = cur.next;//分离新节点 旧节点next指向之前自己的nextcur.next = next;//新节点next 分离 指向自己的next 也就是对应老节点的nextnewNode.next = next!=null?next.next:null;cur = next;}return ans;}
}
七、给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null
【要求】
* 如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。
核心思路:
空间复杂度如果不限制,那么可以将两链表保存在数组,然后依次从头遍历如果有相等的就表示首个相交点
* 1.分类进行判断 两个链表都没有环,那么相交节点往后的节点肯定都是一样的。不可能后面还交叉,节点只有一个next
* 就可以判断如果相交了 那么最后两链表节点是肯定相等的 。确定相交后,再判断两个链表长度,较长的链表减去两链表长度差值
* 后开始与较短的链表同时遍历,一旦相等,就是相交第一个节点
* 2.两个链表一个有环,一个没环,那么肯定是没有相交的节点的 没有这种符合链表结构,因为一定会出现有节点存在两个next指针
* 3.两个链表有环,那相交节点 会存在两种形式。
* 两个链表环节点1即第一个相交点(这里也有可能相交节点也是入环首节点,判断逻辑都一样 ), 另一种就是 1 2 都可以认为是第一相交节点
* 第一种如两链表长度不同,那么就较长的链表减去两链表长度差值,后开始与较短的链表同时遍历,一旦相等,就是相交第一个节点
* 第二种就是以一个链表的入环节点1.next节点开始遍历,直到走回入环节点,过程遇到另外一个链表的入环节点2则表示相交。返回1或2都认为是相交首节点
* * * *
* * * *
1 1 * 2
* * *
* * * * *
* * *
代码演示:
package class10;/*** 给定两个可能有环也可能无环的单链表,头节点head1和head2。请实现一个函数,如果两个链表相交,请返回相交的 第一个节点。如果不相交,返回null* 【要求】* 如果两个链表长度之和为N,时间复杂度请达到O(N),额外空间复杂度 请达到O(1)。* <p>* 思路:(空间复杂度如果不限制,那么可以将两链表保存在数组,然后依次从头遍历如果有相等的就表示首个相交点)* 1.分类进行判断 两个链表都没有环,那么相交节点往后的节点肯定都是一样的。不可能后面还交叉,节点只有一个next* 就可以判断如果相交了 那么最后两链表节点是肯定相等的 。确定相交后,再判断两个链表长度,较长的链表减去两链表长度差值* 后开始与较短的链表同时遍历,一旦相等,就是相交第一个节点* 2.两个链表一个有环,一个没环,那么肯定是没有相交的节点的 没有这种符合链表结构,因为一定会出现有节点存在两个next指针* 3.两个链表有环,那相交节点 会存在两种形式。* 两个链表环节点1即第一个相交点(这里也有可能相交节点也是入环首节点,判断逻辑都一样 ), 另一种就是 1 2 都可以认为是第一相交节点* 第一种如两链表长度不同,那么就较长的链表减去两链表长度差值,后开始与较短的链表同时遍历,一旦相等,就是相交第一个节点* 第二种就是以一个链表的入环节点1.next节点开始遍历,直到走回入环节点,过程遇到另外一个链表的入环节点2则表示相交。返回1或2都认为是相交首节点* * * * ** * * * ** 1 1 * 2* * * ** * * * * ** * * **/
public class FindFirstIntersectNode {public static class Node {public int value;public Node next;public Node(int v) {value = v;}}//判断两链表是否相交,相交则返回第一个节点 不相交就返回nullpublic static Node getIntersectNode(Node head1, Node head2) {if (head1 == null || head2 == null) return null;//获取两链表的入环首节点 无环则返回nullNode loop1 = getLoopNode(head1);Node loop2 = getLoopNode(head2);//判断 两链表没有环 两链表都有环的两种情况if (loop1 == null && loop2 == null) {//没环的两个链表头节点入参进行获取相交首节点return noLoop(head1, head2);}if (loop1 != null && loop2 != null) {//有环的两个链表,需要将头节点 入环首节点都入参return bothLoop(head1, loop1, head2, loop2);}//如果是一个链表有环,一个链表无环,那么肯定没有相交节点 返回nullreturn null;}//获取两个有环链表相交首节点,没有相交则返回nullpublic static Node bothLoop(Node head1, Node loop1, Node head2, Node loop2) {if(head1 == null || head2 == null) return null;//1.链表有环,那有两种情况//一个是两链表的入环首节点相等,那么将两链表等长位置开始往环首节点遍历过程如果有相等节点就表示相交首节点//另一个是入环首节点不相等,两个环节点都是相交首节点 返回哪个都可以if(loop1 == loop2){//如果入环首节点相交Node cur1 = head1;Node cur2 = head2;int n = 0;//定义较长链表与较短链表,这里注意 只需要遍历到loop 环节点,因为环内都是相同节点不需遍历while(cur1 != loop1){cur1 = cur1.next;n++;}while(cur2 != loop2){cur2 = cur2.next;n--;}cur1 = n > 0? head1:head2;cur2 = cur1 ==head1?head2:head1;//确定好长短链表 就将长链表cur1先走n步 再两链表同时走,相遇即为相交首节点 n要取绝对值n = Math.abs(n);while(n!=0){//递减差值直到0 较长链表cur1节点就来到和cur2同长节点n--;cur1 = cur1.next;}//遍历一定会存在一个相等节点 为相交首节点while(cur1 != cur2){cur1 = cur1.next;cur2 = cur2.next;}//相遇则返回其中一个节点即可return cur1;}else {//两链表入环首节点不相交 链表结构就是两个入环节点都是相交首节点//判断是否相交,就先遍历其中一个链表入环节点.next,绕一圈退出,假如有节点是与另外一个链表的入环节点一致 就表示相交 返回任意两链表一个入环节点Node cur = loop1.next;while(cur != loop1){//假如节点中有与head2链表的入环节点loop2相等 那么就表示有相交,返回相交节点就是两链表任意一个入环节点if(cur == loop2){return loop1;}cur = cur.next;}}//如果没相交 返回nullreturn null;}//获取两个无环链表相交首节点,没有相交则返回nullpublic static Node noLoop(Node head1, Node head2) {if (head1 == null || head2 == null) return null;Node cur1 = head1;Node cur2 = head2;int n = 0; //定义变量保存链表长度//1.先遍历head1while (cur1 != null) {n++;cur1 = cur1.next;}//2.再遍历head2 同时将head1的长度n 递减 看看最后n值 如果是大于0 那么表示head1链表较长while (cur2 != null) {n--;cur2 = cur2.next;}//3.两链表来到了最后节点,此时判断 如果两链表有相交节点,那么两链表的尾节点一定相等的,因为不可能相交后又交叉出去,链表节点只有一个nextif (cur1 != cur2) return null;//4.走到这里表示链表有相交,重定义cur1为较长的链表 cur2是较短的链表 假如n=0,两遍链表相等 等长不用交换cur1 = n > 0 ? head1 : head2;cur2 = cur1 == head1 ? head2 : head1;//4.将较长链表cur1先减去 差值n 需要取绝对值,假如n=0,那么就不用减,两链表直接从头节点开始遍历肯定会有相等的首节点n = Math.abs(n);while (n != 0) {cur1 = cur1.next;n--;}//5.执行完差值后 两链表cur1 cur2就开始往后等长,同时遍历 相遇时则是相交首节点while (cur1 != cur2) {cur1 = cur1.next;cur2 = cur2.next;}//6.最后返回相交首节点return cur1;}//获取链表入环第一个节点 如果没环返回nullpublic static Node getLoopNode(Node head) {if (head == null || head.next == null || head.next.next == null) return null;Node slow = head;Node fast = head;//先把快慢指针调整一次,下面的while条件才能执行起来 否则一开始都是头节点会存在相同情况slow = slow.next;fast = fast.next.next;//1.快慢指针遍历直到相遇while (slow != fast) {//如果快指针指向空 则表示链表没有环 那么就需要返回nullif (fast.next == null || fast.next.next == null) return null;slow = slow.next;fast = fast.next.next;}//2.快指针或慢指针其一重新指向头节点 然后再同步长进行指向,一旦快慢指针相等就表示来到入环的第一个节点fast = head;while (slow != fast) {slow = slow.next;fast = fast.next;}return slow;}public static void main(String[] args) {// 1->2->3->4->5->6->7->nullNode head1 = new Node(1);head1.next = new Node(2);head1.next.next = new Node(3);head1.next.next.next = new Node(4);head1.next.next.next.next = new Node(5);head1.next.next.next.next.next = new Node(6);head1.next.next.next.next.next.next = new Node(7);// 0->9->8->6->7->nullNode head2 = new Node(0);head2.next = new Node(9);head2.next.next = new Node(8);head2.next.next.next = head1.next.next.next.next.next; // 8->6System.out.println(getIntersectNode(head1, head2).value);// 1->2->3->4->5->6->7->4...head1 = new Node(1);head1.next = new Node(2);head1.next.next = new Node(3);head1.next.next.next = new Node(4);head1.next.next.next.next = new Node(5);head1.next.next.next.next.next = new Node(6);head1.next.next.next.next.next.next = new Node(7);head1.next.next.next.next.next.next = head1.next.next.next; // 7->4// 0->9->8->2...head2 = new Node(0);head2.next = new Node(9);head2.next.next = new Node(8);head2.next.next.next = head1.next; // 8->2System.out.println(getIntersectNode(head1, head2).value);// 0->9->8->6->4->5->6..head2 = new Node(0);head2.next = new Node(9);head2.next.next = new Node(8);head2.next.next.next = head1.next.next.next.next.next; // 8->6System.out.println(getIntersectNode(head1, head2).value);}
}
八、不给单链表的头节点,只给想要删除的节点,就能做到在链表上把这个点删掉
比如 1 -> 2 -> 3 -> 4 -> 5 ,链表 要删除指定节点,3, 但是不给链表头节点:
思路:
拿到的是指定的节点3位置, 可以将节点3.val = 节点3.next ,也就是3改成4数值。变成:
1 -> 2 -> 4 -> 4 -> 5
然后将节点3.next删除, 节点3.next = 节点3.next.next,变成:
1 -> 2 -> 4 ->5
相关文章:

【算法数据结构体系篇class09】:链表问题:快慢指针、回文结构、复制、中点,分区、相交
一、链表解题的方法论 1)对于笔试,不用太在乎空间复杂度,一切为了时间复杂度2)对于面试,时间复杂度依然放在第一位,但是一定要找到空间最省的方法二、链表常用数据结构和技巧1)使用容器(哈希表、数组等)2)快…...
实验室信息化管理行业方案
为适应新时代下的管理机制与应用场景,越来越多的检测实验室需对研发部门和实验部门进行全面的、现代化的、电子化的综合管理,帮助检测机构对实验室的规划与计划、项目立项与管理、项目成果、合同,以及基建等工作进行统一的管理,而…...

docker学习
docker 环境搭建 MySql # mysql5.7 docker run --name mysql10 -p 3306:3306 -v D:\MySql\conf:/etc/mysql/conf.d -v D:\MySql\data:/var/lib/mysql -e MYSQL_ROOT_PASSWORD123456 -d mysql:5.7docker run --name mysql10 -p 3306:3306 -v /etc/mysql/conf.d:/etc/mysql/co…...
Linux 常用命令
重启 # 重启(root 用户操作) reboot# 强制重启 reboot -f关机 # 关机 # shutdown [OPTION] [TIME] [MESSAGE] shutdown-h 关机 -r 重启-c 取消上一个命令 第二个参数指的是多少分钟后执行操作,以分钟为单位,如果不加时间&am…...
数据结构-顺序表(2)
目录 1. 线性表 2. 顺序表 2.1 动态顺序表 3. 接口实现 前期工作 3.1 初始化、销毁与检查容量 3.1.1 初始化 3.1.2 销毁 3.1.3 检查容量 3.2 尾插 3.3 尾删 3.4 头插 3.5 头删 3.6 插入 3.7 删除 顺序表源码 SeqList.h SeqList.c test.c 写在最后ÿ…...

初学C/C++内存管理--new和delete的使用
一,内存分布 栈区: 一般的局部变量和函数的返回数据以及返回地址,函数的参数都在战栈区上开辟空间。栈区开空间一般由编译器自动管理,出了生命周期自动释放。也可以通过一些方式自己手动开辟栈区空间,不过一般用不到…...

【Java】volatile
一、volatile volatile是Java虚拟机提供的轻量级的同步机制,它有3个特性: 1)保证可见性 2)不保证原子性 3)禁止指令重排 当写一个volatile变量时,JMM会把该…...
混沌工程 Chaos Mesh 实践经验(持续更新)
使用 k8s JVM故障 Linux内核版本 Linux 系统内核必须为 4.1 及以上版本。 不然会一直失败,可以从Chaos Mesh dashboard前端看到。 对native方法注入故障无效 实测对Thread.sleep(Long) 注入故障无效,猜测是因为对native方法无效,大概因为…...

追梦之旅【数据结构篇】——详解C语言实现链栈
详解C语言实现链栈~😎前言🙌整体实现内容分析💞1.头文件编码实现🙌2.功能文件编码实现🙌3.测试函数功能代码🙌总结撒花💞😎博客昵称:博客小梦 😊最喜欢的座右…...

oracle数据库常用操作
1.连接登录切换用户su - oracle以管理员模式登录到sqlplus:sqlplus / as sysdba oracle登录身份有三种:1.1Normal 普通身份;1.2.sysdba 系统管理员身份;若以 ‘sysdba’ 方式认证,登录用户为 ‘SYS’,为 Or…...

一文教会你如何在Linux系统中使用Docker安装Redis 、以及如何使用可视化工具连接【详细过程+图解】
文章目录1、安装redis2、在外部创建配置文件3、创建redis4、启动测试redis5、数据持久化存储6、使用可视化工具连接redis前言在windows上安装过reids、在linux上也安装过redis,但是都没有docker上安装redis方便。这里给出docer安装redis的相关教程1、安装redis 默认…...

mysql 内存架构
1. 背景 从 innodb 的整体架构中可以知道 innodb 的内存架构中分为 buffer pool 缓存区, change pool 修改缓冲区, adaptive hash index 自适应哈希索引, 和 log buffer 日志缓冲区. 2. buffer pool buffer pool 是用于缓冲磁盘页的数据,mysql 的80%的内存会分配给…...

Helm安装Harbor
一、介绍 1.1 Harbor Harbor 是由 VMware 公司为企业用户设计的 Registry Server 开源项目,包括了权限管理 (RBAC)、LDAP、审计、管理界面、自我注册、HA 等企业必需的功能,同时针对中国用户的特点,设计镜像复制和中文支持等功能。目前该项…...

梯度下降优化器:SGD -> SGDM -> NAG ->AdaGrad -> AdaDelta -> Adam -> Nadam -> AdamW
目录 1 前言 2 梯度概念 3 一般梯度下降法 4 BGD 5 SGD 6 MBGD 7 Momentum 8 SGDM(SGD with momentum) 9 NAG(Nesterov Accelerated Gradient) 10 AdaGrad 11 RMSProp 12 Adadelta 13 Adam 13 Nadam 14 AdamW 15 Lion(EvoLve…...

Ubuntu下gcc多版本管理
Ubuntu下多gcc版本的管理 开发过程中,在编译一个开源项目时,由于代码使用的c版本过高,而系统内置的gcc版本过低时,这个时候我们就需要升级gcc版本,但是为了避免兼容性问题,安装多个版本的gcc,然…...

吃透8图1模板,人人可以做架构
前言 在40岁老架构师 尼恩的读者交流群(50)中,很多小伙伴问尼恩: 大佬,我们写架构方案, 需要从哪些方面展开 大佬,我们写总体设计方案需要一些技术亮点,可否发一些给我参考下 诸如此类,问法很多…...

骨传导耳机推荐哪款好,列举几款是市面上热销的骨传导耳机
骨传导耳机是一种新型的耳机类型,通过震动和声音将振动传到了耳道外,对耳道不会产生损伤,能够保护听力。相比于传统耳机的优势有很多,比如运动时佩戴更加稳固,也可以在听歌时与人交谈。但在市面上的骨传导耳机款式可…...

CFS三层内网渗透
目录 环境搭建 拿ubuntu主机 信息收集 thinkphp漏洞利用 上线msf 添加路由建立socks代理 bagecms漏洞利用 拿下centos主机 msf上线centos 添加路由,建立socks代理 拿下win7主机 环境搭建 设置三块虚拟网卡 开启虚拟机验证,确保所处网段正确&a…...

SQL server设置用户只能访问特定数据库、访问特定表或视图
在实际业务场景我们可能需要开放单独用户给第三方使用,并且不想让第三方看到与业务不相关的表或视图,我们需要在数据库中设置一切权限来实现此功能: 1.设置用户只能查看数据库中特定的视图或表 1.创建用户名 选择默认数据库 服务器角色默认…...

linux:http服务器搭建及实验案例
目录准备工作http服务器各个配置文件大概说明实验1:访问不同ip获得不同网页实验2:同一ip访问不同端口获得不同网页准备工作 1,安装http服务 2,将 /etc/selinux/config 文件下面的 SELINUX值改为 disabled 或者 permissive 。 3&a…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
高防服务器能够抵御哪些网络攻击呢?
高防服务器作为一种有着高度防御能力的服务器,可以帮助网站应对分布式拒绝服务攻击,有效识别和清理一些恶意的网络流量,为用户提供安全且稳定的网络环境,那么,高防服务器一般都可以抵御哪些网络攻击呢?下面…...
C#中的CLR属性、依赖属性与附加属性
CLR属性的主要特征 封装性: 隐藏字段的实现细节 提供对字段的受控访问 访问控制: 可单独设置get/set访问器的可见性 可创建只读或只写属性 计算属性: 可以在getter中执行计算逻辑 不需要直接对应一个字段 验证逻辑: 可以…...

MySQL 知识小结(一)
一、my.cnf配置详解 我们知道安装MySQL有两种方式来安装咱们的MySQL数据库,分别是二进制安装编译数据库或者使用三方yum来进行安装,第三方yum的安装相对于二进制压缩包的安装更快捷,但是文件存放起来数据比较冗余,用二进制能够更好管理咱们M…...
JavaScript 数据类型详解
JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型(Primitive) 和 对象类型(Object) 两大类,共 8 种(ES11): 一、原始类型(7种) 1. undefined 定…...

通过 Ansible 在 Windows 2022 上安装 IIS Web 服务器
拓扑结构 这是一个用于通过 Ansible 部署 IIS Web 服务器的实验室拓扑。 前提条件: 在被管理的节点上安装WinRm 准备一张自签名的证书 开放防火墙入站tcp 5985 5986端口 准备自签名证书 PS C:\Users\azureuser> $cert New-SelfSignedCertificate -DnsName &…...
CppCon 2015 学习:REFLECTION TECHNIQUES IN C++
关于 Reflection(反射) 这个概念,总结一下: Reflection(反射)是什么? 反射是对类型的自我检查能力(Introspection) 可以查看类的成员变量、成员函数等信息。反射允许枚…...
shell脚本质数判断
shell脚本质数判断 shell输入一个正整数,判断是否为质数(素数)shell求1-100内的质数shell求给定数组输出其中的质数 shell输入一个正整数,判断是否为质数(素数) 思路: 1:1 2:1 2 3:1 2 3 4:1 2 3 4 5:1 2 3 4 5-------> 3:2 4:2 3 5:2 3…...

VASP软件在第一性原理计算中的应用-测试GO
VASP软件在第一性原理计算中的应用 VASP是由维也纳大学Hafner小组开发的一款功能强大的第一性原理计算软件,广泛应用于材料科学、凝聚态物理、化学和纳米技术等领域。 VASP的核心功能与应用 1. 电子结构计算 VASP最突出的功能是进行高精度的电子结构计算ÿ…...