一文通数据结构与算法之——链表+常见题型与解题策略+Leetcode经典题
文章目录
- 1 链表
- 1.1 常见题型及解题策略
- 1.1.1 LeetCode中关于链表的题目有以下五种类型题:
- 1.1.2 解题策略
- 1.2 链表的基本内容
- 1.2.1 链表的基本结构:
- 1.2.2 插入新元素
- 1.2.3 删除某个元素
- 1.2.4 遍历单链表
- 1.3 删除链表结点类题目
- 1.3.1 题解方法
- 1.3.2 可能出现的问题
- 1.3.3 题库列表:
- 237、删除链表中的节点
- 203、移除链表元素
- 剑指 Offer 18. 删除链表的节点
- 面试题 02.01. 移除重复节点
- 82. 删除排序链表中的重复元素 II
- 19、删除链表的倒数第 N 个结点
- 876、链表的中间结点
- 86、分隔链表
- 328、奇偶链表(分割链表的变形)
- 1.4 反转链表节点类题目
- 1.4.1 动图解释,双指针修改指针的方向
- 1.4.2 题库列表
- 206、反转链表
- 92、反转链表 II
- 234、 回文链表
- 25. K 个一组翻转链表
- 1.5 合并链表
- 1.5.1 题库列表
- 21、合并两个有序链表
- 23、合并K个升序链表
- 1.6 排序链表
- 1.6.1 题库列表
- 147、对链表进行插入排序
- 148、排序链表:归并法
- 1.7 环形链表
- 1.7.1 题库列表
- 160、相交链表
- 141、环形链表
- 142、环形链表II
1 链表
链表是最基本的数据结构,面试官常常用链表来考察面试者的基本能力,而且链表相关的操作相对而言比较简单,也适合考察写代码的能力。链表的操作也离不开指针,指针又很容易导致出错。综合多方面的原因,链表题目在面试中占据着很重要的地位。
以下内容思路主要参考:算法面试题 | 链表问题总结
1.1 常见题型及解题策略
1.1.1 LeetCode中关于链表的题目有以下五种类型题:
- 删除链表节点
- 反转链表
- 合并链表
- 排序链表
- 环形链表
1.1.2 解题策略
- dummy虚拟头节点,专门处理头结点可能会被改动的情况
- 快慢双指针
1.2 链表的基本内容
推荐一篇文章:基础知识讲的很清楚,Java数据结构与算法之链表
链表的分类:单链表(分带头结点和不带头结点的单链表,就是head里面有没有data的区别)、双向链表、循环链表
重点理解指针的概念
如下代码 ans.next 指向什么?ans = ListNode(1)
ans.next = head //ans.next 指向取决于最后切断 ans.next 指向的地方在哪,所以ans.next指向head
head = head.next //ans 和 head 被切断联系了
head = head.next
ans = ListNode(1)
head = ans// ans和head共进退
head.next = ListNode(3)
head.next = ListNode(4)
// ans.next 指向什么?ListNode(3)
ans = ListNode(1)
head = ans //head 和 ans共进退
head.next = ListNode(3)
head = ListNode(2) //head 和 ans 的关系就被切断了
head.next = ListNode(4)
居然找到人跟我一样卡在这里了,笑死
1.2.1 链表的基本结构:
* public class ListNode {* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
1.2.2 插入新元素
//0.找到要插入的位置
temp = 待插入位置的前驱节点.next //1.先用一个临时节点把 待插位置后面的内容先存起来
待插入位置的前驱节点.next = 待插入指针 //2.将新元素插入
待插入指针.next = temp //3.再把后面的元素接到新元素的next
1.2.3 删除某个元素
待删除位置的前驱节点.next = 待删除位置的前驱节点.next.next
1.2.4 遍历单链表
当前指针 = 头指针
while 当前节点不为空 {print(当前节点)当前指针 = 当前指针.next
}for (ListNode cur = head; cur != null; cur = cur.next) {print(cur.val)
}
1.3 删除链表结点类题目
1.3.1 题解方法
- 画草图:理解指针的变动与思考逻辑!!(重要!实用!)
- 边界条件:怎么处理不会有空指针异常?在循环里放什么停止条件
- 如果是遍历链表元素,
while(node!=null)
- 如果是删除某个元素,需要,
while(node.next!=null)
- 需要考虑的仅仅是被改变 next 指针的部分,并且循环之后哪个指针在最后的节点处,就判断谁
//比如快慢指针,输出中间节点,slow和fast的指针都在变,但是fast先指向链表尾巴,所以判断 fast
//同时每个判断next.next的都必须先判断,next,才能保证 奇偶链长 中不会出现空指针异常
while(fast.next!=null && fast.next.next!=null){slow = slow.next;fast = fast.next.next;}
- 只要会删除头结点,都要进行
dummy
虚指针 - 特殊的需求可以考虑结合各种工具类,比如删除重复里面,利用
HashSet
,删除倒数第k个,利用栈LinkedList
1.3.2 可能出现的问题
①
NullPointerException
,就是当前节点为空,我们还去操作它的next
;② 输出不了结果,一定是指针移动出了问题
1.3.3 题库列表:
237. 删除链表中的节点 ====面试题 02.03. 删除中间节点
203. 移除链表元素(虚拟头结点)
- 83. 删除排序链表中的重复元素
- 剑指 Offer 18. 删除链表的节点
- 面试题 02.01. 移除重复节点
- 82. 删除排序链表中的重复元素 II
19. 删除链表的倒数第 N 个结点(双指针经典类型)
- 876. 链表的中间结点
- 86. 分隔链表
- 328. 奇偶链表
237、删除链表中的节点
//237.传入待删除结点,直接将当前节点的值改为next的值,next指向next.next,实现原地更新。
public void deleteNode(ListNode node) {node.val = node.next.val;node.next = node.next.next;
}
203、移除链表元素
① 如果删除的节点是中间的节点,则问题似乎非常简单:
- 选择要删除节点的前一个结点
prev
。- 将
prev
的next
设置为要删除结点的next
。② 当要删除的一个或多个节点位于链表的头部时,要另外处理
三种方法:
- 删除头结点时另做考虑(由于头结点没有前一个结点)
- 添加一个虚拟头结点,删除头结点就不用另做考虑
- 递归
- 双指针法
即便是参考别人的代码,一看就看的懂,但其实我们有时候不知道内涵,只要自己闭着眼睛敲一遍,发现了问题,才知道是怎么考虑出来的
// 执行耗时:1 ms,击败了99.79% 的Java用户
// 内存消耗:39.4 MB,击败了49.10% 的Java用户
// 时间复杂度是O(n)。空间复杂度O(1)public ListNode removeElements(ListNode head, int val) {//删除值相同的头结点后,可能新的头结点也值相等,用循环解决//比如输入 [7 7 7 7] 删除7,我一开始是直接用 if,发现有些案例无法通过才知道用while的原因while(head!=null && head.val==val){head = head.next;}//因为前面是对head的操作,所以极可能最后完了,head为空,所以把判断的过程放在后面//我本来是吧if放在删除头结点的前面打,结果报错空指针异常,所以才知道为什么判空的要放在后面if(head==null){return head;}ListNode temp = head;//临时指针while(temp.next!=null){if(temp.next.val==val){temp.next = temp.next.next;}else{temp = temp.next;}}return head;}
添加一个虚拟头结点
//执行耗时:1 ms,击败了99.79% 的Java用户
//内存消耗:39.2 MB,击败了82.52% 的Java用户
//时间复杂度是O(n)。空间复杂度O(1)public ListNode removeElements(ListNode head, int val){// 创建虚节点ListNode dummyNode = new ListNode(val-1);dummyNode.next = head;ListNode prev = dummyNode;while(prev.next!=null){if(prev.next.val==val){prev.next = prev.next.next;}else{prev = prev.next;}}return dummyNode.next;}
递归
//时间复杂度是O(n)。递归的方法调用栈深度是n,所以空间复杂度O(n),会超时
public ListNode removeElements(ListNode head, int val){if(head==null){return head;}// 因为递归函数返回的是已经删除节点之后的头结点// 所以直接接上在head.next,最后就只剩下判断头结点是否与需要删除的值一致了head.next = removeElements(head.next,val);if(head.val==val){return head.next;}else{return head;}}
剑指 Offer 18. 删除链表的节点
双指针
// 剑指 Offer 18. 删除链表的节点public ListNode deleteNode(ListNode head, int val){if(head.val==val){//因为互不相等,如果头指针相等,直接返回return head.next;}//双指针ListNode pre = head;ListNode cur = head.next;while(cur!=null && cur.val!=val){//找元素pre = cur;cur = cur.next;}if(cur!=null){//找到了,进行删除操作pre.next = cur.next;}return head;//删完了,返回}
面试题 02.01. 移除重复节点
// 面试题 02.01. 移除重复节点
// 法一:借助HashSet的特征
// 移除未排序链表中的重复节点。保留最开始出现的节点,重复的元素不一定连续public ListNode removeDuplicateNodes(ListNode head) {if (head == null) {return head;}ListNode temp = head;HashSet<Integer> set = new HashSet<>();set.add(head.val);while(temp.next!=null){if(set.add(temp.next.val)){//加进去说明不重复temp = temp.next;}else{temp.next = temp.next.next;//原地删除}}return head;}
// 法二:用空间换时间
// 双重循环,一个定位一个遍历后序,用时间换空间public ListNode removeDuplicateNodes(ListNode head) {if(head==null){return head;}ListNode pre = head;while(pre!=null){ListNode cur = pre;while(cur.next!=null){if(cur.next.val==pre.val){cur.next = cur.next.next;}else{cur = cur.next;}}pre = pre.next;}return head;}
82. 删除排序链表中的重复元素 II
//升序链表,删除链表中所有重复的节点【1 1 1 1 2 3】-->【2 3】//双指针记录pre 用cur记录相同的数,加虚头节点public ListNode deleteDuplicates(ListNode head) {if(head==null){return head;}ListNode dummy = new ListNode(0);//可能删除头结点,所以使用虚节点dummy.next = head;ListNode pre = dummy;ListNode cur = dummy.next;while(cur!=null && cur.next!=null){//画图最好理解if(cur.val==cur.next.val ){//如果有奇数个相同的值,就删不完,所以必须用while循环while(cur!=null && cur.next!=null && cur.val==cur.next.val ){cur = cur.next;//找到最后一个相等的数}pre.next = cur.next;cur = pre.next;}else{pre = cur;cur = cur.next;}}return dummy.next;}
19、删除链表的倒数第 N 个结点
// 删除链表的倒数第 n 个结点,并且返回链表的头结点
// 双指针public ListNode removeNthFromEnd(ListNode head, int k){if(head==null) return head;
// 可能会删除头结点ListNode dummy = new ListNode(0,head);ListNode pre = dummy.next;for (int i = 0; i < k; i++) {pre = pre.next;}ListNode cur = dummy;while(pre!=null){cur = cur.next;pre = pre.next;}cur.next = cur.next.next;return dummy.next;}
// 另外一个方法,利用栈的先进后出特点,效率会更低
// 执行耗时:1 ms,击败了19.42% 的Java用户
// 内存消耗:37.7 MB,击败了5.02% 的Java用户
public ListNode removeNthFromEnd(ListNode head, int n) {if(head==null){return head;}ListNode dummy = new ListNode(0,head);ListNode temp = dummy;LinkedList<ListNode> stack = new LinkedList<>();while(temp!=null){stack.push(temp);temp = temp.next;}for (int i = 0; i < n; i++) {stack.pop();}ListNode pre = stack.peek();pre.next = pre.next.next;return dummy.next;
}
876、链表的中间结点
//执行耗时:0 ms,击败了100.00% 的Java用户
//内存消耗:35.7 MB,击败了68.38% 的Java用户
public ListNode middleNode(ListNode head) {ListNode slow = head;ListNode fast = head;//如果不加fast != null,链表元素个数为偶数时会报空指针异常while(fast!=null && fast.next!=null){slow = slow.next;fast = fast.next.next;}return slow;}
86、分隔链表
两个临时链表
// 给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
// 另外创建一个链表,遍历原来的链表,删除小于的接上去。可能删除头结点public ListNode partition(ListNode head, int x) {ListNode small = new ListNode(0);//可能会动头结点,所以需要虚节点ListNode smallHead = small;//要记住头结点,所以需要另外设置HeadListNode large = new ListNode(0);ListNode largeHead = large;while(head!=null){if(head.val<x){small.next = head;small = small.next;}else{large.next = head;large = large.next;}head = head.next;}large.next = null;//再拼接两个链表,尾巴指向nullsmall.next = largeHead.next;return smallHead.next;}
328、奇偶链表(分割链表的变形)
两个临时链表的变形
// 给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。// 请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。// 法一,利用额外空间public ListNode oddEvenList(ListNode head) {if(head==null){return head;}ListNode odd = new ListNode(0);ListNode oddHead = odd;ListNode even = new ListNode(0);ListNode evenHead = even;int count = 1;while(head!=null){if(count%2==1){//奇数odd.next = head;odd = odd.next;}else{even.next = head;even = even.next;}head = head.next;count++;}even.next = null;odd.next = evenHead.next;return oddHead.next;}
直接双指针前后遍历奇数偶数
// 不需要额外空间,双指针操作public ListNode oddEvenList(ListNode head) {if(head==null){return head;}ListNode odd = head;ListNode even = head.next;ListNode evenHead = even;while(even!=null && even.next!=null){odd.next = even.next;//先把奇数连起来odd = odd.next;//移动奇数的指针even.next = odd.next;//此时加偶数even = even.next;//移动偶数的指针}odd.next = evenHead;return head;}
1.4 反转链表节点类题目
反转的这些操作尤其需要记忆,要每天多写几遍才可以。
1.4.1 动图解释,双指针修改指针的方向
1.4.2 题库列表
206. 反转链表====剑指 Offer 24. 反转链表
92. 反转链表 II
234. 回文链表====面试题 02.06. 回文链表
25. K 个一组翻转链表
206、反转链表
双指针法迭代
//给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。public ListNode reverseList(ListNode head) {if(head==null){return head;}ListNode pre = null;ListNode cur = head;while(cur!=null){ListNode temp = cur.next;cur.next = pre;pre = cur;cur = temp;}return pre;}
递归法:可参考文章:labuladong:递归反转链表的一部分
- 使用递归的3个条件
- 大问题可以拆解成两个子问题
- 子问题的求解方式和大问题一样
- 存在最小子问题
// 递归法public ListNode reverseList(ListNode head) {if(head==null || head.next==null){return head;}//递归最重要的是明确递归函数的定义。//reverseList代表”「以 head 为起点」的链表反转,并返回反转之后的头结点“//所以last表示 head.next后面一整段反转之后的头结点。所以最后return lastListNode last = reverseList(head.next);//重点理解,此时head.next指向的已经是反转部分的尾巴,也就是图中的2head.next.next = head;head.next = null;//head指向null,表示此时head已经是尾巴了。return last;}
92、反转链表 II
① 穿针引线法,三个指针
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mv8Avw5Y-1633245545523)(…/…/…/AppData/Roaming/Typora/typora-user-images/image-20210920172349817.png)]
node = cur.next;//cur表示当前操作的指针,node保存后面的顺序
cur.next = node.next;
node.next = pre.next;
pre.next = node;
方法一:迭代
// 反转位置left到位置right中间的部分,其余部分不变,
public ListNode reverseBetween(ListNode head, int left, int right) {ListNode dummy = new ListNode(-1,head);//迭代法,先找到起点ListNode pre = dummy;for (int i = 0; i < left-1; i++) {pre = pre.next;//来到 left 节点的前一个节点}ListNode cur = pre.next;//cur是真正反转的指针ListNode node;//node的保存cur.next 的临时指针for (int i = 0; i < right - left; i++) {node = cur.next;//保存后面的顺序cur.next = node.next;//穿针引线,其实很有规律node.next = pre.next;pre.next = node;}return dummy.next;
}
方法二,递归
// 递归法反转前n个元素public ListNode reverseN(ListNode head,int n){ListNode succssor = null;if(n==1){successor = head.next;//把后继记录下来,基线是只有一个元素return head;}
// 看递归不要进递归函数里面,而是看函数返回了什么结果ListNode last = reverseN(head.next,n-1);
// 整个链表 = head+reverseN(n-1)这一个已经反转的小团体 + succssor后继
// 注意此时head.next指向的是已经反转的小团体的末尾head.next.next = head;head.next = succssor;return last;}public ListNode reverseBetween(ListNode head, int left, int right) {if(left==1){return reverseN(head,right);}head.next = reverseBetween(head.next,left-1,right-1);return head;}
234、 回文链表
// 请判断一个链表是否为回文链表。
// 反转后半段,两头往中间遍历是否相等,返回前复原链表public boolean isPalindrome(ListNode head) {// 1.快慢指针找到中间节点ListNode slow = head;ListNode fast = head;while(fast!=null && fast.next!=null){slow = slow.next;fast = fast.next.next;}ListNode point = slow;//存储中间的断点,用于恢复原来的顺序if(fast!=null){slow = slow.next;//slow要定位到中间的后一个位置}
// 2.反转slow后面的元素ListNode left = head;ListNode right = reverse(slow);ListNode q = right;//存储末尾的断点用于恢复原来链表的顺序// 3.两头往中间遍历,是否相等,因为后半段尾巴是nullwhile(right!=null){if(left.val!=right.val){return false;}left = left.next;right = right.next;}point.next = reverse(q);//还原链表return true;}public ListNode reverse(ListNode head){if(head==null){return head;}ListNode pre = null;ListNode cur = head;while(cur!=null){ListNode temp = cur.next;cur.next = pre;pre = cur;cur = temp;}return pre;}
25. K 个一组翻转链表
感觉一点设计这种一段一段的都可以考虑递归等算法,但对我的下意识总是,暴力求解。。。
学习!努力学习!
public ListNode reverseKGroup(ListNode head, int k) {if(head==null){return head;}
// 1.反转前k个元素ListNode pre = head;ListNode cur = head;for (int i = 0; i < k; i++) {if(cur==null){return head;//不够长,保持不变}cur = cur.next;}ListNode newHead = reverse(pre,cur);// 2.递归反转后面的,并将后续的连接
// pre一直没有动,所以pre变成最后一个元素之后将next连上pre.next = reverseKGroup(cur,k);return newHead;}// 反转区间[head end)之间的元素public ListNode reverse(ListNode head,ListNode end){ListNode pre = null;ListNode cur = head;while(cur!=end){ListNode temp = cur.next;cur.next = pre;pre = cur;cur = temp;}return pre;}
1.5 合并链表
合并有序链表问题在面试中出现频率 较高
1.5.1 题库列表
21. 合并两个有序链表
23. 合并K个升序链表
21、合并两个有序链表
/*** 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的*/
public class _03_01_mergeList {public ListNode mergeTwoLists(ListNode l1, ListNode l2) {// 双指针比较两位置大小,新建一链表ListNode newHead = new ListNode(0);ListNode head = newHead;while(l1!=null && l2!=null){if(l1.val>l2.val){newHead.next = l2;l2 = l2.next;}else{newHead.next = l1;l1 = l1.next;}newHead = newHead.next;}// 只有一个是空的,那么便把另一个直接接上去就可以了newHead.next = l1==null?l2:l1;return head.next;}// 2.递归法public ListNode mergeTwoLists2(ListNode l1, ListNode l2) {// 递归基线是当前数组为空,直接返回if(l1==null){return l2;}if(l2==null){return l1;}
// 判断当前的大小,原地修改if(l1.val<=l2.val){l1.next = mergeTwoLists(l1.next,l2);return l1;}else{l2.next = mergeTwoLists(l1,l2.next);return l2;}}
}
23、合并K个升序链表
方法一:逐个合并,方法二,递归分治,时间消耗相对少很多
public ListNode mergeKLists(ListNode[] lists) {if(lists.length==0){return null;}
// 遍历链表数组,每次选择两个链表,进行合并int i=0;ListNode head = null;while(i<lists.length){head = merge(head,lists[i]);i++;}return head;}public ListNode merge(ListNode l1,ListNode l2){ListNode newHead = new ListNode(0);ListNode head = newHead;while(l1!=null && l2!=null){if(l1.val>l2.val){newHead.next = l2;l2 = l2.next;}else{newHead.next = l1;l1 = l1.next;}newHead = newHead.next;}// 只有一个是空的,那么便把另一个直接接上去就可以了newHead.next = l1==null?l2:l1;return head.next;}
public ListNode mergeKLists(ListNode[] lists) {return mergeFrom(lists,0,lists.length-1);}public ListNode mergeFrom(ListNode[] lists,int left,int right){if(left==right){return lists[left];}if(left>right){return null;}int mid = left + (right-left)/2;//merge是合并两个链表的方法,如上return merge(mergeFrom(lists,left,mid),mergeFrom(lists,mid+1,right));}
1.6 排序链表
1.6.1 题库列表
147. 对链表进行插入排序
148. 排序链表
147、对链表进行插入排序
public ListNode insertionSortList(ListNode head) {if(head==null){return head;}
// 1.会移动头结点,所以用到虚拟头结点ListNode dummy = new ListNode(0);dummy.next = head;// 2.外层循环遍历完链表所有数,遍历[head,lastSort]这段位置找插入ListNode cur = dummy.next;ListNode lastSort = head;//维护已排序部分的最后一个位置while(cur!=null){//cur为遍历的待插入元素if(lastSort.val<=cur.val){lastSort = cur;//大,直接后移}else{ListNode pre = dummy;//用来遍历已经排序的部分// 3.从前往后比较,找插入的位置while(cur.val>pre.next.val){pre = pre.next;}
// 4.找到位置进行插入操作lastSort.next = cur.next;cur.next = pre.next;pre.next = cur;}
// 5.指针后移cur = lastSort.next;}return dummy.next;}
148、排序链表:归并法
// 要求时间空间复杂度分别为O(nlogn)和O(1):归并排序
// 递归额外空间:递归调用函数将带来O(logn)的空间复杂度
// 使用递归版归并,会额外用到logn的时间复杂度public ListNode sortList(ListNode head) {// 1.base lineif(head==null || head.next==null){return head;}// 2.找中点,偶数找的前面那个中点的位置,奇数找到中点ListNode slow =head;ListNode fast = head.next;while(fast!=null && fast.next!=null){slow = slow.next;fast = fast.next.next;}// 3.将链表分割成两个子链表ListNode temp = slow.next;slow.next = null;ListNode left = sortList(head);ListNode right = sortList(temp);// 4.新建一个链表,对已排序的链表进行归并操作ListNode newHead = new ListNode(0);ListNode res = newHead;while(left!=null && right!=null){if(left.val<right.val){newHead.next = left;left = left.next;}else{newHead.next = right;right = right.next;}newHead = newHead.next;}newHead.next = left==null?right:left;return res.next;}
1.7 环形链表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ya2BbAvB-1633245545528)(image/算法/e1dbea51f21247a4972c2cb28855609a~tplv-k3u1fbpfcp-watermark.webp?lastModify=1632129814)]
1.7.1 题库列表
160. 相交链表
141. 环形链表
- 142. 环形链表 II
160、相交链表
// 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
// Set里放的是ListNode而不是ListNode.val,比较的是指针地址
// 方法一:哈希表
// 方法一:哈希表法。存的是ListNode,所以相等时代表地址相同,也就是同一个元素
// 即便val相等,在哈希判断是也不会相等,所以可以使用hash表的方法public ListNode getIntersectionNode2(ListNode headA, ListNode headB) {if(headA==null|| headB==null){return null;}
// 1.先将某一个链表中的元素存到 set 中HashSet<ListNode> set = new HashSet<>();ListNode cur = headA;while(cur!=null){set.add(cur);cur = cur.next;}// 2.再遍历第二个链表,如果有就直接返回,如果没有继续遍历ListNode node= headB;while(node!=null){if(set.contains(node)){return node;}node = node.next;}return null;}//方法二:双指针public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if(headA==null || headB==null){return null;}ListNode nodeA = headA;ListNode nodeB = headB;while(nodeA!=nodeB){// 退出的关键是:指向同一个指针(不是值相等),或者都指向nullnodeA = nodeA==null?headB:nodeA.next;nodeB = nodeB==null?headA:nodeB.next;}return nodeA;//如果没有相等的那么nodeA==nodeB==null}
141、环形链表
// 双指针public boolean hasCycle(ListNode head) {if(head==null ||head.next==null){return false;}ListNode slow = head;ListNode fast = head.next;while(slow!=fast){// 因为快指针在前面,所以只要判断快指针是否达到了队尾就可以if(fast==null || fast.next==null){return false;}slow = slow.next;fast = fast.next.next;}return true;}// 哈希表,耗时非常慢public boolean hasCycle(ListNode head) {if(head==null){return false;}HashSet<ListNode> set = new HashSet<>();ListNode cur = head;while(cur!=null){if(set.contains(cur)){return true;}set.add(cur);cur = cur.next;}return false;}
142、环形链表II
//给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
// 方法一是哈希表,方法二双指针public ListNode detectCycle(ListNode head) {ListNode slow = head;ListNode fast = head;
// 1.快慢指针找重合点while(fast!=null && fast.next!=null){slow = slow.next;fast = fast.next.next;
// 2.重合了,这个时候,从头来一个指针遍历if(fast==slow){ListNode cur = head;while(cur!=slow){cur = cur.next;slow = slow.next;}return slow;}}
// 3没有环,返回nullreturn null;}
一文通数据结构与算法之——链表+常见题型与解题策略+Leetcode经典题相关推荐
- 数据结构与算法--单链表相关面试题
此文章仅作为自己学习过程中的记录和总结,同时会有意地去用英文来做笔记,一些术语的英译不太准确,内容如有错漏也请多指教,谢谢! 一.概述 获取单链表的有效元素个数[新浪面试题1] 获取单链表倒数第k个结 ...
- python定义链表节点_Python数据结构与算法之链表定义与用法实例详解【单链表、循环链表】...
本文实例讲述了Python数据结构与算法之链表定义与用法.分享给大家供大家参考,具体如下: 本文将为大家讲解: (1)从链表节点的定义开始,以类的方式,面向对象的思想进行链表的设计 (2)链表类插入和 ...
- 数据结构与算法之链表结构寻找p、q最近的公共祖先
链表结构,寻找p.q最近的公共祖先 数据结构与算法之链表结构寻找p.q最近的公共祖先 链表结构,寻找p.q最近的公共祖先 问题 想法 代码 问题 设一棵二叉树的结点结构为(LLINK, INFO, R ...
- 数据结构与算法 内核链表实现商品购物系统项目+Makefile
数据结构与算法 内核链表实现商品购物系统项目 第一章 项目实现思维 [1]编译介绍 [2]框架思维 第二章 Makefile编写 第三章 代码编写实现 [1]favorite.txt文件 [2]his ...
- 数据结构与算法 第八天常见排序+冒泡排序+快速排序+文件IO+大数据排序+文件合并
数据结构与算法 第八天常见排序+冒泡排序+快速排序+文件IO+大数据排序+文件合并 第一章 冒泡排序 [1]Bubble_Sort.c 第二章 快速排序 [1]quick_sort.c 第三章 大数据 ...
- (左神)数据结构与算法 ---- 判断链表是否为回文结构的三种高效解法
链表在数据结构与算法中可谓"北斗之尊",现在让我们通过判断链表回文的小练习进一步更深地了解链表~ 文章目录 一.链表的节点结构 二.判断一个链表是否为回文结构 (一)解法1:将链表 ...
- JS数据结构与算法_链表
上一篇:JS数据结构与算法_栈&队列 下一篇:JS数据结构与算法_集合&字典 写在前面 说明:JS数据结构与算法 系列文章的代码和示例均可在此找到 上一篇博客发布以后,仅几天的时间竟然 ...
- java数据接口之链表_Java数据结构和算法之链表
三.链表 链结点 在链表中,每个数据项都被包含在'点"中,一个点是某个类的对象,这个类可认叫做LINK.因为一个链表中有许多类似的链结点,所以有必要用一个不同于链表的类来表达链结点.每个LI ...
- 数据结构与算法--复杂链表的复制
复杂链表的复制 题目:实现一个函数complexListNode 复制一个复杂链表.在链表中,每个节点除了有一个next指针指向下一个节点,还有另外一个before节点,before节点指向链表中任意 ...
最新文章
- PYTHON编程导论群问题汇总(五)
- 黑苹果挂载不了分区_让黑苹果变得更完美——BCM94532HMB无线蓝牙接力完美驱动!...
- xtrabackup支持的engine
- Java中的传值与传引用
- HTML5清爽简洁外贸网站模板
- php微信个性化菜单,微信公众平台新增个性化菜单接口,实现公众号
- 手机支付优惠促销活动插画素材,拿来就能用,高效省时。
- 自动驾驶感知-车道线系列(三)——霍夫变换
- 《战舰世界》携手汉堡王开启“战舰堡胃战”主题活动
- 修复Lvgl的roller控件点击位置向上偏移的问题
- 薄板开孔建模计算的ansys命令流
- java判断生肖_Java写出生肖年判断
- python爬取酷狗音乐top500及歌词_爬取酷狗音乐Top500(示例代码)
- 利用稀疏格式矩阵求解方程组以及机器学习训练速度对比
- 【基于深度学习的细粒度分类笔记2】弱监督学习下商品识别:CVPR 2018细粒度识别挑战赛获胜方案简介
- 国产手机已经用上了 120W 快充技术,苹果还在用20W的原因一
- 服务器国产linux操作系统,国产linux操作系统适于做服务器系统的有哪些
- java swing组件_Java -- Swing 组件使用
- 网站收录链接分析之网站排名查询
- 用python爬取实时基金估值
热门文章
- 【校招VIP】产品设计之游戏常识
- metasploit利用IE漏洞XSS挂马拿内网主机
- iOS 审核被拒记录 Guideline 2.5.1 HealthKit; 2.5.4 UIBackgroundModes audio; 1.5 Developer Information
- reduce函数详解
- table表格溢出隐藏
- 分享四种换发型方法,轻易测试出适合你的发型
- 万网云解析设置二级域名解析到同IP不同端口
- 最新爱情经典语录 真爱一个人,就要尽量让他开心
- 马宁的Windows Phone 7.1初体验(二)——Push Notification
- 3D Vision 八讲:第四讲