Leetcode_ 入门_链表

1、找出两个链表的交点(160、Easy)

1)题目要求

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

如下面的两个链表:

在这里插入图片描述

在节点 c1 开始相交。

示例 1:

在这里插入图片描述

输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。

示例 2:

在这里插入图片描述

输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个链表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。

示例 3:
在这里插入图片描述

输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。

注意:

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

2)我的解法

c++

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
    {
        ListNode*current1=headA;
        int tag=0;
        while(current1!=NULL)
        {
            ListNode*current2=headB;
            while(current2!=NULL)
            {
                if(current1==current2)return current1;
                else current2=current2->next;
            }
            current1=current1->next;
        }
        return NULL;
    }
};

3)其他解法

(1) 哈希表法
遍历链表 A 并将每个结点的地址/引用存储在哈希表中。然后检查链表 B 中的每一个结点 bi ,是否在哈希表中。若在,则 bi为相交结点。

class Solution {
public:

    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {

        std::unordered_set<ListNode*> set;

        ListNode* cur_a = headA;
        
        while (cur_a)
        {
            set.insert(cur_a);
            cur_a = cur_a->next;
        }

        ListNode* cur_b = headB;

        while (cur_b)
        {
            if(set.find(cur_b) != set.end()) //找到了
            {
                return cur_b;
            }
            cur_b = cur_b->next;
        }

        return nullptr;
    }
};

时间复杂度 : O(m+n)。
空间复杂度 : O(m)或 O(n)。

作者:eric-345
链接:link
来源:力扣(LeetCode)

(2)双指针法

A的指针遍历完A 接着从headB开始遍历
B的指针遍历完B 接着从headA开始遍历
两个指针都最多走m + n + 1步。
当两个指针同时为空时,表示不相交;当两个都非空且相等时,表示相交

时间复杂度O(m + n) 空间复杂度O(1)

动画展示:
link

class Solution {
public:
    
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {

        if(headA == nullptr || headB == nullptr) 
            return nullptr;

        ListNode* cur_a = headA;
        ListNode* cur_b = headB;

        while(cur_a != cur_b)
        {
            cur_a = (cur_a == nullptr ? headB : cur_a->next);
            cur_b = (cur_b == nullptr ? headA : cur_b->next);
        }

        return cur_a;
    }
};

作者:eric-345
链接:link
来源:力扣(LeetCode)

4)自己的优化代码

c++

 ListNode *getIntersectionNode(ListNode *headA, ListNode *headB)
    {
        ListNode*cur1=headA;
        ListNode*cur2=headB;
        if(!headA||!headB)return NULL;
        while(cur1!=cur2)
        {
            if(cur1)cur1=cur1->next;
            else cur1=headB;
            if(cur2)cur2=cur2->next;
            else cur2=headA;
        }
        return cur1;//若不相交,cur1就是null
    }

5)学到的东西

哈希表知识: std::unordered_set<ListNode*> set;
双指针
思想:两个指针走的路程相等,a+c+b=b+c+a

2、链表反转(206、Easy)

1)题目要求

反转一个单链表。

示例:

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

2)我的解法

c++
(1)迭代

class Solution {
public:
    ListNode* reverseList(ListNode* head)
    {
        if(head==NULL)return head;
        ListNode*current=head;
        while(current!=NULL&&current->next!=NULL)
        {
            current=current->next;
        }
        ListNode*head1=current;
        while(current!=head)
        {
            ListNode*p=head;
            while(p!=NULL&&p->next!=NULL)
            {
                if(p->next==current){current->next=p;current=current->next;break;}
                else p=p->next;
            }
        }
        current->next=NULL;
        return head1;

    }
};

时间复杂度:O(n^2)
空间复杂度:O(1)
(2)递归

class Solution {
public:
    void findpre(ListNode* current,ListNode* head)
    {
        if(current==head){current->next=NULL;return;}
        ListNode*p=head;
        while(p->next!=NULL&&p->next!=current)
        {
            p=p->next;
        }
        current->next=p;
        findpre(p,head);
        
    }
    ListNode* reverseList(ListNode* head)
    {
        if(head==NULL)return head;
        ListNode*current=head;
        while(current!=NULL&&current->next!=NULL)
        {
            current=current->next;
        }
        ListNode*head1=current;
        findpre(head1,head);
        return head1;
    }
};

时间复杂度:O(n^2)
空间复杂度:O(1)

3)其他解法

(1)神奇解法
C++

// Time 8ms, 99%
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *p;
        for(p=NULL; head; swap(head,p))
            swap(p,head->next);
        return p;
    }
};

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

作者:Joy
来源:Leetcode评论区

(2)双指针迭代

java

public ListNode reverseList(ListNode head) {
    ListNode prev = null;
    ListNode curr = head;
    while (curr != null) {
        ListNode nextTemp = curr.next;
        curr.next = prev;
        prev = curr;
        curr = nextTemp;
    }
    return prev;
}

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

作者:LeetCode
链接:link
来源:力扣(LeetCode)

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

