leetcode经典题目(7)--链表

1. 找两个链表的交点(NO.160)

题目描述:编写一个程序,找到两个单链表相交的起始节点。要求时间复杂度为 O(N),空间复杂度为 O(1)。如果不存在交点则返回 null。
解题思路
设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。
当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。
如果不存在交点,那么 a + b = b + a,以下实现代码中 l1 和 l2 会同时为 null,从而退出循环。

/**
 * 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) {
    
    
        ListNode *l1 = headA, *l2 = headB;
        while (l1 != l2){
    
    
            l1 = (l1 == nullptr) ? headB : l1->next;
            l2 = (l2 == nullptr) ? headA : l2->next;
        }
        return l1;
    }
};

暴力解法:遍历两个链表,找交点。

class Solution {
    
    
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
    
    
        if (headA == nullptr || headB == nullptr)
            return nullptr;
        ListNode *idx = headA;
        while (idx != nullptr){
    
    
            ListNode *now = headB;
            while (now != nullptr){
    
    
                if (now == idx)
                    return now;
                now = now->next;
            }
            idx = idx->next;
        }
        return nullptr;
    }
};

如果只是判断是否存在交点,有两种解法:
(1) 把第一个链表的结尾连接到第二个链表的开头,看第二个链表是否存在环;
(2) 直接比较两个链表的最后一个节点是否相同。

2. 链表反转(NO.206)

题目描述:反转一个单链表。
解法一:使用三个指针。

class Solution {
    
    
public:
    ListNode* reverseList(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr)
            return head;
        ListNode *pre = nullptr, *pNode = head, *pNext = head->next;
        while(pNext != nullptr){
    
    
            pNode->next = pre;
            pre = pNode;
            pNode= pNext;
            pNext = pNext->next;
        }
        pNode->next = pre;
        return pNode;
    }
};

解法二:使用头插法创建一个新链表。

class Solution {
    
    
public:
    ListNode* reverseList(ListNode* head) {
    
    
        ListNode *newHead = nullptr;
        while (head != nullptr){
    
    
            ListNode *tmp = head;
            head = head->next;
            tmp->next = newHead;
            newHead = tmp;           
        }
        return newHead;
    }
};
3. 合并两个有序链表(NO.21)

题目描述:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
解题思路:使用尾插法,建立一个新链表。过程与归并两个有序数组一样。

class Solution {
    
    
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    
    
        ListNode *res = (ListNode*)malloc(sizeof(ListNode));
        res->next = nullptr;
        ListNode *p = res;
        while (l1 != nullptr && l2 != nullptr){
    
    
            ListNode *tmp;
            if (l1->val < l2->val){
    
    
                tmp = l1;
                l1 = l1->next;
            }
            else{
    
    
                tmp = l2;
                l2 = l2->next;
            }
            p->next = tmp;
            p = tmp;
        } 
         if (l1 != nullptr){
    
    
             p->next = l1;
         }  
         if (l2 != nullptr){
    
    
             p->next = l2;
         } 
         return res->next;
    }
};

解法二:使用递归方法。

class Solution {
    
    
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
    
    
        if (l1 == nullptr)
            return l2;
        if (l2 == nullptr)
            return l1;
        ListNode *newHead = nullptr;
        if (l1->val < l2->val){
    
    
            newHead = l1;
            newHead->next = mergeTwoLists(l1->next,l2);
        }
        else{
    
    
            newHead = l2;
            newHead->next = mergeTwoLists(l1,l2->next);
        }
        return newHead;
    }
};
4. 删除链表中的重复元素(NO.83)

题目描述:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
解法一:寻找与当前节点值相同的后续节点,释放空间,并将当前节点的next指针指向下一个与它的值不相同的节点。注意如果最后几个节点值相同,则tmp和pNode会是空指针,所以要加入相应的判断条件。

class Solution {
    
    
public:
    ListNode* deleteDuplicates(ListNode* head) {
    
    
        ListNode *pNode = head;
        while(pNode != nullptr && pNode->next != nullptr){
    
    
            ListNode *tmp = pNode->next;
            while (tmp != nullptr && tmp->val == pNode->val){
    
    
                ListNode *del = tmp;
                tmp = tmp->next;
                delete del;//释放节点空间
            }
            pNode->next = tmp;
            pNode = tmp; 
        } 
        return head;   
    }
};

解法二:使用递归。

class Solution {
    
    
public:
    ListNode* deleteDuplicates(ListNode* head) {
    
    
        if (head == nullptr || head->next == nullptr)
            return head;
        head->next = deleteDuplicates(head->next);
        return (head->val == head->next->val) ? head->next : head;
    }
};
5. 删除链表的倒数第N个节点

题目描述:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
我的解法:先遍历一次链表,获得链表的长度length。再遍历找到倒数第n个节点的前一个节点,即正数第length-n个节点,然后将倒数第n个节点删除。注意n==length的话,要删除的是头结点。

class Solution {
    
    
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    
    
        int length = 0;
        ListNode *tmp = head;
        while (tmp != nullptr){
    
    
            length++;
            tmp = tmp->next;
        }
        int k = length - n;//要删除节点的前一个节点
        if (k == 0){
    
    //要删除的是头结点
            ListNode *del = head;
            head = head->next;
            delete del;
        }  
        else{
    
    
            int i = 1;
            ListNode *pNode = head;
            while (i < k){
    
    
                pNode = pNode->next;
                i++;
            }
            ListNode *del = pNode->next;
            pNode->next = del->next; 
            delete del;
        }
        return head;     
    }
};

进阶:通过一趟扫描实现删除过程。使用两个指针l1和l2,首先让l1移动到第n+1个节点处,让l2指向头结点,这样,当l1移动到尾结点时,l2正好移动到倒数第n+1个结点处。这样理解:删除倒数第n个结点,需要找其前驱结点,即倒数第n+1个节点,其标号为N-n,从头结点移动到该节点需要N-n-1次,而从标号为n+1的结点移动到尾结点也是N-n-1次,所以首先让l1移动到第n+1个节点处,然后l1和l2同时向后移动。

class Solution {
    
    
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
    
    
        ListNode *l1 = head;
        while (n > 0){
    
    
            n--;
            l1 = l1->next;
        } 
        if (l1 == nullptr){
    
    
            ListNode *del = head;
            head = head->next;
            delete del;
        }
        else{
    
    
            ListNode *l2 = head;
            while (l1->next != nullptr){
    
    
                l1 = l1->next;
                l2 = l2->next;
            } 
            ListNode *del = l2->next;
            l2->next = del->next;
            delete del;
        }
        return head;     
    }
};
6. 两两交换链表中的节点(NO.24)

题目描述:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。注意不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
解题思路:使用三个指针preNode,pNode,nexNode,交换pNode和nexNode所指结点,然后三个指针都后移两位进行下一次的交换。

class Solution {
    
    
public:
    ListNode* swapPairs(ListNode* head) {
    
    
        ListNode *newNode = (ListNode*)malloc(sizeof(ListNode));
        newNode->next = head;
        ListNode *preNode = newNode, *pNode = head;
        while (pNode != nullptr && pNode->next != nullptr){
    
    
            ListNode *nexNode = pNode->next;

            preNode->next = nexNode;
            pNode->next = nexNode->next;
            nexNode->next = pNode;

            preNode = pNode;//后移,注意pNode和nexNode换了位置,所以更新preNode=pNode
            pNode = pNode->next;
        }
        return newNode->next;
    }
};
7. 两个链表数字求和(NO.445)

题目描述:给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。可以假设除了数字 0 之外,这两个数字都不会以零开头。
解题思路:将两个链表中的数据放在两个栈中,将出栈的两个元素相加,并判断是否需要进位。

class Solution {
    
    
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
    
    
        ListNode *head1 = l1;
        stack<int> stack1;
        while (head1 != nullptr){
    
    
            stack1.push(head1->val);
            head1 = head1->next;
        }
        ListNode *head2 = l2;
        stack<int> stack2;
        while (head2 != nullptr){
    
    
            stack2.push(head2->val);
            head2 = head2->next;
        }
        ListNode *res = nullptr;
        int flag = 0;
        while (!stack1.empty() || !stack2.empty() || flag != 0){
    
    
            int a = 0, b = 0;
            if (!stack1.empty()){
    
    
                a = stack1.top();
                stack1.pop();
            }
            if (!stack2.empty()){
    
    
                b = stack2.top();
                stack2.pop();
            }
            int num = a + b + flag;
            if (num >= 10){
    
    
                flag = 1;
                num -= 10;
            }
            else
                flag = 0;
            ListNode *s = new ListNode;
            s->val = num;
            s->next = res;
            res = s;
        }
        return res;
    }
};
8. 分割链表(NO.725)

题目描述:给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。返回一个符合上述规则的链表的列表。
输入: root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解题思路:首先遍历链表得到链表的长度len,根据长度和分割的份数确定每份的长度。通过len/k获得平均长度,len%k得到较长子表的份数。接下来就是根据每个子表的长度分割链表,要将每一个子表最后一个结点的next指针置为nullptr来断开原链表中的连接。

class Solution {
    
    
public:
    vector<ListNode*> splitListToParts(ListNode* root, int k) {
    
    
        int len = 0;
        ListNode *begin = root;
        while (begin != nullptr){
    
    
            len++;
            begin = begin->next;
        }
        int subLen = len / k;
        int n1 = len % k;
        begin = root;
        ListNode* pre = nullptr;
        vector<ListNode*> result(k,nullptr);
        for (int i = 0; begin != nullptr && i < k; i++){
    
    
            result[i] = begin;
            int curLen = i < n1 ? subLen + 1 : subLen;
            for (int j = 0; j < curLen; j++){
    
    
                pre = begin;
                begin = begin->next;
            }
            pre->next = nullptr;
        }

        return result;
    }
};
9. 链表元素按节点编号的奇偶性聚集(NO.328)

题目描述:给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
解题思路:结点1作为奇数链的头,结点2作为偶数链的头,使用odd和even两个指针,从第3个点开始遍历,依次轮流附在奇、偶链的后面。遍历完后,奇数链的尾连向偶链的头,偶链的尾为空, 返回奇数链的头。注意循环结束的判断条件。

class Solution {
    
    
public:
    ListNode* oddEvenList(ListNode* head) {
    
    
        if (head == nullptr)
            return head;       
        ListNode *Odd = head;
        ListNode *EvenHead = head->next, *Even = EvenHead;
        while (Even != nullptr && Even->next != nullptr){
    
    
            Odd->next = Even->next;
            Odd = Even->next;
            Even->next = Odd->next;
            Even = Odd->next;
        }
        Odd->next = EvenHead;
        return head;
    }
};
10. 回文链表(NO.234)

题目描述:请判断一个链表是否为回文链表。
解题思路:使用快慢两个指针,快指针每次移动两个,满指针每次移动一个,当快指针到链表末尾时,满指针正好到达链表中间。将前一部分满指针遍历的节点值添加到一个栈中,并与链表后半部分元素比较。注意这里考虑链表节点总数是奇数还是偶数,若为奇数,中间节点值不用进栈,若为偶数,中间节点值进栈。

class Solution {
    
    
public:
    bool isPalindrome(ListNode* head) {
    
    
        if (head == nullptr)
            return true;
        ListNode* fast = head;
        ListNode* slow = head;
        stack<int> st;
        while (fast->next != nullptr && fast->next->next != nullptr){
    
    
            st.push(slow->val);
            slow = slow->next;
            fast = fast->next->next;
        }
        if (fast->next != nullptr)//偶数个节点,将中间元素加入
            st.push(slow->val);
        slow = slow->next;//后移一位,指向链表后半部分的第一个节点
        while (slow != nullptr){
    
    
            if (slow->val != st.top())
                return false;
            slow = slow->next;
            st.pop();
        }
        return true;
    }
};

猜你喜欢

转载自blog.csdn.net/qq_42820853/article/details/107331151