JHHK

欢迎来到我的个人网站
行者常至 为者常成

【题目】2、链表

目录

链表相关算法建议

一、解题建议

一定要多画图

二、解题技巧

虚拟头结点
快慢指针
多指针
……

三、常用代码要非常熟练

链表节点的插入、删除
反转(翻转)一个链表
快慢指针求中心节点
计算链表的长度
……

203.移除链表元素

题目描述

_203_移除链表元素 - https://leetcode-cn.com/problems/remove-linked-list-elements/     

删除链表中等于给定值 val 的所有节点。     

示例:     

输入: 1->2->6->3->4->5->6, val = 6     
输出: 1->2->3->4->5     

题目实现

时间复杂度O(n),空间复杂度O(1)

class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        //虚拟头结点
        ListNode * dummyHead  = new ListNode(0,head);
        
        //扫描节点
        ListNode * curNode = dummyHead;

        while (curNode->next) {
            if (curNode->next->val == val) {
                curNode->next = curNode->next->next;
            }else{
                curNode = curNode->next;
            }
        }

        return dummyHead->next;
    }
};

237.删除链表中的节点

题目描述

237. 删除链表中的节点        

请编写一个函数,使其可以删除某个链表中给定的(非末尾)节点。传入函数的唯一参数为 要被删除的节点 。        

现有一个链表 -- head = [4,5,1,9],它可以表示为:        

示例 1:        

输入:head = [4,5,1,9], node = 5        
输出:[4,1,9]        
解释:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 4 -> 1 -> 9.        
示例 2:        

输入:head = [4,5,1,9], node = 1        
输出:[4,5,9]        
解释:给定你链表中值为 1 的第三个节点,那么在调用了你的函数之后,该链表应变为 4 -> 5 -> 9.        
  

提示:        
链表至少包含两个节点。        
链表中所有节点的值都是唯一的。        
给定的节点为非末尾节点并且一定是链表中的一个有效节点。        
不要从你的函数中返回任何结果。        

题目实现

时间复杂度O(1),空间复杂度O(1)

class Solution {
public:
    void deleteNode(ListNode* node) {
        node->val  = node->next->val;
        node->next = node->next->next;
    }
};

206.反转一个单链表

题目描述

反转一个单链表。     

示例:     

输入: 1->2->3->4->5->NULL     
输出: 5->4->3->2->1->NULL     
进阶:     
你可以迭代或递归地反转链表。你能否用两种方法解决这道题?     

来源:力扣(LeetCode)     
链接:https://leetcode-cn.com/problems/reverse-linked-list     
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。     

一、递归方式

时间复杂度O(n),空间复杂度O(n)

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

二、迭代方式

时间复杂度O(n),空间复杂度O(1)

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode * newHead = nullptr;
        while (head) {
            ListNode* temp = head->next;
            head->next = newHead;
            newHead = head;
            head = temp;
        }
        
        return newHead; 
    }
};

2.两数相加

题目描述

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。       
    
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。       
    
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。       
    
示例:       
    
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)       
输出:7 -> 0 -> 8       
原因:342 + 465 = 807       
    
来源:力扣(LeetCode)       
链接:https://leetcode-cn.com/problems/add-two-numbers       
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。       

题目实现

class Solution {
    
    func addTwoNumbers(_ l1: ListNode?, _ l2: ListNode?) -> ListNode? {
        
        
        var head1 = l1
        var head2 = l2
        
        var param = 0
        var preHead = ListNode()
        var currentNode = preHead
        
        while head1 != nil || head2 != nil {
            
            var val1 = 0
            if  head1 != nil {
                val1 = head1!.val
                head1 = head1!.next
            }
            
            var val2 = 0
            if  head2 != nil {
                val2 = head2!.val
                head2 = head2!.next
            }
            
            
            
            let sum = val1 + val2 + param
            let node = ListNode()
            
            if sum >= 10 {
                node.val = sum - 10
                param = 1
            } else {
                node.val = sum
                param = 0
            }
            currentNode.next = node
            currentNode = node
        }

        if param > 0 { currentNode.next = ListNode(param) }
        
        return preHead.next
    }
    
}