在这里插入图片描述

public ListNode reverseList(ListNode head) {
    if (head == null || head.next == null) return head;
    ListNode p = reverseList(head.next);
    head.next.next = head;
    head.next = null;
    return p;
}

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

作者:LeetCode
链接:link
来源:力扣(LeetCode)

(4)另一种双指针
java
原链表的头结点就是反转之后链表的尾结点,使用 head 标记 .
定义指针 cur,初始化为 head.
每次都让 head 下一个结点的 next指向 cur ,实现一次局部反转
局部反转完成之后,cur和 head的 next 指针同时 往前移动一个位置
循环上述过程,直至 cur到达链表的最后一个结点 .

在这里插入图片描述

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (head == NULL) { return NULL; }
        ListNode* cur = head;
        while (head->next != NULL) {
            ListNode* t = head->next->next;
            head->next->next = cur;
            cur = head->next;
            head->next = t;
        }
        return cur;
    }
};


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

作者:huwt
链接: link
来源:力扣(LeetCode)

4)自己的优化代码

c++

ListNode* reverseList(ListNode* head)
    {
        if(head==NULL)return head;
        ListNode*current=head;
        ListNode*pre=NULL;//重点
        while(current!=NULL)//这里用current->next的话,while后面还得再加一行,代码会比较啰嗦
        {
            ListNode*NextItem=current->next;
            current->next=pre;
            pre=current;
            current=NextItem;
        }
        return pre;
    }

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

5)学到的东西

递归思想
双指针算法
局部一点点变换
swap函数的应用

3、删除链表的倒数第 n 个节点(19、Medium)

1)题目要求

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:

给定一个链表: 1->2->3->4->5, 和 n = 2.

当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:

给定的 n 保证是有效的。

进阶:

你能尝试使用一趟扫描实现吗?

2)我的解法

c++
(1)暴力

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

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

(2)反转链表
先反转链表,再删除元素,再反转链表。

  ListNode* removeNthFromEnd(ListNode* head, int n)
    {
       if(head==NULL)return head;
        ListNode*current=head;
        ListNode*pre=NULL;//重点
        while(current!=NULL)
        {
            ListNode*NextItem=current->next;
            current->next=pre;
            pre=current;
            current=NextItem;
        }
        ListNode*head1=pre;
        if(n-1==0)head1=head1->next;
        else{
        int i=1;
        current=pre;
        while(i<n-1)
        {
            current=current->next;
            i++;
        }
        current->next=current->next->next;
        }
        current=head1;
        pre=NULL;//重点
        while(current!=NULL)
        {
            ListNode*NextItem=current->next;
            current->next=pre;
            pre=current;
            current=NextItem;
        }
        return pre;
    }

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

3)其他解法

(1)双指针
上述算法可以优化为只使用一次遍历。我们可以使用两个指针而不是一个指针。第一个指针从列表的开头向前移动 n+1步,而第二个指针将从列表的开头出发。现在,这两个指针被 个结点分开。我们通过同时移动两个指针向前来保持这个恒定的间隔,直到第一个指针到达最后一个结点。此时第二个指针将指向从最后一个结点数起的第 n 个结点。我们重新链接第二个指针所引用的结点的 next 指针指向该结点的下下个结点。
在这里插入图片描述

java

public ListNode removeNthFromEnd(ListNode head, int n) {
    ListNode dummy = new ListNode(0);
    dummy.next = head;
    ListNode first = dummy;
    ListNode second = dummy;
    // Advances first pointer so that the gap between first and second is n nodes apart
    for (int i = 1; i <= n + 1; i++) {
        first = first.next;
    }
    // Move first to the end, maintaining the gap
    while (first != null) {
        first = first.next;
        second = second.next;
    }
    second.next = second.next.next;
    return dummy.next;
}

作者:LeetCode
链接:link
来源:力扣(LeetCode)

4)自己的优化代码

c++

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n)
    {
       if(head==NULL)return head;
        ListNode*cur1=head;
        ListNode*cur2=head;
        int i=0;
        while(i<n&&cur2)
        {
            cur2=cur2->next;
            i++;
        }
        while(cur2&&cur2->next)
        {
            cur1=cur1->next;
            cur2=cur2->next;
        }
        if(cur2)cur1->next=cur1->next->next;
        else head=cur1->next;
        return head;
    }
};

5)学到的东西

双指针算法

思想:保持一定的间隔,当后一个指针到最后一个时,前一个便会刚好到要删结点之前那个结点

4、归并两个有序的链表(21、Easy)

1)题目要求

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

示例:

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

2)我的解法

