Leetcode初级算法(链表篇)

Posted 南云之苑

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Leetcode初级算法(链表篇)相关的知识,希望对你有一定的参考价值。

删除链表的倒数第N个节点

感觉自己对于链表的知识还是了解的不够深入,所以没有想到用双指针进行操作。我的想法是这样的,首先计算整个链表的长度,然后遍历到长度减去n的节点处,执行删除操作。
自己的代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* cur=head;
        ListNode *cou=head;
         int i=0;
        
        int count=1;
        while(cou->next!=NULL)
        {
            cou=cou->next;
            count++;
        }
        if(count==1) return NULL;
        if(count==n) {head=head->next;
                     return head;}
        
        while(head->next!=NULL && i<count-1-n)
        {
            head=head->next;
            i++;
        }
        if(head->next!=NULL)
        {
        ListNode *temp;
        temp=head->next;
        head->next=temp->next;
        }

        return cur;
    }
};

相信看过代码后都会觉得这个代码的逻辑比较奇怪,尤其是

if(count==1) return NULL;        if(count==n) {
head=head->next;
return head;
}

这段代码,感觉是根据测试案例试出来的。的确是这样的,我的代码在[1,2]去掉倒数第二个元素或者[1,2,3]去掉倒数第三各元素的时候总是会去除中间的额元素,经排查原因应该是head节点的理解有些问题。我认为的head是头指针,head->next才是第一个结点,但这代码里的意思好像是head就已经是第一个节点了,所以在后面判断的时候有些bug。当然我们还是习惯用比较常规的方法就是用双指针的方法进行问题的解决:
这里放出大神的代码:

class Solution{
public:
ListNode* removeNthFromEnd(ListNode* head,int n){
if(!head->next) return NULL;
LisNode *pre=head,*cur=head;
for(int i=0;i<n;i++) cur=cur->next;
if(!cur) return head->next;
while(cur->next){
cur=cur->next;
pre=pre->next;
}
pre->next=pre->next->next;
return head;
}
};

下面我来解释一下这段代码的意思,定义了两个指针,两个指针相差n个距离,这样的话当其中一个指针指向结尾处的时候,另一个指针就自然指向了我们需要删除的前一个元素位置了,然后执行删除操作,这样非常的快捷。

反转链表

感觉这道题有些帅,试着走了一遍逻辑,并不是像我之前以为的一个新链表然后反向遍历读出数据,但是这个逻辑是一次反转一个,用到dummy始终指向头结点、cur始终指向当前的节点、tmp指向下一个结点,逻辑就是将cur下一个指向tmp的下一个结点,将tmp指向头结点,然后dummy再指向tmp,然后再次进行迭代。仔细思考一下这个反转转的真的很有智慧。代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(!head)   return head;
        ListNode *dummy=new ListNode(-1);
        dummy->next=head;
        ListNode *cur=head;
        while(cur->next)
        {
            ListNode *tmp=cur->next;
            cur->next=temp->next;
            tmp->next=dummy->next;
            dummy->next=tmp;
        }
        return dummy->next;
    }
};

合并两个有序链表

这题比较简单,就是不断地判断;如果有一个链表已经比较晚了,则直接把后面所有的元素再加到新链表中就OK了。代码如下:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(l1!=NULL&&l2==NULL) return l1;
        if(l1==NULL&&l2!=NULL) return l2;
        ListNode head(0); 
        ListNode* l3=&head;
        while(l1!=NULL&&l2!=NULL)
        {
            if(l1->val<l2->val)
            {
                l3->next=l1;
                l1=l1->next;
            }
            else
            {
                l3->next=l2;
                l2=l2->next;
            }
            l3=l3->next;
                
        }
        
        if(l1!=NULL)
            l3->next=l1;
        if(l2!=NULL)
            l3->next=l2;
        
        return head.next;
    }
};

回文链表

解释在代码注释里了,感觉学到了一招快速到达链表的中央了(slow与fast的应用,fast->next->next是slow->next速度的两倍,当fast->next指向结尾,slow指向中间!!)

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        //O(n)时间表示不能有嵌套循环, O(1)空间表示不能再次添加新链表
        if(!head||!head->next) return true;
        ListNode *slow=head,*fast=head;
        stack<int> s;
        s.push(head->val);
        while(fast->next!=NULL && fast->next->next!=NULL)
        {
            slow=slow->next;
            fast=fast=fast->next->next;  //fast的速度是slow的两倍,这样就能保证slow最后能找到链表的中间
            s.push(slow->val);
        }
        if(fast->next==NULL)
            s.pop();   //说明是奇数个,中间的就不用进行比较了,所以直接pop掉比较后面的
        
        while(slow->next!=NULL)
        {
            slow=slow->next;
            int tmp=s.top();
            s.pop();
            if(tmp!=slow->val) return false;
        }
     return true;   
    }
};

环形链表

环形链表是循环链表的最简单形式,即首尾是相连的。所以若我们指定两个指针,fast和slow。两个指针同时从头结点开始出发,fast指针走两步,slow指针走一步;若链表有环,这两个指针肯定会在某点相遇;若链表无环,fast会直接先到达NULL。因为不断的循环,只要有环就一定会相遇,如果没环则一定会存在NULL。
代码如下:

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

以上是关于Leetcode初级算法(链表篇)的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode与《代码随想录》链表篇:做题笔记与总结-JavaScript版

Leetcode刷题笔记之链表篇234. 回文链表

Leetcode刷题笔记之链表篇141. 环形链表

Leetcode刷题笔记之链表篇160. 相交链表

Leetcode刷题笔记之链表篇160. 相交链表

Leetcode刷题笔记之链表篇234. 回文链表