oj链表讲解(第一弹~~~)

单链表

(链表面试题)

目录

单链表

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;
    }
 
}

下一个文章再见啦,大家学习快乐哦哦~ 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值