c++

 ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) 
    {
        if(!l1)return l2;
        if(!l2)return l1;
        ListNode*cur1=nullptr;
        ListNode*cur2=nullptr;
        ListNode*p=nullptr;
        if(l1->val<=l2->val)
        {
            cur1=l1;
            p=l1;
            cur2=l2;
        }
        else
        {
            cur1=l2;
            p=l2;
            cur2=l1;
        }
        ListNode*temp=nullptr;
        while(cur1&&cur2)
        {
            if(cur2->val>=cur1->val)
            {
                if(!cur1->next||cur2->val<=cur1->next->val)
                {
                    temp=cur2->next;
                    cur2->next=cur1->next;
                    cur1->next=cur2;
                    cur1=cur1->next;
                    cur2=temp;
                }
                else{cur1=cur1->next;}
            }
            else{cur2=cur2->next;}
        }
        return p;
    }

3)其他解法

方法一:递归
思路

我们可以如下递归地定义两个链表里的 merge 操作(忽略边界情况,比如空链表等):

\left{ \begin{array}{ll} list1[0] + merge(list1[1:], list2) & list1[0] < list2[0] \ list2[0] + merge(list1, list2[1:]) & otherwise \end{array} \right.
{
list1[0]+merge(list1[1:],list2)
list2[0]+merge(list1,list2[1:])

list1[0]<list2[0]
otherwise

也就是说,两个链表头部值较小的一个节点与剩下元素的 merge 操作结果合并。

算法

我们直接将以上递归过程建模,同时需要考虑边界情况。

如果 l1 或者 l2 一开始就是空链表 ,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断 l1 和 l2 哪一个链表的头节点的值更小,然后递归地决定下一个添加到结果里的节点。如果两个链表有一个为空,递归结束。

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;
        }
    }
};

作者:LeetCode-Solution
链接: link
来源:力扣(LeetCode)

方法二:迭代
思路

我们可以用迭代的方法来实现上述算法。当 l1 和 l2 都不是空链表时,判断 l1 和 l2 哪一个链表的头节点的值更小,将较小值的节点添加到结果里,当一个节点被添加到结果里之后,将对应链表中的节点向后移一位。

算法

首先,我们设定一个哨兵节点 prehead ,这可以在最后让我们比较容易地返回合并后的链表。我们维护一个 prev 指针,我们需要做的是调整它的 next 指针。然后,我们重复以下过程,直到 l1 或者 l2 指向了 null :如果 l1 当前节点的值小于等于 l2 ,我们就把 l1 当前的节点接在 prev 节点的后面同时将 l1 指针往后移一位。否则,我们对 l2 做同样的操作。不管我们将哪一个元素接在了后面,我们都需要把 prev 向后移一位。

在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。这意味着我们只需要简单地将非空链表接在合并链表的后面,并返回合并链表即可。

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* preHead = new ListNode(-1);

        ListNode* prev = preHead;
        while (l1 != nullptr && l2 != nullptr) {
            if (l1->val < l2->val) {
                prev->next = l1;
                l1 = l1->next;
            } else {
                prev->next = l2;
                l2 = l2->next;
            }
            prev = prev->next;
        }

        // 合并后 l1 和 l2 最多只有一个还未被合并完,我们直接将链表末尾指向未合并完的链表即可
        prev->next = l1 == nullptr ? l2 : l1;

        return preHead->next;
    }
};

作者:LeetCode-Solution
链接: link
来源:力扣(LeetCode)

4)自己的优化代码

c++

 ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) 
    {
        ListNode*p=new ListNode(-1);
        ListNode*head=p;
        while(l1||l2)
        {
            if(!l2||(l1&&l2&&l1->val<=l2->val)){p->next=l1;l1=l1->next;}
            else if(!l1||(l1&&l2&&l1->val>l2->val)){p->next=l2;l2=l2->next;}
            p=p->next;
        }
        return head->next;
    }

5)学到的东西

思想:链表的结点可以一个个连接,这样比插入结点快。

5、从有序链表中删除重复节点(83、Easy)

1)题目要求

给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。

示例 1:

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

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

2)我的解法

c++

    ListNode* deleteDuplicates(ListNode* head) 
    {
        ListNode*cur=head;
        while(cur&&cur->next)
        {
            if(cur->val==cur->next->val)
            {
                cur->next=cur->next->next;
            }
            else cur=cur->next;
        }
        return head;
    }

3)其他解法

递归套路解决链表问题:

找终止条件:当head指向链表只剩一个元素的时候,自然是不可能重复的,因此return
想想应该返回什么值:应该返回的自然是已经去重的链表的头节点
每一步要做什么:宏观上考虑,此时head.next已经指向一个去重的链表了,而根据第二步,我应该返回一个去重的链表的头节点。因此这一步应该做的是判断当前的head和head.next是否相等,如果相等则说明重了,返回head.next,否则返回head

java

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if(head == null || head.next == null){
            return head;
        }
        head.next = deleteDuplicates(head.next);
        if(head.val == head.next.val) head = head.next;
        return head;
    }
}

作者:mata川
来源:力扣(LeetCode)评论区

4)自己的优化代码

c++

 ListNode* deleteDuplicates(ListNode* head) 
    {
        ListNode*cur=head;
        while(cur&&cur->next)
        {
            if(cur->val==cur->next->val)
            {
                cur->next=cur->next->next;
            }
            else cur=cur->next;
        }
        return head;
    }

