力扣HOT100链表题

206、反转链表

(1)双指针法(迭代)

//自己的思路,该方法无法处理空链表的情况
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode*cur, *pre;
        cur = head;
        pre = head->next;  //若为空链表则head为空,此句报错。
        cur->next = NULL;
        while(pre!=NULL){
            ListNode* t = pre->next;
            pre->next = cur;
            cur = pre;
            pre = t;
        }
        return cur;
    }
};
//官方解答
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode*cur, *pre;
        cur = NULL;
        pre = head;
        while(pre!=NULL){
            ListNode* t = pre->next;
            pre->next = cur;
            cur = pre;
            pre = t;
        }
        return cur;
    }
};

(2)递归法

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head==NULL||head->next==NULL)
            return head;
        ListNode* result = reverseList(head->next);
        head->next->next = head;
        head->next = NULL;
        return result;
    }
};

使用递归函数,一直递归到链表的最后一个结点,该结点就是反转后的头结点,记作 ret 。此后,每次函数在返回的过程中,让当前结点的下一个结点的 next 指针指向当前节点。同时让当前结点的 next 指针指向 NULL,从而实现从链表尾部开始的局部反转。当递归函数全部出栈后,链表反转完成。

21、合并两个有序链表

(1)迭代法(双指针法)
添加哑结点简化代码,当一方为空节点时结束迭代,把非空一方的剩余节点加入答案中。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* preHead = new ListNode();
        ListNode* pre = preHead;
        while(l1!=NULL && l2!=NULL){
            if(l1->val<=l2->val){
                pre->next = l1;
                l1 = l1->next;
            }
            else{
                pre->next = l2;
                l2 = l2->next;
            }
            pre = pre->next;
        }
        pre->next = (l1==NULL) ? l2 : l1;
        return preHead->next;
    }
};

(2)递归法
比较两个链表的头节点,递归调用函数,注意终止条件。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(l1==nullptr){
            return l2;
        }
        else if(l2==nullptr){
            return l1;
        }
        else if(l1->val <= l2->val){
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        }
        else{
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
};

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

(1)计算链表长度
技巧:在对链表进行操作时,一种常用的技巧是添加一个哑节点(dummy node),它的next 指针指向链表的头节点。这样一来,我们就不需要对头节点进行特殊的判断了。结果返回哑结点的next(即原链表的头结点)。

class Solution {
public:
    int getLength(ListNode* head){
        int Length = 0;
        while(head){
            ++Length;
            head = head->next;
        }
        return Length;
    }
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        int length = getLength(head);
        //声明哑节点,不需要对头结点进行特殊判断
        ListNode* dummy = new ListNode(0, head);  
        ListNode* curNode = dummy;
        for(int i=1; i<length-n+1; i++){
            curNode = curNode->next;
        }
        curNode->next = curNode->next->next;
        //声明另一个节点用来应对删除的是头结点的情况
        //(若删除头结点且返回head的话,则返回的只是第一个节点,不是链表)
        ListNode* res = dummy->next;  
        delete dummy;
        return res;
    }
};

(2)双指针法(快慢指针)

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, head);
        ListNode* first = dummy;  //快指针
        ListNode* second = dummy;  //慢指针
        int index =0;
        //快慢指针相差n个位置
        while(index<n){
            first = first->next;
            index++;
        }
        //快指针到终点,慢指针到达被删除节点的前置节点
        while(first->next){
            first = first->next;
            second = second->next;
        }
        second->next = second->next->next;
        ListNode* res = dummy->next;
        delete dummy;
        return res;
    }
};

(3)栈

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* dummy = new ListNode(0, head);
        ListNode* cur = dummy;
        stack<ListNode*> stk;
        while(cur){
            stk.push(cur);
            cur = cur->next;
        }
        for(int i=0; i<n; i++){
            stk.pop();
        }
        ListNode* target = stk.top();
        target->next = target->next->next;
        ListNode* res = dummy->next;
        delete dummy;
        return res;
    }
};

106、相交链表

(1)双指针

//自己写的,时间复杂度O(m+n),空间复杂度O(1)
class Solution {
public:
    int getlength(ListNode* head){
        int length = 0;
        while(head){
            length++;
            head = head->next;
        }
        return length;
    }
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        int lengthA = getlength(headA);
        int lengthB = getlength(headB);
        ListNode* pA(headA), *pB(headB);
        if(lengthA>lengthB){
            for(int i=0; i<lengthA-lengthB; i++)
                pA = pA->next;
        }
        else{
            for(int i=0; i<lengthB-lengthA; i++)
                pB = pB->next;
        }
        do{
            if(pA==pB) return pA;
            pA = pA->next;
            pB = pB->next;
        }while(pA);
        return NULL;
    }
};
//官方思路路人解法
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(!headA || !headB)
            return nullptr;
        ListNode* pA(headA), *pB(headB);
        while(pA!=pB){
        	//当两个链表长度相同时,两指针最终都会指向null,从而跳出循环
            pA = (pA? pA->next: headB);
            pB = (pB? pB->next: headA);
        }
        return pA;
    }
};

