【题目链接】:
移除链表元素
【题目描述】:
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
【解题】:
删除所有值为key的节点分为三步:
(1)遍历链表,找到要删除节点cur
(2) 让cur的上一个节点指向cur的下一个节点,删除cur
(3)继续遍历链表,找到所有与key相等的节点,重复(1)(2)步骤,直至遍历完链表
上述过程没有检查头节点,还有要考虑头节点情况,当要删除的数据是第一个节点时,只需要让head指向第二个节点即可做到删除第一个节点
public ListNode removeElements(ListNode head, int key) {
if (head == null) {
return null;
}
ListNode prev = head;
ListNode cur = head.next;
while(cur!=null){
if(cur.val == key){
prev.next = cur.next;
cur = cur.next;
}else {
prev = cur;
cur = cur.next;
}
}
if(head.val == key){
head = head.next;
}
return head;
}
【题目链接】:
206.反转链表
【题目描述】:
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
【解题】:目的:直接反转链表结构
先判断链表是否为空,为空直接返回null,从第二个我节点开始逐个使用头插法插入节点,过程如下:
public ListNode reverseList(ListNode head) {
//链表为空
if(head == null) {
return null;
}
//链表只有一个节点
if(head.next == null) {
return head;
}
ListNode cur = head.next;
head.next = null;
while(cur != null) {
ListNode curNext = cur.next;
cur.next = head;
head = cur;
cur = curNext;
}
return head;
}
【题目链接】:
876.链表的中间节点
【题目描述】:
给你单链表的头结点 head ,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
【解题】:
方式一:先求整个链表的长度,再求长度的一半找到中间节点
public ListNode middleNode(ListNode head) {
if(head == null) {
return null;
}
ListNode cur = head;
int count = size(head) >> 1;
while (count != 0){
cur = cur.next;
count--;
}
return cur;
}
//求链表的长度
public int size(ListNode head) {
int count = 0;
ListNode cur = head;
while (cur != null) {
cur = cur.next;
count++;
}
return count;
}
方式二:使用快、慢指针,快指针一次走两步,慢指针一次走一步,当快指针走到终点时,慢指针就刚好停在中间的位置。
public ListNode middleNode(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
【注意】: while(fast != null && fast.next != null)该条件语句中的两个语句的位置不能颠倒:
如果颠倒之后语句变成 fast.next != null&&fast != null ,当fast为null时,fast.next就会抛出空指针异常。如果没有颠倒时,当fast为null时,第一个条件成立为false,逻辑运算符&& 遵守短路求值的规则,不会对后一个条件进行判断,不会出现异常情况。
【题目链接】:
链表中倒数第k个结点
【题目描述】:
输入一个链表,输出该链表中倒数第k个结点。
【解题】:
考点:快、慢指针
方式一:
public ListNode FindKthToTail(ListNode head,int k) {
int len = size(head);
if(k < 0 || head == null || len - k < 0){
return null;
}
ListNode cur = head;
for(int i = 0;i < len - k;i++){
cur = cur.next;
}
return cur;
}
//求链表的长度
public int size(ListNode head) {
int count = 0;
ListNode cur = head;
while (cur != null) {
cur = cur.next;
count++;
}
return count;
}
方式二:快、慢指针
public ListNode FindKthToTail(ListNode head,int k) {
//判断k是否小于0 //当链表为空时,直接返回空
if(k < 0 || head == null){
return null;
}
ListNode fast = head;
ListNode slow = head;
int count = 0;
while(count != (k-1)){
fast = fast.next;
if(fast == null){//判断k是否大于链表的长度
return null;//要找的节点不存在,超过链表的长度
}
count++;
}
while(fast.next != null){
slow = slow.next;
fast = fast.next;
}
return slow;
}
【题目链接】:
21. 合并两个有序链表
【题目描述】:
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
【解题】:
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
ListNode newH = new ListNode();
ListNode tmpH = newH;
while(list1 != null && list2 != null) {
if(list1.val <= list2.val){
tmpH.next = list1;
list1 = list1.next;
tmpH = tmpH.next;
}else {
tmpH.next = list2;
list2 = list2.next;
tmpH = tmpH.next;
}
}
if(list1 != null) {
tmpH.next = list1;
}
if(list2 != null) {
tmpH.next = list2;
}
newH = newH.next;
return newH;
}
【题目链接】:
OR36 链表的回文结构
【题目描述】:
对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
【解题】:
第一步:找到链表的中间位置
第三步:从两端开始逐个对比,判断是否是回文
public boolean chkPalindrome(ListNode head) {
// write code here
//找到中间节点
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
//反转后半部分的链表
ListNode cur = slow.next;
while(cur != null) {
ListNode curNext = cur.next;
cur.next = slow;
slow = cur;
cur = curNext;
}
//判断是否是回文
while(slow != head && head.next != slow) {
//当奇数个节点时,slow和head相等,那么就是回文;
//当偶数个节点时,slow和head的下一个节点相等,那么就是回文;
if(slow.val != head.val) {
return false;
}
slow = slow.next;
head = head.next;
}
return true;
}
【题目链接】:
CM11 链表分割
【题目描述】:
现有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针。
【解题】:
题目分析:
首先要将所有小于x的结点排在其余结点之前,那么可以将链表分为两个部分,一部分为小于c的结点数据,一部分为大于等于x的数据,最后将两部分串联起来;并且题目中提到不能改变原来的数据顺序,所以可以使用尾插法来插入数据,可以保证数据的顺序。
该方法分为以下几个步骤:
public ListNode partition(ListNode pHead, int target) {
if (pHead == null) {
return null;
}
// write code here
//小于目标值的部分
ListNode bs = null;
ListNode be = null;
//大于等于目标值的部分
ListNode as = null;
ListNode ae = null;
ListNode cur = pHead;
while (cur != null) {
ListNode curNext = cur.next;
if (cur.val < target) {
// bs为null,插入第一个数据节点
if (bs == null) {
bs = cur;
be = cur;
cur = curNext;
} else {
be.next = cur;
be = cur;
cur = curNext;
}
} else {
if (as == null) {
as = cur;
ae = cur;
cur = curNext;
} else {
ae.next = cur;
ae = cur;
cur = curNext;
}
}
}
//小于目标值的部分不存在
if (bs == null) {
return as;
}
//小于目标值的部分存在
//将两个部分串联起来
be.next = as;
//如果后半部分存在。需要将后半部分的最后一个元素置为null
if (ae != null) {
//将最后一个值置为null
ae.next = null;
}
return bs;
}
【题目链接】:
160.相交链表
【题目描述】:
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
【解题】:
在上图中,节点5便是两个链表的相交点,从节点5之后两个链表的后续节点都是相同的,在上图中只需要同时移动head1和head2,只要两个节点相等时,那么就是相交点。这个方法并不完善,如果两个链表的长度不同时,此方法就不可行,如下图:
在上图中的两个链表长度并不相同,我们只需要让长的链表先走上两个链表的差值的步数,再按之前的方法,同时移动head1和head2,只要两个节点相等时,那么就是相交点。
解题步骤如下:
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int len1 = size(headA);
int len2 = size(headB);
//计算两个链表的长度的绝对值
int len = Math.abs(len1 - len2);
ListNode curA = headA;
ListNode curB = headB;
//判断那个链表较长,较长的链表先走差值的步数
if (len1 > len2) {
while (len != 0) {
curA = curA.next;
len--;
}
} else {
while (len != 0) {
curB = curB.next;
len--;
}
}
//两个链表同时走
while (curA != null && curB != null) {
//如果存在相等的节点,那么两个链表相交
if (curA == curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
//不存在相交点
return null;
}
//计算链表的长度
public int size(ListNode head) {
ListNode cur = head;
int count = 0;
while (cur != null) {
cur = cur.next;
count++;
}
return count;
}
如果用 while (hl != hs) 判断是否走到相交点,还需要判断是否走到链表的结尾,如果走到结尾两个节点并没有相遇但是两个值是相等的
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
// 使用hl和hs分别指向较长的链表,较短的链表
ListNode hl = headA;
ListNode hs = headB;
// 1.获取两个链表的长度
int len1 = 0;
while (hl != null) {
hl = hl.next;
len1++;
}
int len2 = 0;
while (hs != null) {
hs = hs.next;
len2++;
}
int len = len1 - len2;
hl = headA;
hs = headB;
if (len < 0) {
// 改变hl、hs指针的指向
hl = headB;
hs = headA;
len = len2 - len1;
}
// 代码截至到此,hl一定指向较长的链表,hs指向较短的链表
// 2. 让较长的代码先走差值的步数
while (len != 0) {
hl = hl.next;
len--;
}
// 3. 两个链表一起走,如果存在节点相等,那么就是相交点
while (hl != hs) {
hl = hl.next;
hs = hs.next;
}
// 判断是否走到链表的结尾,如果走到结尾没有相遇但是两个值是相等的
if (hl == null) { //不相遇
return null;
}
return hl;//相遇点
}
【题目链接】:
141.环形链表
【题目描述】:
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
【解题】:
【思路】 快、慢指针,即慢指针一次走一步,快指针一次走两步,两个指针从链表起始位置开始运行,如果链表带环则一定会在环中相遇,否则快指针率先走到链表的末尾。比如:陪女朋友到操作跑步减肥。
再走一步,那么快慢指针就会相遇:
【扩展问题】
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow) {
return true;
}
}
return false;
}
}
public boolean hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
break;
}
}
// 可以在结束循环时,载进行一下判断
if (fast == null || fast.next == null) {
return false;
}
return false;
}
【题目链接】:
142.环形链表||
【题目描述】:
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
【解题】:
【结论】:
让一个指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始绕环运行,两个指针都是每次均走一步,最终肯定会在入口点的位置相遇。
【证明】
public ListNode detectCycle(ListNode head) {
// 获取相遇点
ListNode node = hasCycle(head);
//相遇点不存在
if (node == null) {
return null;
}
//相遇点存在
ListNode fast = node;
ListNode slow = head;
//判断入口点
while (slow != fast) {
slow = slow.next;
fast = fast.next;
}
//入口点
return slow;
}
public ListNode hasCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {
break;
}
}
if (fast == null || fast.next == null) {
return null;
}
return fast;
}