5)学到的东西

链表的递归思想

6、回文链表(234、Easy)

1)题目要求

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

示例 1:

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

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

2)我的解法

c++

 bool isPalindrome(ListNode* head) 
    {
        ListNode*cur=head;
        stack<int> s;
        if(!head||!head->next)return true;
        s.push(cur->val);cur=cur->next;
        while(cur)
        {
            if(s.empty())s.push(cur->val);
            else if(cur->val==s.top())s.pop();
            else if(cur->next&&s.top()==cur->next->val){s.pop();cur=cur->next->next;continue;}
            else s.push(cur->val);
            cur=cur->next;
        }
        if(s.empty())return true;
        else return false;
    }

3)其他解法

(1)将值复制到数组中后用双指针法

算法:

我们可以分为两个步骤:

复制链表值到数组列表中。
使用双指针法判断是否为回文。
第一步,我们需要遍历链表将值复制到数组列表中。我们用 currentNode 指向当前节点。每次迭代向数组添加 currentNode.val,并更新 currentNode = currentNode.next,当 currentNode = null 则停止循环。

class Solution {
    public boolean isPalindrome(ListNode head) {
        List<Integer> vals = new ArrayList<>();

        // Convert LinkedList into ArrayList.
        ListNode currentNode = head;
        while (currentNode != null) {
            vals.add(currentNode.val);
            currentNode = currentNode.next;
        }

        // Use two-pointer technique to check for palindrome.
        int front = 0;
        int back = vals.size() - 1;
        while (front < back) {
            // Note that we must use ! .equals instead of !=
            // because we are comparing Integer, not int.
            if (!vals.get(front).equals(vals.get(back))) {
                return false;
            }
            front++;
            back--;
        }
        return true;
    }
}


作者:LeetCode
链接: link
来源:力扣(LeetCode)

(2)用快慢指针遍历的同时翻转前半部分,然后与后半部分比较即可。
我们可以计算链表节点的数量,然后遍历链表找到前半部分的尾节点。

或者可以使用快慢指针在一次遍历中找到:慢指针一次走一步,快指针一次走两步,快慢指针同时出发。当快指针移动到链表的末尾时,慢指针到链表的中间。通过慢指针将链表分为两部分。

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(!head || !head->next)
            return 1;
        ListNode *fast = head, *slow = head;
        ListNode *p, *pre = NULL;
        while(fast && fast->next){
            p = slow;
            slow = slow->next;    //快慢遍历
            fast = fast->next->next;

            p->next = pre;  //翻转
            pre = p;
        }
        if(fast)  //奇数个节点时跳过中间节点
            slow = slow->next;

        while(p){       //前半部分和后半部分比较
            if(p->val != slow->val)
                return 0;
            p = p->next;
            slow = slow->next;
        }
        return 1;
    }
};

作者:zed-65536
链接:link
来源:力扣(LeetCode)

(3)将所有节点值入栈,然后一一出栈并比较

class Solution {
public:
    bool isPalindrome(ListNode* head) {
        stack<int> s;
        ListNode *p = head;
        while(p){
            s.push(p->val);
            p = p->next;
        }
        p = head;
        while(p){
            if(p->val != s.top()){
                return 0;
            }
            s.pop();
            p = p->next;
        }
        return 1;
    }
};

作者:zed-65536
链接:link
来源:力扣(LeetCode)

4)自己的优化代码

c++

 bool isPalindrome(ListNode* head) 
    {
        ListNode*cur=head;
        stack<int> s;
        while(cur){s.push(cur->val);cur=cur->next;}
        cur=head;
        while(cur)
        {
            if(cur->val!=s.top())return false;
            else {s.pop();cur=cur->next;}
        }
        return true;
    }

5)学到的东西

回文字符串特性:从前往后和从后往前是完全相同的

思想:快慢指针,快指针到最后时,慢指针到中间
链表也可以进行分割操作

7、交换链表中的相邻结点(24、Medium)

1)题目要求

给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

示例:

给定 1->2->3->4, 你应该返回 2->1->4->3.

2)我的解法

c++

class Solution {
public:
    ListNode*Swap(ListNode* fir)
    {
        if(!fir||!fir->next)return fir;
        ListNode*sec=fir->next;
        ListNode*temp=sec->next;
        sec->next=fir;
        fir->next=Swap(temp);
        return sec;
    }
    ListNode* swapPairs(ListNode* head) 
    {
        return Swap(head);
    }
};

在这里插入图片描述第一次纯靠自己写出双100%的解法,而且还是一次通过,我觉得值得纪念一下,嘿嘿。

3)其他解法

方法一:递归
这个题目要求我们从第一个节点开始两两交换链表中的节点,且要真正的交换节点。

算法:

从链表的头节点 head 开始递归。
每次递归都负责交换一对节点。由 firstNode 和 secondNode 表示要交换的两个节点。
下一次递归则是传递的是下一对需要交换的节点。若链表中还有节点,则继续递归。
交换了两个节点以后,返回 secondNode,因为它是交换后的新头。
在所有节点交换完成以后,我们返回交换后的头,实际上是原始链表的第二个节点。

