单链表
(链表面试题)
目录
1,链表的中间结点
题目描述
思路
1.数组解法
由于链表不能通过下标访问对应的结点,所以我们将所有的结点存储在数组中,这样就可以通过下标访问数组的中间元素,继而找到链表的中间结点!
1.开辟一个数组用来存放结点
2.遍历链表,将链表中的元素逐个放入数组中
3.当链表为null时退出循环
4.返回数组的中间元素
因为数组长度是从0开始的,所以当我的链表为null时我的数组长度是size=5,5/2=2,如果链表长度是偶数,是4的话,4/2=2,找到的结点依旧是第三个结点.
class Solution {
public ListNode middleNode(ListNode head) {
//创建一个数组,类型是ListNode类,数组大小为100,
ListNode[] A = new ListNode[100];
int size = 0;
while (head != null) {
A[sisze++] = head;
head = head.next;
}
return A[size / 2];
}
}
复杂度分析:
时间复杂度O(n),其中 N 是给定链表中的结点数目。
空间复杂度O(n),即数组 A 用去的空间
2单指针解法
我们可以对方法一进行空间优化,省去数组 A。
我们可以对链表进行两次遍历。第一次遍历时,我们统计链表中的元素个数 N;第二次遍历时,我们遍历到第 N/2 个元素(链表的首节点为第 0 个元素)时,将该元素返回即可。
1.遍历整个链表,记录链表的长度
2.将链表按照总长度的一半再走一遍,直到循环结束
第一遍
遍历单链表记录单链表长度
第二遍
找到中间结点
因为链表长度是从0开始的,所以当我的链表为null时我的size长度是size=5,5/2=2,如果链表长度是偶数,是4的话,4/2=2,找到的结点依旧是第三个结点.
class Solution {
public ListNode middleNode(ListNode head) {
int size = 0; //用于记录链表长度
ListNode cur = head;//
//遍历链表求出链表的长度
while (cur != null) {
++size;
cur = cur.next;
}
int k = 0;
//重新从头开始向后遍历
cur = head;
//因为k是从0开始的,所以只需要小于size/2就行
//如果k是1开始,那么就是k<=size/2
while (k < size / 2) {
++k;
cur = cur.next;
}
return cur;
}
}
复杂度分析:
时间复杂度O(n),其中 N 是给定链表中的结点数目。
空间复杂度O(1),只需要常数空间存放变量和指针
3.快慢指针
1.定义两个指针,一个快指针,一个慢指针。
2.快指着一次走两步,慢指针一次走一步
3.考虑快指针指向哪里时,我们的慢指针刚好走到中间结点
Q:那我们可不可以再去优化一下这个解法,让他只遍历一遍就找到该链表的中间结点?
我们可以继续优化方法二,用两个指针 slow 与 fast 一起遍历链表。slow 一次走一步,fast 一次走两步。那么当 fast 到达链表的末尾时,slow 必然位于中间。
1.定义快慢指针
2.快指针一次走一步,慢指针一次走两步
3.当快指针为null或快指针的next为null时退出循环
前提讲到了,如果有两个中间结点,则返回第二个中间结点,所以我们再看一下这个解法对于结点个数为偶数个的还是否合适
无论该链表是奇数个还是偶数个,都不会影响最终的结果。
判断条件:
当fast==null时,或者fast.next==null时,退出循环。
注意:由于逻辑运算符&&是先判断左侧,左侧为真再去判断右侧,如果我的fast已经是null,那么如果我将fast.next写在左侧就会产生空指针异常,所以我们需要先判断fast是否为null,也就是将fast!=null写在左侧
class Solution {
public ListNode middleNode(ListNode head) {
//定义两个指针,一个快指针,一个慢指针
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;//慢指针一次走一步
fast = fast.next.next;//快指针一次走两步
}
return slow;//返回慢指针
}
}
复杂度分析:
时间复杂度O(n),其中 N 是给定链表中的结点数目。
空间复杂度O(1),只需要常数空间存放 slow 和 fast 两个指针。
2,链表的回文结构
题目描述
思路
思路1:利用数组,判断是否回文
class PalindromeList {
public:
//判断数组是否满足回文结构
bool isReverse(int arr[], int left, int right)
{
while(left < right)
{
if(arr[left] != arr[right])
{
return false;
}
left++;
right--;
}
return true;
}
bool chkPalindrome(ListNode* A)
{
int arr[900];
ListNode* cur = A;
int i = 0, listLength = 0;
while(cur)
{
arr[i++] = cur->val;//将链表中的值保存到数组中
cur = cur->next;
listLength++;//求链表的长度
}
return isReverse(arr, 0, listLength - 1);
}
};
思路2:求链表的中间节点+反转链表
寻找链表的中间节点 mid。
将中间节点 mid 以及之后的节点组成的链表反转。
遍历反转后的链表,当一个一个与原链表的数据域对比,若相同则是回文结构。
1,奇数
2,偶数
3.相交链表
题目描述
思路
如果链表的两条链的长度一样,链表两端对齐,解决这个问题将会变得非常简单,直接分别遍历两个链表,想等时的节点即为所求。我们想办法让链表对齐--分别从a和b遍历链表,分别求出以a开始和以b开始时的链表长度,求出a,b之差的绝对值k。然后再让较长一端先走k步,这样就对齐了。然后再同时遍历链表,两端相等时,这个节点即为所求。
其实,这就是一个快慢指针的解法,快慢指针每次都只走一步,只不过快指针先走使链表对齐。
代码
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
ListNode* a = headA;
ListNode* b = headB;
int count1 = 0;
int count2 = 0;
while(a->next)
{
a = a->next;
count1++;
}
while(b->next)
{
b = b->next;
count2++;
}
if (a != b)
{
return NULL;
}
a = headA;
b = headB;
int k = abs(count1 - count2);
ListNode* LongA = a;
ListNode* shortB = b;
if(count2 > count1)
{
LongA = b;
shortB = a;
}
while(k--)
{
LongA = LongA->next;
}
while(shortB && LongA)
{
LongA = LongA->next;
shortB = shortB->next;
if (shortB == LongA)
return shortB;
}
}
下一个文章再见啦,大家学习快乐哦哦~