//时间复杂度O(n),空间复杂度O(n) (n l1与l2d中较长的链表长度)
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        
        int carray = 0;//进位默认为0
        ListNode * dummyHead = new ListNode(0);//虚拟头结点
        ListNode * curNode = dummyHead;//扫描节点
        
        while (l1 || l2) {
            int v1 = 0;
            if (l1) {
                v1 = l1->val;
                l1 = l1->next;
            }
            
            int v2 = 0;
            if (l2) {
                v2 = l2->val;
                l2 = l2->next;
            }
                        
            int sum = v1 + v2 + carray;
            curNode->next = new ListNode(sum%10);
            carray = sum / 10;
            
            curNode = curNode->next;
        }
                
        if (carray >0) {
            curNode->next = new ListNode(carray);
        }
        
        return dummyHead->next;
    }
};

160.相交链表

题目描述

编写一个程序,找到两个单链表相交的起始节点。       

如下面的两个链表:       

注意:       

如果两个链表没有交点,返回 null.       
在返回结果后,两个链表仍须保持原有的结构。       
可假定整个链表结构中没有循环。       
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存       

来源:力扣(LeetCode)       
链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists       
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。       

题目实现

//时间复杂度O(1),空间复杂度O(n+m) (n:headA的长度,m:headB的长度)
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        
        ListNode * trailA = headA;
        ListNode * trailB = headB;
        
        while (trailA != trailB) {
            trailA = (trailA == nullptr) ? headB : trailA->next;
            trailB = (trailB == nullptr) ? headA : trailB->next;
        }
        
        return trailA;
    }
};

86. 分隔链表

题目描述


编写一个程序,找到两个单链表相交的起始节点。      

如下面的两个链表:      

注意:      

如果两个链表没有交点,返回 null.      
在返回结果后,两个链表仍须保持原有的结构。      
可假定整个链表结构中没有循环。      
程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存      

来源:力扣(LeetCode)      
链接:https://leetcode-cn.com/problems/intersection-of-two-linked-lists      
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。       

题目实现

//时间复杂度O(1),空间复杂度O(n) (n:head链表长度)

class Solution {
public:
    ListNode* partition(ListNode* head, int x) {
        if (head == nullptr || head->next == nullptr) return head;
        
        ListNode * headA = new ListNode(0);
        ListNode * trailA = headA;
        ListNode * headB = new ListNode(0);
        ListNode * trailB = headB;
        ListNode * trailNode = head;
        
        while (trailNode) {
            if (trailNode->val < x) {
                trailA->next = trailNode;
                trailA = trailA->next;
            }else{
                trailB->next = trailNode;
                trailB = trailB->next;
            }
            trailNode = trailNode->next;
        }
        
        trailB->next = nullptr;
        trailA->next = headB->next;
        return headA->next;
    }
};

234.回文链表

题目描述

请判断一个链表是否为回文链表。        

示例 1:        

输入: 1->2        
输出: false        
示例 2:        

输入: 1->2->2->1        
输出: true        
进阶:        
你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?        

来源:力扣(LeetCode)        
链接:https://leetcode-cn.com/problems/palindrome-linked-list        
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。        

题目实现

//时间复杂度O(1),空间复杂度O(n) (n:head链表长度)
class Solution {
public:
    bool isPalindrome(ListNode* head) {

        if (head == nullptr || head->next == nullptr) return true;
        if (head->next->next == nullptr) return head->val == head->next->val;
        
        //获取中间节点
        ListNode * mid = midNode(head);
        
        //反转右半部分
        ListNode * rHead = reverseLinkList(mid->next);
        
        //不破坏原有链表结构
        ListNode * lHead = head;
        
        //
        ListNode * oldRHead = rHead;
        
        bool result = true;
        while (rHead) {
            if (lHead->val != rHead->val) {
                result = false;
                break;
            }
            
            lHead = lHead->next;
            rHead = rHead->next;
        }
        
        //不破坏原有的链表结构,将oldRHead再反转回来
        reverseLinkList(oldRHead);
        
        return result;
    }
    
    /// 获取链表的中间节点(快慢指针)
    /// @param head 链表
    ListNode * midNode(ListNode* head){
        if(head == nullptr) return nullptr;
        ListNode * slowNode = head;
        ListNode * fastNode = head->next;
        
        while (fastNode && fastNode->next) {
            fastNode = fastNode->next->next;
            slowNode = slowNode->next;
        }
        return slowNode;
    }
    