java

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {

        // If the list has no node or has only one node left.
        if ((head == null) || (head.next == null)) {
            return head;
        }

        // Nodes to be swapped
        ListNode firstNode = head;
        ListNode secondNode = head.next;

        // Swapping
        firstNode.next  = swapPairs(secondNode.next);
        secondNode.next = firstNode;

        // Now the head is the second node
        return secondNode;
    }
}

时间复杂度:O(N),其中 N指的是链表的节点数量。
空间复杂度:O(N),递归过程使用的堆栈空间。

作者:LeetCode
链接:link
来源:力扣(LeetCode)

(2)方法二:迭代
我们把链表分为两部分,即奇数节点为一部分,偶数节点为一部分,A 指的是交换节点中的前面的节点,B 指的是要交换节点中的后面的节点。在完成它们的交换,我们还得用 prevNode 记录 A 的前驱节点。

算法:

firstNode(即 A) 和 secondNode(即 B) 分别遍历偶数节点和奇数节点,即两步看作一步。
交换两个节点:

firstNode.next = secondNode.next
secondNode.next = firstNode
还需要更新 prevNode.next 指向交换后的头。

prevNode.next = secondNode

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) { val = x; }
 * }
 */
class Solution {
    public ListNode swapPairs(ListNode head) {

        // Dummy node acts as the prevNode for the head node
        // of the list and hence stores pointer to the head node.
        ListNode dummy = new ListNode(-1);
        dummy.next = head;

        ListNode prevNode = dummy;

        while ((head != null) && (head.next != null)) {

            // Nodes to be swapped
            ListNode firstNode = head;
            ListNode secondNode = head.next;

            // Swapping
            prevNode.next = secondNode;
            firstNode.next = secondNode.next;
            secondNode.next = firstNode;

            // Reinitializing the head and prevNode for next swap
            prevNode = firstNode;
            head = firstNode.next; // jump
        }

        // Return the new head node.
        return dummy.next;
    }
}

作者:LeetCode
链接:link
来源:力扣(LeetCode)

4)自己的优化代码

c++

 ListNode* swapPairs(ListNode* head) 
    {
        if(!head||!head->next)return head;
        ListNode*sec=head->next;
        ListNode*temp=sec->next;
        sec->next=head;
        head->next=swapPairs(temp);
        return sec;
    }

5)学到的东西

递归思想
迭代时可利用多个指针,不一定非得2个

8、链表求和(445、Medium)

1)题目要求

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。

你可以假设除了数字 0 之外,这两个数字都不会以零开头。

进阶:

如果输入链表不能修改该如何处理?换句话说,你不能对列表中的节点进行翻转。

2)我的解法

c++
利用栈处理

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
    {
        stack<int> s,s1,s2;
        ListNode*l=new ListNode(0);
        ListNode* head=l;
        ListNode*newnode=NULL;
        while(l1)
        {
            s1.push(l1->val);
            l1=l1->next;
        }
        while(l2)
        {
            s2.push(l2->val);
            l2=l2->next;
        }
        int Jinwei=0;//进位
        int x=0;
        while(!s1.empty()||!s2.empty()||Jinwei)
        {
            x=0;
            if(Jinwei==1){x++;Jinwei=0;}
            if(!s1.empty()&&!s2.empty()){x+=s1.top()+s2.top();s1.pop();s2.pop();}
            else if(s1.empty()&&!s2.empty()){x+=s2.top();s2.pop();}
            else if(!s1.empty()&&s2.empty()){x+=s1.top();s1.pop();}
            if(x>=10){Jinwei=1;x=x-10;}
            s.push(x);
        }
        while(!s.empty())
        {
            newnode=new ListNode(s.top());
            s.pop();
            newnode->next=l->next;
            l->next=newnode;
            l=l->next;
        }
        return head->next;
    }
};

3)其他解法

1、反转链表
java

class Solution {
public:
    ListNode* reverse(ListNode* head)
    {
        if(!head || !head->next)
            return head;
        ListNode* p = head->next, *q = head;
        while(p)
        {
            head->next = p->next;
            p->next = q;
            q = p;
            p = head->next;
        }
        return q;
    }

    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode* n1 = reverse(l1);
        ListNode* n2 = reverse(l2);
        ListNode head, *tail = &head;
        int carry = 0;
        while(n1 || n2 || carry)
        {
            int n1val = n1 ? n1->val : 0;
            int n2val = n2 ? n2->val : 0;
            int sum = n1val + n2val + carry;
            carry = sum/10;
            ListNode* node = new ListNode(sum % 10);
            tail->next = node;
            tail = node;
            if(n1) 
                n1 = n1->next;
             if(n2) 
                n2 = n2->next;           
        }
        ListNode* res = reverse(head.next);
        return res;
    }
};

作者:Sunny_SMILE
链接:link
来源:力扣(LeetCode)