(2)哈希表法

//官方思路,路人解法
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        set<ListNode*> mSet;  //使用set,时间复杂度比unordered_set长
        ListNode* pA = headA;
        ListNode* pB = headB;
        while(pA){
            mSet.insert(pA);
            pA = pA->next;
        }
        while(pB){
            if(mSet.find(pB)!=mSet.end())
                return pB;
            pB = pB->next;
        }
        return nullptr;
    }
};

141、环形链表

(1)hash表法

//自己的思路
class Solution {
public:
    bool hasCycle(ListNode *head) {
        unordered_set<ListNode*> list;
        ListNode* curNode = head;
        while(curNode){
            if(list.count(curNode)>0)
                return true;
            list.insert(curNode);
            curNode = curNode->next;
        }
        return false;
    }
};

(2)双指针法(龟兔赛跑)

//官方解答:快慢指针,只要有环,快慢指针一定会重合。
//快指针从第二位开始是因为若都从head开始的话进不去while循环。
class Solution {
public:
    bool hasCycle(ListNode *head) {
        if(head==nullptr || head->next==nullptr)
            return false;
        ListNode* fast(head->next), *slow(head);
        while(fast!=slow){
            if(fast==nullptr || fast->next==nullptr)//若fast->next==nullptr判断放在前,是否会判断出错?
                return false;
            fast = fast->next->next;
            slow = slow->next;
        }
        return true;
    }
};

142、环形链表II

(1)哈希表法

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        unordered_set<ListNode*> mSet;
        ListNode* cur = head;
        while(cur){
            if(mSet.count(cur))
                return cur;
            mSet.insert(cur);
            cur = cur->next;
        }
        return NULL;
    }
};

(2)双指针法
先用快慢指针判断是否为环形链表(思路同141.环形链表,但快慢指针同时从头节点出发),慢指针一次前进1步快指针一次前进2步,若有环则必在环内相遇,设链表头部到环入口的距离为a,慢指针在环内走过的距离为b,而快指针在环内已经走过n圈,则快指针走过的距离为a+n(b+c)+b,且快指针走过的距离始终为慢指针的2倍,则有a+n(b+c)+b=2(a+b),即a=c+(n-1)(b+c)。因此,当快慢指针相遇时,再使用额外的指针ptr指向头节点,ptr与慢指针同时出发,当慢指针走过n-1圈外加c的距离后,与ptr在入环处相遇。


在这里插入图片描述

中间加入各种边界判断,如空链表、单节点链表、非环形链表等。

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast(head), *slow(head);
        while(fast!=nullptr){
            slow = slow->next;
            if(fast->next==nullptr)
                return NULL;
            fast = fast->next->next;
            if(slow==fast){
                ListNode* ptr = head;
                while(ptr!=slow){
                    ptr = ptr->next;
                    slow = slow->next;
                }
                return ptr;
            }
        }
        return NULL;
    }
};

2、两数相加

(1)暴力解法
自己的思路,因为两个链表非空,所以用两个指针分别从两个链表的头节点同时出发,将对应的val相加存在任意链表(此处为l1)的相应位置,注意考虑进位的情况。当有链表到达末尾时,用从哑节点出发的指针cur来连接还未到达末尾的链表剩余的部分,同样别忘记考虑进位的情况。待两个链表都到达末尾时,判断最后一个节点的val是否有进位,若有还需new一个节点,此时用到另一个指针end(比cur落后一位)。
该方法过于冗杂,考虑情况较为杂乱。时间复杂度为O(max(m,n)),空间复杂度为O(1)。

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        //if(l1->val==0) return l2;
        //else if(l2->val==0) return l1;
        ListNode* dummy = new ListNode(0,l1);
        ListNode* cur = dummy;
        int ext = 0;
        while(l1!=nullptr && l2!=nullptr){
            l1->val = l1->val + l2->val + ext;
            ext = l1->val>9 ? 1 : 0;
            l1->val = l1->val%10;
            l1 = l1->next;
            l2 = l2->next;
            cur = cur->next;
        }
        ListNode* end = cur;
        //还得判断剩余链表节点的val+ext是否大于9
        if(l1==nullptr){
            cur->next = l2;
            cur = cur->next;
        }
        else{
            cur->next = l1;
            cur = cur->next;
        }
        while(cur!=nullptr){
            cur->val = cur->val + ext;
            ext = cur->val>9 ? 1 : 0;
            cur->val = cur->val%10;
            cur = cur->next;
            end = end->next;  //end始终比cur慢一位
        }
        //判断最后一位是否大于9
        if(ext>0){
            ListNode* endNode = new ListNode(1);
            end->next = endNode;  //在链表末尾添加元素必须使用最后一个元素
        }
        ListNode* res = dummy->next;
        delete dummy;
        return res;
    }
};

