剑指offer 第四章链表

向链表尾部插入一个节点时候如果不用哨兵节点,此时会有分类讨论原链表是否为空来讨论两种情况,删除第一个值为指定值的链表也是如此都可以用哨兵节点来防止分类讨论

哨兵节点可以简化创建或删除链表头结点操作代码

双指针法 删除倒数第k个节点

目的是要找到倒数第k+1个节点,先把第一个指针往前移动k次,然后再移动第二个指针,当第一个指针移动到了链表尾部时候第二个指针也就移动到了第k+1处

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    ListNode *dummy=new ListNode(0);
    dummy->next=head;
    ListNode *front=dummy,*back=dummy;
    for(int i=0;i<n;i++)
    {
        front=front->next;
    }
    while(front->next!=nullptr)
    {
        front=front->next;
        back=back->next;
    }
    back->next=back->next->next;
    return dummy->next;
    }
};

链表中环的入口节点

第一种方法:知道链表环的长度的情况

把后指针先往后移动n个单位,然后前指针和后指针一同向前移动,第一次相遇的地方就是链表的环入口 

知道环的长度的方法,就是用一快一慢两个指针来往前进,如果有环的话快的的相对距离超过慢的的一圈后,两个指针就会相遇,相遇后再绕一圈就能知道环的长度

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
    ListNode *inloop=getnodeinloop(head);
    if(inloop==nullptr)
    return nullptr;
    int loopcount=1;
    for(ListNode* start=inloop;start->next!=inloop;start=start->next)
    loopcount++;
    ListNode *fast=head;
    for(int i=0;i<loopcount;i++)
    fast=fast->next;
    ListNode *slow=head;
    while(fast!=slow)
    {
        slow=slow->next;
        fast=fast->next;
    }
    return slow;
    }
    ListNode *getnodeinloop(ListNode *head)
    {
       ListNode *slow=head,*fast=head;
       while(slow!=nullptr&&fast!=nullptr)
       {
          slow=slow->next;
          fast=fast->next;
          if(fast!=nullptr)
          fast=fast->next;
          if(slow==fast)
          return slow;
       }
       return nullptr;
    }
};

不需要知道环中节点数目的做法

快链表假设走了2k步数,慢指针假设走了k步数,多走了k步,多走的k步一定是环长度的整数倍

所以可以再要一个后指针,前指针从相遇的点处出发,两个指针一起移动,当两个指针相遇时,此时的相遇点一定是出发点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
    ListNode *inloop=getnodeinloop(head);
    if(inloop==nullptr)
    return nullptr;
    ListNode *node=head;
    while(node!=inloop)
    {
        node=node->next;
        inloop=inloop->next;
    }
    return node;
    }
    ListNode *getnodeinloop(ListNode *head)
    {
       ListNode *slow=head,*fast=head;
       while(slow!=nullptr&&fast!=nullptr)
       {
          slow=slow->next;
          fast=fast->next;
          if(fast!=nullptr)
          fast=fast->next;
          if(slow==fast)
          return slow;
       }
       return nullptr;
    }
};

两个链表的第一个重合节点

暴力法,对a链表的每一个结点,都去检查b的每一个是否和他相同

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    for(ListNode* node1=headA;node1!=nullptr;node1=node1->next)
    {
        for(ListNode *node2=headB;node2!=nullptr;node2=node2->next)
        {
            if(node1==node2)
            return node1;
        }
    }
    return nullptr;    
    }
};

观察可以发现,两个链表在相遇节点后的所有部分都相同,所以可以从后往前遍历,直到节点开始不相等,可以准备一个栈来方便逆着遍历

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    stack<ListNode*> s1,s2;
    for(ListNode *node1=headA;node1!=nullptr;node1=node1->next)
    s1.push(node1);
     for(ListNode *node1=headB;node1!=nullptr;node1=node1->next)
    s2.push(node1);
    if(s1.top()!=s2.top())
    return nullptr;
    ListNode* node1;
    while(!s1.empty()&&!s2.empty()&&s1.top()==s2.top())
    {
         node1=s1.top();
         s1.pop();
         s2.pop();
    }
    return node1;
    }
};