2、栈实现

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        stack<int> s1, s2;
        while(l1)
        {
            s1.push(l1->val);
            l1 = l1->next;
        }
        while(l2)
        {
            s2.push(l2->val);
            l2 = l2->next;
        }
        int carry = 0;//进位
        ListNode* ans = nullptr;
        while(!s1.empty()||!s2.empty()||carry!=0)
        {
            int a = s1.empty() ? 0 : s1.top();
            int b = s2.empty() ? 0 : s2.top();
            if(!s1.empty()) s1.pop();
            if(!s2.empty()) s2.pop();
            int cur = a + b + carry;
            carry = cur/10;
            cur %= 10;
            auto curnode = new ListNode(cur);
            curnode->next = ans;
            ans = curnode;
        }
        return ans;
    }
};

作者:Sunny_SMILE
链接:link
来源:力扣(LeetCode)

3、原地计算(参考@aninvalidname)

class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        int count = 0, temp = 0;
        ListNode *head, *last;
        //计算两链表长度,使l1指向长链,l2指向短链
        for(head = l1; head; head = head->next)
            count++;
        for(head = l2; head; head = head->next)
            count--;
        if(count < 0)                       
            swap(l1,l2);
        //在链首加一个值为0的节点作为初始的last节点,如果最终该节点值仍为0则删除该节点            
        last = head = new ListNode(0);      
        head->next = l1;
        for(int i = abs(count); i != 0; i--){  //将两链数位对齐
            if(l1->val != 9)
                last = l1;
            l1 = l1->next;
        }
        while(l1)
        {
            temp = l1->val + l2->val;
            if(temp > 9){                   //如果发生进位,则更新last到l1之间所有数位的值
                temp -= 10;                 //进位后当前数位最大值为8,故将last指针指向当前数位
                last->val += 1;
                last = last->next;
                while(last != l1)
                {
                    last->val = 0;
                    last = last->next;
                }
            }
            else if(temp != 9)             
                last = l1;
            l1->val = temp;
            l1 = l1->next;
            l2 = l2->next;
        }
        return head->val == 1 ? head : head->next;
    }
};

作者:Sunny_SMILE
链接:link
来源:力扣(LeetCode)

4、递归
(参考link

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    int length(ListNode* l)
    {
        int len = 0;
        while(l)
        {
            len++;
            l = l->next;
        }
        return len;
    }

    void add(ListNode* l1, ListNode* l2, ListNode* p, int m, int n)
    {
        if(!l1)
            return;
        if(m > n)
            add(l1->next, l2, l1, m - 1, n);
        else
        {
            add(l1->next, l2->next, l1, m - 1, n - 1);
            l1->val += l2->val;
        }
        int carry = l1->val/10;
        l1->val = l1->val % 10;
        p->val += carry;
    }

    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        int m = length(l1), n = length(l2);
        ListNode* node = new ListNode(0);
        if(m > n)
        {
            add(l1, l2, node, m, n);
            node->next = l1;
        }
        else 
        {
            add(l2, l1, node, n, m);
            node->next = l2;
        }
        return node->val ? node : node->next;
    }
};


作者:Sunny_SMILE
链接:link
来源:力扣(LeetCode)

4)自己的优化代码

c++

ListNode* addTwoNumbers(ListNode* l1, ListNode* l2)
    {
        stack<int> s1,s2;
        ListNode*l=NULL;
        ListNode* head=l;
        ListNode*newnode=NULL;
        while(l1){s1.push(l1->val);l1=l1->next;}
        while(l2){s2.push(l2->val);l2=l2->next;}
        int Jinwei=0;//进位
        int x=0;
        while(!s1.empty()||!s2.empty()||Jinwei)
        {
            x=0;
            if(Jinwei==1){x++;Jinwei=0;}
            if(!s1.empty()&&!s2.empty()){x+=s1.top()+s2.top();s1.pop();s2.pop();}
            else if(s1.empty()&&!s2.empty()){x+=s2.top();s2.pop();}
            else if(!s1.empty()&&s2.empty()){x+=s1.top();s1.pop();}
            if(x>=10){Jinwei=1;x=x-10;}
            newnode=new ListNode(x);
            newnode->next=l;
            l=newnode;
        }
        return l;
    }

5)学到的东西

学会利用栈
给链表添加元素可以从后往前加

9、分隔链表(725、Medium)

1)题目要求

给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。

每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。

这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。

返回一个符合上述规则的链表的列表。

举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]

示例 1:

输入:
root = [1, 2, 3], k = 5
输出: [[1],[2],[3],[],[]]
解释:
输入输出各部分都应该是链表,而不是数组。
例如, 输入的结点 root 的 val= 1, root.next.val = 2, \root.next.next.val = 3, 且 root.next.next.next = null。
第一个输出 output[0] 是 output[0].val = 1, output[0].next = null。
最后一个元素 output[4] 为 null, 它代表了最后一个部分为空链表。
示例 2:

输入:
root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解释:
输入被分成了几个连续的部分,并且每部分的长度相差不超过1.前面部分的长度大于等于后面部分的长度。

提示:

root 的长度范围: [0, 1000].
输入的每个节点的大小范围:[0, 999].
k 的取值范围: [1, 50].

2)我的解法

c++

    vector<ListNode*> splitListToParts(ListNode* root, int k) 
    {
        vector<ListNode*> vec;
        int size=0;//从0开始数
        ListNode*cur=root;
        ListNode*p=NULL;
        while(cur){cur=cur->next;size++;}//数到cur为NULL为止
        cur=root;int m=0;
        int kk=k;
        for(int j=0;j<kk;j++)
        {
            if(size%k!=0)m=size/k+1;//把多余出来的结点分配到前面
            //size%k所得即余数,即多余出来的结点
            else m=size/k;
            vec.push_back(cur);
            for(int i=1;i<m;i++){cur=cur->next;}
            if(cur){p=cur->next;cur->next=NULL;cur=p;}
            size=size-m;k--;
        }
        return vec;
    }

3)其他解法

方法一、
本题实质:考察如何划分一个整数n
巧妙类比:
(1)先数出有多少块小糖,得n块;
(2)将n块小糖分给k个小朋友,从左往右均分(n/k)块;
(3)将剩下的(n%k)块小糖再从左往右1人1块,直至分完。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    vector<ListNode*> splitListToParts(ListNode* root, int k) {
        //(1)先数有多少块小糖,得n块;——> 计算链表长度length
        ListNode *p = root;
        int length = 0;
        while(p){
            length++;
            p = p->next;
        }

        //(2)将n块小糖分给k个小朋友:从左往右均分(n/k)块;——> 向量vector直接初始化为k个均分值(length/k)
        vector<int> splitLength(k,length/k);

        //(3)将剩下的(n%k)块小糖再从左往右1人1块,直至分完。——> 向量vector从0到length%k增加1
        for(int i = 0; i < length % k; i++)
            splitLength[i] += 1;

        //分割链表
        vector<ListNode*> splitList;

        //虚拟头结点:dummyHead大法好
        ListNode dummyHead(0);
        dummyHead.next = root;
        auto pre = root;

        int i = 0;//记录splitLength中序号,已取出索引值
        int num = 0;//记录子链表的长度
        while(root){
            num++;
            pre = root;
            root = root->next;

            if(num == splitLength[i]){
                i++;//准备记录下一个splitLength中的序号
                num = 0;//恢复子链表长度初始值,准备记录下一个子链表
                pre->next = NULL;
                splitList.push_back(dummyHead.next);//将子链表压入栈中
                pre = root;
                dummyHead.next = root;
            }
        }

        //糖果小于小朋友人数,每个人分零块
        while(i++ < k)
            //将root(上面遍历一遍为空,无需再申请空node)直接压入栈中,然后i值加1,简单干练粗暴
            splitList.push_back(root);

        return splitList;
    }
};

作者:wangxiaole
链接:link
来源:力扣(LeetCode)

方法二
1,遍历链表获取长度 length(这个跑不掉 😓);
2,length 除以 k 得到每段链表的平均长度 aveLength 和 余数 remainder,remainder 的值就是有多少个长度为 (aveLength + 1) 的子链表排在前面。
2.1,举个例子帮助理解一下 11 / 3 = 3 余 2: 一共有3段,每段平均3个节点,但是剩下了2个节点,剩下的2个节点不能丢啊,得全部塞到子链表里面去,怎么塞呢?
2.2,根据题意长的链表排前面,短的链表排后面,所以只有前面的两个子链表一人分担一个多余的节点,如此一来便形成了 4 4 3 的结构。
3,接下来的事儿就比较简单了,按照每个子链表应该的长度[4, 4, 3]去截断给定的链表。

vector<ListNode*> splitListToParts(ListNode* root, int k) {
        ListNode *temp = root;
        int length = 0;
        while (temp) { //遍历链表得到其长度
            temp = temp->next;
            length ++;
        }
        int aveLength = length / k; //每个子链表平均元素的个数
        int remainder = length % k; //余数
        vector<ListNode *> result(k, nullptr);
        ListNode *head = root;
        ListNode *pre = nullptr;
        for (int i = 0; i < k; i ++) {   //数组有k个元素需要遍历k次
            result[i] = head;
            int tempLength = remainder > 0 ? (aveLength + 1) : aveLength;
            for (int j = 0; j < tempLength; j ++) {
                pre = head;
                head = head->next;
            }
            if (pre) pre->next = nullptr; //一个子链表已经生成,断开连接
            if (remainder) remainder --;
        }
        return result; 
    }

作者:CoderYQ
来源:力扣(LeetCode)评论区

4)自己的优化代码