    /// 反转一个链表,返回新的链表
    /// @param head 链表
    ListNode* reverseLinkList(ListNode* head){
        ListNode * newHead = nullptr;
        while (head) {
            ListNode * temp = head->next;
            head->next = newHead;
            newHead = head;
            head = temp;
        }
        return newHead;
    }
};

141.环形链表

题目描述

给定一个链表,判断链表中是否有环。    

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

如果链表中存在环,则返回 true 。 否则,返回 false 。    

  

进阶:    

你能用 O(1)(即,常量)内存解决此问题吗    

来源:力扣(LeetCode)    
链接:https://leetcode-cn.com/problems/linked-list-cycle    
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。    

题目实现

class Solution {
public:
    bool hasCycle(ListNode *head) {
         //思路:快慢指针
    
        //空链表或链表只有一个节点时,返回false
        if (head == nullptr || head->next == nullptr) return false;
        
        //避免起始时两个指针指向相同
        ListNode* slow = head;          //慢指针指向首个节点
        ListNode* fast = head->next;    //快指针指向第二个节点
        
        while (fast && fast->next) {
            slow = slow->next;
            fast = fast->next->next;
            if (slow == fast) return true;
        }
        
        return false;
    }
};

21.合并两个有序链表

题目描述


将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。     

示例:     

输入:1->2->4, 1->3->4     
输出:1->1->2->3->4->4     

来源:力扣(LeetCode)     
链接:https://leetcode-cn.com/problems/merge-two-sorted-lists     
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。     

迭代方式实现

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
                
        //创建一个虚拟头结点
        ListNode * dummyHead = new ListNode(0);
        ListNode * current = dummyHead;
        
        while (l1 && l2) {
            if (l1->val < l2->val) {
                current->next = l1;
                l1 = l1->next;
            }else{
                current->next = l2;
                l2 = l2->next;
            }
            current = current->next;
        }
        
        if (l1) {
            current->next = l1;
        }else{
            current->next = l2;
        }
        
        return dummyHead->next;
    }
};

递归方式实现

//递归方式实现
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
                
        if (l1 == nullptr) return l2;
        if (l2 == nullptr) return l1;

        if (l1->val < l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        }else{
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
};

23.合并K个升序链表

题目描述

给你一个链表数组,每个链表都已经按升序排列。       

请你将所有链表合并到一个升序链表中,返回合并后的链表       
 
示例 1:       

输入:lists = [[1,4,5],[1,3,4],[2,6]]       
输出:[1,1,2,3,4,4,5,6]       
解释:链表数组如下:       
[       
   1->4->5,
   1->3->4,
   2->6
]       
将它们合并到一个有序链表中得到。       
1->1->2->3->4->4->5->6       

来源:力扣(LeetCode)       
链接:https://leetcode-cn.com/problems/merge-k-sorted-lists       
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。       

实现方式一:两两合并

class Solution {
    ListNode * dummyHead = new ListNode(0);
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        //创建一个虚拟头结点
        dummyHead->next = nullptr;
        ListNode * current = dummyHead;
        while (l1 && l2) {
            if (l1->val < l2->val) {
                current->next = l1;
                l1 = l1->next;
            }else{
                current->next = l2;
                l2 = l2->next;
            }
            current = current->next;
        }
        
        if (l1) {
            current->next = l1;
        }else{
            current->next = l2;
        }
        
        return dummyHead->next;
    }
    
    

    ListNode* mergeKLists(vector<ListNode*>& lists) {

        for (int i = 1; i<lists.size(); i++) {
            lists[0] = mergeTwoLists(lists[0], lists[i]);
        }
        return  lists[0];
    }
};

实现方式二:一轮一轮进行,每轮挑出最小的

class Solution {
public:

    ListNode* mergeKLists(vector<ListNode*>& lists) {
        
        ListNode * head = new ListNode(0);
        ListNode * current = head;
        
        while (true) {
            
            //找出最小的节点
            int minIndex = -1;
            for (int i = 0; i<lists.size(); i++) {
                if(lists[i] == nullptr) continue;
                if (minIndex == -1 || lists[i]->val < lists[minIndex]->val) {
                    minIndex = i;
                }
            }
            
            //链表数组已经全部为nullptr
            if(minIndex == -1) break;
            
            current->next = lists[minIndex];
            current = current->next;
            
            //更新链表数组的值
            lists[minIndex] = lists[minIndex]->next;
        }
        
        return head->next;
    }
};

行者常至,为者常成!





R
Valine - A simple comment system based on Leancloud.