双指针方法,由于两个链表只有前面的部分不相同,所以可以分别求出两个链表的长度,然后在长链表处优先移动指针,然后同时移动两个头指针,第一次相遇的地方就是入口处

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    int count1=countlist1(headA);
    int count2=countlist1(headB);
    int cha=abs(count1-count2);
    ListNode *longer=count1>count2?headA:headB;
    ListNode *shorter=longer==headA?headB:headA;
    for(int i=0;i<cha;i++)
    longer=longer->next;
    while(longer!=shorter)
    {
        longer=longer->next;
        shorter=shorter->next;
    }
    return longer;
    }
    int countlist1(ListNode *head)
    {
        int count1=0;
        ListNode *node=head;
        while(node!=nullptr)
        {
            count1++;
            node=node->next;
        }
        return count1;
    }
};

有的面试题需要倒着遍历链表,所以需要反转

在反转链表时候除了知道本节点,还要知道前一个节点,因为需要把next指针指向前一位,还需要知道后一位,因为不知道后一位的话链表会断裂,找不到之后的节点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
    ListNode* pre=nullptr;
    ListNode *cur=head;
    while(cur!=nullptr)
    {
        ListNode *next=cur->next;
        cur->next=pre;
        pre=cur;
        cur=next;
    }
    return pre;
    }
};

两个链表相加

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    l1=reverseList(l1);
    l2=reverseList(l2);
    ListNode *dummy=new ListNode(0);//建立一个哑结点方便
    ListNode *node1=dummy;
    int carry=0;
    while(l1!=nullptr||l2!=nullptr)
    {
        int sum=(l1==nullptr?0:l1->val)+(l2==nullptr?0:l2->val)+carry;
        carry=sum/10;
        sum%=10;
        ListNode *newnode=new ListNode(sum);
        node1->next=newnode;
        node1=newnode;
        l1=l1==nullptr?nullptr:l1->next;
        l2=l2==nullptr?nullptr:l2->next;
    }
    if(carry>0)
    node1->next=new ListNode(carry);
    return reverseList(dummy->next);
    }
    ListNode* reverseList(ListNode* head) {
    ListNode* pre=nullptr;
    ListNode *cur=head;
    while(cur!=nullptr)
    {
        ListNode *next=cur->next;
        cur->next=pre;
        pre=cur;
        cur=next;
    }
    return pre;
    }
};

重排链表

这道题重排方法是把链表对半分开,然后把后一半链表反转,然后逐个连接到 

如何对半分,还是一个快指针一个慢指针,一个每次向前两步,一个每次只向前一步 

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
ListNode* reverseList(ListNode* head) {
    ListNode* pre=nullptr;
    ListNode *cur=head;
    while(cur!=nullptr)
    {
        ListNode *next=cur->next;
        cur->next=pre;
        pre=cur;
        cur=next;
    }
    return pre;
    }
    void reorderList(ListNode* head) {
    ListNode *dummy=new ListNode(0);
    dummy->next=head;
    ListNode *fast=dummy;
    ListNode *slow=dummy;
    while(fast!=nullptr&&fast->next!=nullptr)//偶数的时候是他的下一个为空,奇数的时候是他自己为空
    {
        slow=slow->next;
        fast=fast->next;
        if(fast->next!=nullptr)
        fast=fast->next;
    }
    ListNode *temp=slow->next;
    slow->next=nullptr;
    link(head,reverseList(temp),dummy);
    }
    void link(ListNode* node1,ListNode* node2,ListNode* head)
    {
        ListNode *pre=head;
        while(node1!=nullptr&&node2!=nullptr)
        {
            ListNode *temp=node1->next;
            pre->next=node1;//把节点一粘上去
            node1->next=node2;
            pre=node2;
            node1=temp;
            node2=node2->next;
        }
        if(node1!=nullptr)
        pre->next=node1;
    }
};