c++

  vector<ListNode*> splitListToParts(ListNode* root, int k) 
    {
        vector<ListNode*> vec(k);//避免使用push_back,可降低时间
        int size=0;
        ListNode*cur=root;
        ListNode*p=NULL;
        while(cur){cur=cur->next;size++;}
        cur=root;int m=0;
        int kk=k;
        for(int j=0;j<kk;j++)
        {
            m=size%k!=0?(size/k+1):size/k;
            vec[j]=cur;
            for(int i=1;i<m;i++){cur=cur->next;}
            if(cur){p=cur->next;cur->next=NULL;cur=p;}
            size=size-m;k--;
        }
        return vec;
    }

5)学到的东西

初始化vector时如果知道数组长度就尽量指定好长度,避免之后重新申请内存空间。

vector< int > vec(10)比直接初始化vector< int > vec 再调用vec.push_back要快

10、链表元素按奇偶聚集(328、Medium)

1)题目要求

给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。

请尝试使用原地算法完成。你的算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。

示例 1:

输入: 1->2->3->4->5->NULL
输出: 1->3->5->2->4->NULL
示例 2:

输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
说明:

应当保持奇数节点和偶数节点的相对顺序。
链表的第一个节点视为奇数节点,第二个节点视为偶数节点,以此类推。

2)我的解法

c++

 ListNode* oddEvenList(ListNode* head) 
    {
        if(!head||!head->next)return head;
        ListNode*ji=head;
        ListNode*ou=head->next;
        ListNode*h=ou;
        while(ji->next)
        {
            if(ji->next->next){ji->next=ji->next->next;ji=ji->next;}
            else ji->next=NULL;
            if(!ou->next)continue;
            if(ou->next->next){ou->next=ou->next->next;ou=ou->next;}
            else ou->next=NULL;
        }
        ji->next=h;
        return head;
    }

3)其他解法

public class Solution {
    public ListNode oddEvenList(ListNode head) {
        if (head == null) return null;
        ListNode odd = head, even = head.next, evenHead = even;
        while (even != null && even.next != null) {
            odd.next = even.next;
            odd = odd.next;
            even.next = odd.next;
            even = even.next;
        }
        odd.next = evenHead;
        return head;
    }
}

作者:LeetCode
链接:link
来源:力扣(LeetCode)

4)自己的优化代码

c++

 ListNode* oddEvenList(ListNode* head) 
    {
        if(!head||!head->next)return head;
        ListNode*ji=head;
        ListNode*ou=head->next;
        ListNode*h=ou;
        while(ji->next)
        {
            if(ji->next->next){ji->next=ji->next->next;ji=ji->next;}
            else ji->next=NULL;
            if(!ou->next)continue;
            if(ou->next->next){ou->next=ou->next->next;ou=ou->next;}
            else ou->next=NULL;
        }
        ji->next=h;
        return head;
    }

5)学到的东西

双指针法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Rust 是一种现代的编程语言,特别适合处理内存安全和线程安全的代码。在 LeetCode 中,链表是经常出现的题目练习类型,Rust 语言也是一种非常适合处理链表的语言。接下来,本文将从 Rust 语言的特点、链表的定义和操作,以及 Rust 在 LeetCode 中链表题目的练习等几个方面进行介绍和讲解。 Rust 语言的特点: Rust 是一种现代化的高性能、系统级、功能强大的编程语言,旨在提高软件的可靠性和安全性。Rust 语言具有如下几个特点: 1. 内存安全性:Rust 语言支持内存安全性和原语级的并发,可以有效地预防内存泄漏,空悬指针以及数据竞争等问题,保证程序的稳定性和可靠性。 2. 高性能:Rust 语言采用了“零成本抽象化”的设计思想,具有 C/C++ 等传统高性能语言的速度和效率。 3. 静态类型检查:Rust 语言支持静态类型检查,可以在编译时检查类型错误,避免一些运行时错误。 链表的定义和操作: 链表是一种数据结构,由一个个节点组成,每个节点保存着数据,并指向下一个节点。链表的定义和操作如下: 1. 定义:链表是由节点组成的数据结构,每个节点包含一个数据元素和一个指向下一个节点的指针。 2. 操作:链表的常用操作包括插入、删除、查找等,其中,插入操作主要包括在链表首尾插入节点和在指定位置插入节点等,删除操作主要包括删除链表首尾节点和删除指定位置节点等,查找操作主要包括根据数据元素查找节点和根据指针查找节点等。 Rust 在 LeetCode 中链表题目的练习: 在 LeetCode 中,链表是常见的题目类型,而 Rust 语言也是一个非常适合练习链表题目的语言。在 Rust 中,我们可以定义结构体表示链表的节点,使用指针表示节点的指向关系,然后实现各种操作函数来处理链表操作。 例如,针对 LeetCode 中的链表题目,我们可以用 Rust 语言来编写解法,例如,反转链表,合并两个有序链表,删除链表中的重复元素等等,这样可以更好地熟悉 Rust 语言的使用和链表的操作,提高算法和编程能力。 总之,在 Rust 中处理链表是非常方便和高效的,而 LeetCode 中的练习也是一个非常好的机会,让我们更好地掌握 Rust 语言和链表数据结构的知识。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值