目录
一.链表的回文结构
1、题目要求
对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900。
链表的回文结构_牛客题霸_牛客网 (nowcoder.com)
2、基本思路
首先我们先要弄清楚回文结构是什么意思?
回文结构就类似于一个山峰,有一个顶峰然后两边对称.
这就是一个回文结构,我们不难发现这个结构左边和右边都是对称的。
(1)、用常规方法解决
本题是判断一个链表是否为回文结构, 因此我们需要从链表的头和尾同时遍历然后比较是否相等,假如全部相等那么我们返回true,否则返回false。
首先我们拿到的是一个单向链表,我们需要找到他的中间结点,然后从中间节点往后开始逆置,从而实现从后面结点和头结点开始遍历链表是否为回文结构。
假如这个是我们的目标链表,首先我们可以利用fast和slow结点来找到我们的中间结点,而我们的slow也就是中间结点
接下来我们就需要逆置slow后面的链表即可,这时候我们的slow以及成为了尾结点
我们再利用head和slow来进行比较各自的val值是否相同,但是我们需要思考,假如我们的链表尾偶数怎么办?
我们不难发现其实为偶数的时候我们可以利用head.next == slow来判断最后的结果。这样就解决了这个问题。
(2)、利用Stack求解
这个题我们可以利用栈来求解,首先我们需要遍历一遍链表,得到相应的链表长度,然后将链表的前半段入栈,因为栈的特性为先进后出,所以我们可以用后半段的链表结点依次与栈顶结点的元素比较,最后达成一个判断是否回文的效果
3、代码实现
(1)、常规方法
public boolean chkPalindrome(ListNode head) {
if(head == null){
return true;
}
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(head != slow){
if(head.val != slow.val){
return false;
}
if(head.next == slow){
return true;
}
slow = slow.next;
head = head.next;
}
return true;
// write code here
}
(2)、Stack
public boolean chkPalindrome(ListNode A) {
//https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?
//可以利用Stack做
Stack<Integer> stack = new Stack<>();
ListNode cur = A;
int count1 = 0;
while(cur != null){//求出链表长度
count1++;
cur = cur.next;
}
count1 /= 2;
int count2 = count1;
while(count1 != 0){
stack.push(A.val);
A = A.next;
count1--;
}//链表前半段元素入栈
while(count2 != 0){
if(stack.pop() == A.val){
A = A.next;
}else{
return false;
}
count2--;
}
return true;
// write code here
}
二、相交链表
1、题目要求
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。
图示两个链表在节点 c1 开始相交:
来源:力扣(LeetCode)
2、题目思路
首先题目给我们两个链表,我们需要判断他们之间是否具有一个相同的结点使他们有相同的部分,我们可以想到设置两个结点去遍历两个不同的链表,找出相同点。
首先我们可以定义两个结点p1和p2来分别遍历headA和headB,我们可以让两个结点开始遍历我们的链表,
假如我们的headA比headB长,也就是说我们p2比p1首先遍历完一边链表,当他遍历完之后p1与p2之间会有一个距离为k,这个k也就是两个链表相差的长度,当p2遍历完了之后我们将他安排去遍历headA
当我们p1遍历完整个链表的时候我们让他去遍历headB
我们不难发现当p1走完整个headA之后两个会在一个结点相遇,因为两个结点有一个距离k,这个k也就是相同结点之前两个链表长度的差距,当我们重置了p1与p2之后就相当于吧两个链表的长度差清零,从而让两个结点回到起跑线上,从而各自开始走,直到p1 == p2 的时候也就发现了相同结点。
3、代码实现
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null && headB == null){
return null;
}
ListNode p1 = headA;
ListNode p2 = headB;
//假设A为长链表,B为短链表.那么p2比p1先走完,假如p2走完p1还剩k个结点到达尾结点
//那么他们两个链表的差值为k,p2走完之后再去走headA,p1走完之后去走headB,因为之前他比p1少走
//k个结点,因此交换之后正好走了这个差值之后两个结点相遇获得交叉结点
while(p1 != p2){
p1 = (p1 == null) ? headB : p1.next;
p2 = (p2 == null) ? headA : p2.next;
}
return p2;
}
三、环形链表
1、题目要求
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
来源:力扣(LeetCode)
不允许修改 链表。
来源:力扣(LeetCode)
2、题目思路
首先如果我们这个链表是一个环形链表,那么我们可以设置两个结点,一个快结点,一个慢结点,两个从head开始出发,直到相遇,假如可以相遇那么就说明是一个环形链表,否则不是,这就和我们操场跑步一样,一个速度快的人,一个速度慢的人两个人开始跑,两个人肯定会相遇,只是为了相遇可能快的人跑的圈更多。
但是我们有一个问题值得思考一下,那我们设置两个结点,一个快结点,一个慢结点。这个快结点和慢结点他们两个速度差是多少合适呢?
假设链表带环,两个指针最后都会进入环,快结点先进环,慢结点后进环。当慢结点刚进环时,可能就和快结点相遇了,最差情况下两个指针之间的距离刚好就是环的长度。此时,两个指针每移动一次,之间的距离就缩小一步,不会出现每次刚好是套圈的情况,因此:在满指针走到一圈之前,快指针肯定是可以追上慢指 针的,即相遇。因此我们快结点一次走两步,慢结点一次走一步这样是最佳的方案,因为二者进入环之后最小长度为1,不会出现死循环的情况,因为步数差值都为一,只要在圈内肯定会相遇
那为什不可以让快结点走三步,四步呢?
假如我们快结点进入环之后走了三步,慢结点走一步,到最后两个结点都不会相遇。四步也是一样.
具体可以画图思考。
3、代码实现
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(fast == slow){
return true;
}
}
return false;
}
四、求环的入口
1、题目要求
给定一个链表的头节点 head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
来源:力扣(LeetCode)
2、题目思路
这个题目我是参考了一下《剑指offer》。
首先我们要求他的环入口结点,我们得判断他是不是一个环形链表,所以我们就可以借鉴题目三的做法,首先利用快慢结点同时从头结点出发,如果快的追上了慢的这说明这个链表为一个环形链表,否则不是我们返回null。
然后我们把上面判断完了我们就可以开始寻找链表的入口结点了
这个题我们可以利用一种数学的思路来求解,首先因为这个链表是带环的,所以我们可以吧head到环入口点的距离设定为a,然后环的总长度我们可以设定为b,fast与slow在环内相遇点距离环入口点设定为k。
首先我们可以证明出slow在相遇时走的路程肯定是小于a+b的,也就是说slow结点走的路程不会多余一个圈,因此fast与slow相遇时,肯定是fast转了至少一圈才和slow相遇,因此我们设定m为fast与slow相遇时fast转的圈数。
因此我们就可以求出
slow在相遇时走的路程为 a + b - k 。
fast 在相遇时走的路程为 a + mb + b - k。
因为fast的速度是slow的二倍,所以fast的路程也是slow的二倍,因此我们可以列出以下表达式:
2 *( a + b - k)= a + mb + b - k
解出这个方程式可以得出一个关系等式也就是
a = (m - 1)b + k
和圈数无关。
从这个表达式我们不难看出不管我们的fast走的多少圈,他的 a == b ,因此我们只需当他们在相遇点相遇时,我们利用一个结点cur从头结点开始走,和slow速度一样,当 cur == slow 时他们相遇的结点也就是我们所求的环的入口点。
3、代码实现
public ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
//说明有环
fast = head;
while(fast != slow){
fast = fast.next;
slow = slow.next;
}//a == k
return slow;
}
}
return null;
}