(2)官方解法
由于输入的两个链表都是逆序存储数字的位数的,因此两个链表中同一位置的数字可以直接相加。我们同时遍历两个链表,逐位计算它们的和,并与当前位置的进位值相加。具体而言,如果当前两个链表处相应位置的数字为 n1、n2,进位值为carry,则它们的和为 n1+n2+carry;其中,答案链表处相应位置的数字为 (n1+n2+carry)%10,而新的进位值为 1。如果两个链表的长度不同,则可以认为长度短的链表的后面有若干个 0 。此外,如果链表遍历结束后,有carry>0,还需要在答案链表的后面附加一个节点,节点的值为 1。
该方法将两个链表长度相同和长度不同的情况统一了起来,且答案链表中的每个节点都是new出来的(学到了)。时间复杂度O(max(m,n)),空间复杂度为O(max(m,n))。

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* head(nullptr), *tail(nullptr);
        int carry = 0;
        while(l1!=nullptr || l2!=nullptr){
            int n1 = l1!=nullptr ? l1->val : 0;
            int n2 = l2!=nullptr ? l2->val : 0;
            int sum = n1 + n2 + carry;
            if(head==nullptr)
                head = tail = new ListNode(sum%10);
            else{
                tail->next = new ListNode(sum%10);
                tail = tail->next;
            }
            carry = sum>9 ? 1 : 0;
            if(l1!=nullptr) l1 = l1->next;
            if(l2!=nullptr) l2 = l2->next;
        }
        if(carry>0)
            tail->next = new ListNode(1);
        return head;
    }
};

234、回文链表

(1)栈
自己想的,先将链表的元素依次存入栈中,再用cur指向链表头。每次从栈顶取出一个节点元素同时cur指针前进一位,判断从栈中取出节点的val和cur指向节点的val是否相等,若不等则为非回文链表(注意空链表也是回文链表,题中未指出)。
该方法时间复杂度为O(n),空间复杂度为O(n)。

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(head==nullptr)
            return true;
        stack<ListNode*> mStack;
        for(auto p1=head; p1!=nullptr; p1=p1->next)
            mStack.push(p1);
        ListNode* cur = head;
        while(cur!=nullptr){
            auto p2 = mStack.top();
            if(p2->val!=cur->val)
                return false;
            mStack.pop();
            cur = cur->next;
        }
        return true;
    }
};

(2)双指针法
官方解答,
整个流程可以分为以下五个步骤:

  1. 找到前半部分链表的尾节点。
  2. 反转后半部分链表。
  3. 判断是否回文。
  4. 恢复链表。
  5. 返回结果。

执行步骤一,我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。
我们也可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针恰好到链表的中间。通过慢指针将链表分为两部分。若链表有奇数个节点,则中间的节点应该看作是前半部分。

步骤二可以使用「206. 反转链表」问题中的解决方法来反转链表的后半部分。

步骤三比较两个部分的值,当后半部分到达末尾则比较完成,可以忽略计数情况中的中间节点。

步骤四与步骤二使用的函数相同,再反转一次恢复链表本身。

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(head==nullptr)
            return true;
        ListNode* firstHalf = endOfFirstHalf(head);
        ListNode* secondHalf = reverseList(firstHalf->next);
        ListNode* p1(head), *p2(secondHalf);
        bool result = true;
        while(result && p2!=nullptr){
            if(p1->val!=p2->val)
                result = false;
            p1 = p1->next;
            p2 = p2->next;
        }
        firstHalf->next = reverseList(secondHalf);
        return result;
    }
    ListNode* endOfFirstHalf(ListNode* head){
        ListNode* fast(head), *slow(head);
        while(fast->next!=nullptr && fast->next->next!=nullptr){
            slow = slow->next;
            fast = fast->next->next;
        }
        return slow;
    }
    ListNode* reverseList(ListNode* head){
        ListNode* pre(nullptr), *cur(head);
        while(cur!=nullptr){
            ListNode* next = cur->next;
            cur->next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值