目录
1、反转一个链表
https://leetcode.cn/problems/reverse-linked-list/solutions/
思路:(双指针)
1、先从头开始遍历一遍链表依次翻转
2、记录要翻转的节点以及他的下一个节点(rever、reverNext)
3、head的下一个节点为rever,记录好之后把head.next置空
4、如果只有一个节点,则直接返回他的头节点
public ListNode reverseList(ListNode head) {
//判断是否只有一个节点
if(head == null){
return null;
}
//只有一个节点
if(head.next == null){
return head;
}
ListNode rever = head.next;
head.next = null;
while(rever != null){
ListNode reverNext = rever.next;
rever.next = head;
head = rever;
rever = reverNext;
}
return head;
}
2、返回链表的中间节点
876. 链表的中间结点 - 力扣(LeetCode)https://leetcode.cn/problems/middle-of-the-linked-list/description/
思路:
快慢指针(数学思想:时间相同,A速度是B速度的两倍,则A走过的路程是B走过路程的两倍
1、一开始slow和fast都指向头节点
2、每次让fast走两步,slow走一步
3、当fast走到最后一个节点时slow刚好走到链表的中间
public ListNode middleNode(ListNode head) {
//快慢指针
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
3、输出链表的倒数第K个节点
两种方法:
法1:还是快慢指针,当快指针比慢指针多走k步时,如果快指针走到了最后一个节点,那么慢指针刚好在倒数第k个节点的位置
//思路:快慢指针,快指针比慢指针多走k步
ListNode fast = pHead;
ListNode slow = pHead;
int len = 0;
//求链表的长度
while(cur != null){
cur = cur.next;
len++;
}
// 如果该链表长度小于k,请返回一个长度为 0 的链表。
//这个和没有节点的链表情况一样,合并代码即可
if(k > len){
return null;
}
//快指针走k步
while(k > 0){
if(fast != null){
fast = fast.next;
}else{
slow = null;
}
k--;
}
// 两个指针同步走
while(fast != null){
fast = fast.next;
slow = slow.next;
}
return slow;
法2:
public ListNode FindKthToTail (ListNode pHead, int k) {
// write code here
ListNode cur = pHead;
int len = 0;
//求链表的长度
while(cur != null){
cur = cur.next;
len++;
}
// 如果该链表长度小于k,请返回一个长度为 0 的链表。这个和没有节点的链表情况一样,合并代码即可
if(k > len){
return null;
}
cur = pHead;
for(int i = 0; i < len-k ;i++){
cur = cur.next;
}
return cur;
4、合并两个有序链表
21. 合并两个有序链表 - 力扣(LeetCode)https://leetcode.cn/problems/merge-two-sorted-lists/description/
画图理解
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
//先定义一个新节点作为新的节点
ListNode newHead = new ListNode(-1);
ListNode tmpH = newHead;
//当两个链表都不空时比较他们数值的大小
while(list1 != null && list2 != null){
if(list1.val < list2.val){
//小的接在新的头节点后面
tmpH.next = list1;
tmpH = list1;
list1 = list1.next;
}else{
tmpH.next = list2;
tmpH = list2;
list2 = list2.next;
}
}
//list1 比list2短
if(list1 == null){
tmpH.next = list2;
}
if(list2 == null){
tmpH.next = list1;
}
return newHead.next;
}
5、链表分割
public ListNode partition(ListNode pHead, int x) {
// write code here
//思路:x把链表分为两部分,一部分比x小,一部分比x大,
//分别用双指针分别记录下两段,最后第一段的尾巴接上第二段的头即可
ListNode fBegin = null;//作为第一段的头节点
ListNode fAfter = null;
ListNode rBegin = null;//作为第二段的头节点
ListNode rAfter = null;
ListNode cur = pHead;
//依次遍历链表进行分组
while(cur != null){
//如果链表的值比x小,放在第一段
if(cur.val < x){
//如果是第一次插入
if(fBegin == null){
fBegin = cur;
fAfter = cur;
}else {
fAfter.next = cur;
fAfter = fAfter.next;
}
}else{
//如果是第一次插入
if(rBegin == null){
rBegin = cur;
rAfter = cur;
}else {
rAfter.next = cur;
rAfter = rAfter.next;
}
}
cur = cur.next;
}
if(fBegin == null){
return rBegin;
}
fAfter.next = rBegin;
//如果第二段的最后一个不为空,则需要置空,否则会形成环
if(rBegin != null){
rAfter.next = null;
}
return fBegin;
}
6、判断链表是否为回文链表
链表的回文结构_牛客题霸_牛客网对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为。题目来自【牛客题霸】https://www.nowcoder.com/share/jump/7547604141710813920410集合了前面找中间节点和翻转节点的思想,是重点题型,画图理解
public boolean chkPalindrome(ListNode A) {
// write code here
//1、先找中间节点
ListNode fast = A;
ListNode slow = A;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
}
//此时slow正处在中间节点的位置,而fast在最后一个节点(如果偶数的话就越界溢出了)
//2、翻转后面的节点
ListNode cur = slow.next;
while(cur != null){
ListNode curNext = cur.next;
cur.next = slow;
slow = cur;
cur = curNext;
}
//3、开始判断是否为回文,此时slow在最后一个节点,head和slow进行比较
while(A != slow){
if(A.val != slow.val){
return false;
}
//针对偶数节点
if(A.next == slow){
return true;
}
A = A.next;
slow = slow.next;
}
return true;
}
7、判定链表相交并求出交点
https://leetcode.cn/problems/intersection-of-two-linked-lists/submissions/513940055
思路:还是可以使用快慢指针,相交节点之后的长度是一样的,所以两个链表的长度不同在前面,所以只需要快指针多走两个链表长度之差,然后再一起走如果他们相遇了(slow == fast)就找到相交节点了
步骤:1、首先先求出两链表的长度
2、求差值
3、先让fast多走len步
4、slow和fast同步走,直到相遇
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
//思路:还是可以使用快慢指针,相交节点之后的长度是一样的,
//所以两个链表的长度不同在前面,所以只需要快指针多走两个链表长度之差,然后再一起走
//如果他们相遇了(slow.next == fast.next)就找到相交节点了
//1、首先先求出两链表的长度
//先假设headA的长度要长
int lenA = 0;
int lenB = 0;
ListNode tmpHA = headA;
ListNode tmpHB = headB;
ListNode fast = null;
ListNode slow = null;
while(tmpHA != null){
tmpHA = tmpHA.next;
lenA ++;
}
while(tmpHB != null){
tmpHB = tmpHB.next;
lenB ++;
}
//2、求差值
int len = lenA - lenB;
if(len < 0){//说明lenB的长度长
len = lenB - lenA;
fast = headB;
slow = headA;
}else {
fast = headA;
slow = headB;
}
//3、先让fast多走len步
while(len > 0){
fast = fast.next;
len--;
}
//4、slow和fast同步走,直到相遇
while(fast != slow ){
fast = fast.next;
slow = slow.next;
}
return slow;
}
}
8、判断链表是否带环
https://leetcode.cn/problems/linked-list-cycle/submissions/513777277
快慢指针,快指针走两步,慢指针走一步,如果有环,则快慢指针一定会相遇
public class Solution {
public boolean hasCycle(ListNode head) {
//快慢指针,快指针走两步,慢指针走一步,如果有环,则快慢指针一定会相遇
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(slow == fast){
return true;
}
}
return false;
}
}
9、返回链表开始入环的第一个节点
https://leetcode.cn/problems/linked-list-cycle-ii/description/
思路:快指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始走,两个指针每次均走一步,最终肯定会在入口点的位置相遇
步骤:
1、先找相遇点(快慢指针,快指针走两步,慢指针走一步,如果有环,则快慢指针一定会相遇)
2、让slow指向相遇点,然后两个指针一起走
public class Solution {
public ListNode hasCycle(ListNode head) {
//快慢指针,快指针走两步,慢指针走一步,如果有环,则快慢指针一定会相遇
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(slow == fast){
return fast;
}
}
return null;
}
public ListNode detectCycle(ListNode head) {
//方法:快指针从链表起始位置开始遍历链表,同时让一个指针从判环时相遇点的位置开始走,
//两个指针每次均走一步,最终肯定会在入口点的位置相遇
//1、先找相遇点(快慢指针,快指针走两步,慢指针走一步,如果有环,则快慢指针一定会相遇)
ListNode hasNode = hasCycle(head);
//2、让slow指向相遇点,然后两个指针一起走
ListNode fast = head;
ListNode slow = hasNode;
//如果有环就开始走
if(slow != null){
while(fast != slow){
fast = fast.next;
slow = slow.next;
}
}
return slow;
}
}
总结
这几道oj题运用了双指针等思想,重点是一定要画图理解,然后慢慢整理思路,考虑各种情况,最后调试
双指针包括:快慢指针、对撞指针、滑动窗口等,以上大部分编程题都用的是快慢指针解决,需了解其思想,掌握应用场景
快慢指针
在数组、序列、链表中以不同速度移动的指针。一般来说,快指针一次走两步,慢指针一次走一步,即快指针是慢指针速度的两倍(走的步数可以依题改变),可以解决:
- 处理链表或数组中的循环的问题(链表带环)
- 找链表中点或需要知道特定元素的位置
等应用场景。以上题大多应用到了这个思想,多练多想