回文链表

和刚刚的思路一样,还是把链表对半分 

注意快慢指针的边界讨论

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
    if(head==nullptr||head->next==nullptr)
    return true;
    ListNode *dummy=new ListNode(0);
    dummy->next=head;
    ListNode *slow=dummy;
    ListNode *fast=dummy;
    while(fast->next!=nullptr&&fast->next->next!=nullptr)
    {
        slow=slow->next;
        fast=fast->next;
        if(fast!=nullptr)
        fast=fast->next;
    }
    ListNode *a=(fast->next==nullptr?slow->next:slow->next->next);
    slow->next=nullptr;
    return bijiao(dummy->next,reverseList(a));
    }
    bool bijiao(ListNode *head1,ListNode *head2)
    {
        while(head1!=nullptr&&head2!=nullptr)
        {
            if(head1->val!=head2->val)
            return false;
            head1=head1->next;
            head2=head2->next;
        }
        return head1==nullptr&&head2==nullptr;
    }
    ListNode* reverseList(ListNode* head) {
    ListNode* pre=nullptr;
    ListNode *cur=head;
    while(cur!=nullptr)
    {
        ListNode *next=cur->next;
        cur->next=pre;
        pre=cur;
        cur=next;
    }
    return pre;
    }
};

双向链表和循环链表

一题展平链表 由于每个子链表展开和之前长链表展平步骤一样,所以想到用递归的方式来展平

递归函数获得每次子孩子的展平后的尾部

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* prev;
    Node* next;
    Node* child;
};
*/

class Solution {
public:
    Node* flatten(Node* head) {
        dfs(head);
        return head;
    }
    Node* dfs(Node *head)
    {
        
        Node *node1=head;
        Node *tail=nullptr;
        while(node1!=nullptr)
        {
            Node *next1=node1->next;
            if(node1->child!=nullptr)
            {
                Node *child1=node1->child;
                Node *childtail=dfs(child1);//遇到有分支就去下一层递归,获得下一层状态
                node1->child=nullptr;
                child1->prev=node1;
                node1->next=child1;
                childtail->next=next1;
                if(next1!=nullptr)
                {
                    next1->prev=childtail;
                }
                tail=childtail;
            }
            else
            tail=node1;
            node1=next1;
        }
        return tail;
    }
};

 排序的循环链表

要想插入后仍然有序,就需要插入原来的链表中他的大小刚好在他的中间的两个节点中

如果说他的值最大或者最小,则直接插入到原链表中最大值的节点之后即可

还要排除掉没有节点以及有一个节点情况

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* next;

    Node() {}

    Node(int _val) {
        val = _val;
        next = NULL;
    }

    Node(int _val, Node* _next) {
        val = _val;
        next = _next;
    }
};
*/

class Solution {
public:
    Node* insert(Node* head, int insertVal) {
    Node *node=new Node(insertVal);
    if(head==nullptr)
    {
      head=node;
      node->next=head;
    }
    else if(head->next==head)
    {
        head->next=node;
        node->next=head;
    }else
    {
        Node *cur=head;
        Node *next1=head->next;
        Node *biggest=head;
        while(!(cur->val<=node->val&&next1->val>=node->val)&&next1!=head)
        {
            cur=next1;
            next1=next1->next;
            if(cur->val>=biggest->val)//注意这儿相等时候也一直往后,要不然当最大值有多个时候会发生错误
            biggest=cur;
        }
        if(cur->val<=node->val&&next1->val>=node->val)
        {
            cur->next=node;
            node->next=next1;
        }
        else
        {
            node->next=biggest->next;
            biggest->next=node;
        }
    }
    return head;
    }
};

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值