leetcode【数据结构简介】《链表》卡片——小结

Author: Authur Whywait, 一个努力学习知识的孩子。好吧,其实是老男孩 \ (0^ ◇^0)/
想看博主的其他所有leetcode卡片学习笔记链接?传送门点这儿

复习

回顾单链表和双链表的表现

相同

  • 它们都无法在常量时间内随机访问数据。
  • 它们都能够在 O(1) 时间内在给定结点之后或列表开头添加一个新结点。
  • 它们都能够在 O(1) 时间内删除第一个结点。

不同 - 体现在删除给定结点(包括最后一个结点)

  • 在单链表中,它无法获取给定结点的前一个结点,因此在删除给定结点之前我们必须花费 O(N) 时间来找出前一结点
  • 在双链表中,这会更容易,因为我们可以使用“prev”引用字段获取前一个结点。因此我们可以在 O(1) 时间内删除给定结点。

对照

下面是链表和其他数据结构(包括数组,队列和栈)之间的时间复杂度比较:

在这里插入图片描述
通过比较,不难得出结论:

如果你需要经常添加或删除结点,链表可能是一个不错的选择。
如果你需要经常按索引访问元素,数组可能是比链表更好的选择。

程序练习:

1. 合并两个有序列表

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

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

思路:比较两个链表的表头,将其中较小的一个丢到新的链表里,直至遍历两个链表。

遇到过的报错:runtime error: member access within misaligned address(点击链接,可以查看错误原因)

遇到的第二个问题:
前提:head->next == NULL
错误操作:h = head->next; h = h->next;
错误解释:一个空的东西,哪来的next? (╯‵□′)╯︵┻━┻

下面是代码:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    struct ListNode * head = (struct ListNode *)malloc(sizeof(struct ListNode));
    head->val=0; 
    head->next=NULL;
    struct ListNode * h = head, * h1 = l1, * h2 = l2;

    while(h1 || h2){
        if(h1 && h2){
            struct ListNode * temp = (struct ListNode *)malloc(sizeof(struct ListNode));
            temp->val = h1->val < h2->val ? h1->val : h2->val;
            temp->next = NULL;
            h->next = temp;
            h = h->next;
            if(h->val==h1->val) h1 = h1->next;
            else h2 = h2->next;
        }
        else if(h1 && !h2){
            struct ListNode * temp = (struct ListNode *)malloc(sizeof(struct ListNode));
            temp->val = h1->val;
            temp->next = NULL;
            h->next = temp;
            h = h->next;
            h1 = h1->next;
        }
        else{
            struct ListNode * temp = (struct ListNode *)malloc(sizeof(struct ListNode));
            temp->val = h2->val;
            temp->next = NULL;
            h->next = temp;
            h = h->next;
            h2 = h2->next;
        }
    }
    return head->next;
}

在这里插入图片描述
想清楚情况类型,分情况讨论,这种问题就能轻易解决啦~(当然不要像我一样犯低级错误 (╯‵□′)╯︵┻━┻ )

2. 两数相加

给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。

如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。

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

输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807

思路:回想数组里面的两数相加,是不是就觉得so简单了呢~
这题其实就是关乎进位 - 满十进一

代码直接丢在下头

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */


struct ListNode* addTwoNumbers(struct ListNode* l1, struct ListNode* l2){
    
    struct ListNode *l3 = (struct ListNode*)malloc(sizeof(struct ListNode));
    l3->val = 0; l3->next = NULL;
    struct ListNode* h1 = l1, * h2 =l2, * h3 = l3; 
    int flag = 0;


    while(h1 != NULL && h2 != NULL){      
        
        if(flag){
            h3->val = 1;
            flag = 0;
        }

        h3->val += (h1->val + h2->val);
        if(h3->val >= 10){
            h3->val =  h3->val % 10;
            flag = 1;
        }
        h1 = h1->next;
        h2 = h2->next;
        if((h1 || h2) && !h3->next){
            struct ListNode *tempNode = (struct ListNode*)malloc(sizeof(struct ListNode));
            tempNode->val = 0;
            tempNode->next = NULL;
            h3->next = tempNode;
            h3 = h3->next;
        }
        
    }

    if (!h1 && !h2 && flag){
        if(!h3->next){
            struct ListNode *tempNode = (struct ListNode*)malloc(sizeof(struct ListNode));
            tempNode->val = 1;
            tempNode->next = NULL;
            h3->next = tempNode;
        }
    }

    if(h1){
        while(h1){
            h3->val = flag + h1->val;
            flag = 0;
            if(h3->val >= 10){
                h3->val = h3->val % 10;
                flag = 1;
            }
            h1 = h1->next;
            if(!h3->next && h1){
                struct ListNode *tempNode = (struct ListNode*)malloc(sizeof(struct ListNode));
                tempNode->val = 0;
                tempNode->next = NULL;
                h3->next = tempNode;
            }
            if(h1) h3 = h3->next;
        }
        if(flag && !h3->next){
            if(!h3->next){
                struct ListNode *tempNode = (struct ListNode*)malloc(sizeof(struct ListNode));
                tempNode->val = 1;
                tempNode->next = NULL;
                h3->next = tempNode;
            }
        }
        
    }
    else if(h2){
        while(h2){
            h3->val = flag + h2->val;
            flag = 0;
            if(h3->val >= 10){
                h3->val = h3->val % 10;
                flag = 1;
            }
            h2 = h2->next;
            if(!h3->next && h2){
                struct ListNode *tempNode = (struct ListNode*)malloc(sizeof(struct ListNode));
                tempNode->val = 0;
                tempNode->next = NULL;
                h3->next = tempNode;
            }
            if(h2) h3 = h3->next;
        }
        if(flag && !h3->next){
            if(!h3->next){
                struct ListNode *tempNode = (struct ListNode*)malloc(sizeof(struct ListNode));
                tempNode->val = 1;
                tempNode->next = NULL;
                h3->next = tempNode;
            }
        }
    }
    else ;//puts("something is wrong!");

    return l3;
}

在这里插入图片描述

其实学到这里的时候,我发现,这个程序是我在leetcode写的第二个程序(当初花了近乎一天的时间写出这个程序之后,我发现,单单做题是没用的,还需要吸收相关知识,不管是算法还是数据结构)。所以,这道题之后,我开始学习leetcode里面的卡片,并在CSDN上努力吸收知识。回想起来,我与刚进入leetcode的我,从自我感觉的角度来说,差别还是有的,不说解题的速度,更多的是在于面对难解问题时候内心的无所惧。继续加油!

3. 扁平化多级双向链表

多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。

给你位于列表第一级的头节点,请你扁平化列表,使所有结点出现在单级双链表中。

输入:head = [1,2,null,3]
输出:[1,3,2]
解释:
输入的多级列表如下图所示:
1—2---NULL
|
3—NULL

方法:把到处分叉的双向链表捋直了。觉得理不清就画个图,事半功倍
代码写的C++, 因为这道题leetcode没有给出C的语言环境
(╯‵□′)╯︵┻━┻

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* prev;
    Node* next;
    Node* child;
};
*/
class Solution {
public:
    Node* flatten(Node* head) {
        if(!head) return nullptr;
        Node *cur=head;
        while(cur){
            Node* nxt = cur->next;
            if(cur->child){
                Node * child1=cur->child;
                cur->next=child1;
                child1->prev=cur;
                cur->child=nullptr;

                Node *tail=child1;
                while(tail&&tail->next) tail=tail->next;

                tail->next=nxt;
                if(nxt) nxt->prev=tail;

            }
            cur = cur->next;
        }
        return head;
    }
};

在这里插入图片描述

4. 复制带随机指针的链表

给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。

要求返回这个链表的 深拷贝

我们用一个由 n 个节点组成的链表来表示输入/输出中的链表。每个节点用一个 [val, random_index] 表示:

  • val:一个表示 Node.val 的整数。
  • random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为 null 。

深拷贝是什么鬼?难道还有浅拷贝?于是我陷入了深深的沉思··· ok, fine. 我其实是去查资料了(这种东西单单靠脑子肿么想得出来)。深拷贝与浅拷贝传送门在这儿(看到这里的你一定黑人问号脸:传送门搁那儿呢?谁又能想到传送门是这儿后面的那个句号呢?我真是个小机灵鬼(●ˇ∀ˇ●))

"沉思"结束,我马上一顿操作
主要步骤分为两步:

  1. 构建一个新的链表,除了其中每个结点的随机指针h1->random==NULL都为NULL,和原链表并无其他差别;
  2. 新链表构建好之后,我们利用计数器index,依次遍历新链表,对每个结点的随机指针进行赋值。
/**
 * Definition for a Node.
 * struct Node {
 *     int val;
 *     struct TreeNode *next;
 *     struct TreeNode *random;
 * };
 */

struct Node* copyRandomList(struct Node* head) {
	if(!head) return NULL;

    /*使用next将整个链表构建起来*/
    
    struct Node *head1 = (struct Node *)malloc(sizeof(struct Node));
    head1->val = head->val;
    head1->next = NULL;
    head1->random = NULL;

    struct Node* h = head, * h1 = head1;

    while(h->next){
        h = h->next;
        struct Node * temp = (struct Node *)malloc(sizeof(struct Node));
        temp->val = h->val;
        temp->next = NULL;
        temp->random = NULL;
        
        h1->next = temp;
        h1 = h1->next;
    }
    
    /*处理那个随机指针*/
    h = head; h1 = head1; 
    while(h){
        if(!h) break;

        int index=0;
        struct Node * cur=head, * cur1=head1;

        if(h->random){
            while(cur && cur!=h->random){
                index++;
                cur = cur->next;
            }
            while(index){
                index--;
                cur1 = cur1->next;
            }
            h1->random = cur1;
        }
        else h1->random = NULL;
        
        h = h->next;
        h1 = h1->next;
    }
    return head1;
}

在这里插入图片描述
PS:此题所用到的深拷贝,只是深拷贝里面比较简单的一种。过些日子要写一篇blog,专门总结整理一下深拷贝的相关知识点,包括深拷贝与浅拷贝的区别以及深拷贝的程序实现等内容。ヾ(≧ ▽ ≦)ゝ

5. 旋转链表

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL

如果你是随着我的整个leetcode卡片学习系列一路下来,那你一定是知道我曾经遇到过一道题——旋转数组,这道题的解法颇多,详情看链接。(这次传送门不在句号上,惊喜不?( ¯(∞)¯ ) )

言归正传,我们可以用 旋转数组 里面的思想来处理这道题目么?

自然是可以的!
不管是暴力换头术,辅助链表,还是其他方法,都可以解决这类问题。
但是对于链表而言,用来做,不香吗?

方法:

  1. 最后一个节点的next指针指向头节点
  2. 找到相应的位置,断掉环,返回新的头节点。

实际操作小技巧:使用%取余,减少不必要的循环次数

struct ListNode* rotateRight(struct ListNode* head, int k){
	/*如果为空,直接返回NULL*/
    if(!head) return NULL;

	/*得到节点个数size*/
    int size=1;
    struct ListNode* cur=head;
    while(cur->next){
        cur = cur->next;
        size++;
    }
    
    /*如果右移位数k为size的整数倍,则直接返回原链表,right为头节点右移的位数*/
    int right = size - (k%size); 
    if(size==right) return head;
    
	/*尾首相连,得到一个环,找到新的头节点的位置以及新的最后一个节点的位置*/
    cur->next = head;
    struct ListNode* newtail=cur;
    while(right){
        newtail = newtail->next;
        right--;
    }

	/*断环*/
    head = newtail->next;
    newtail->next = NULL;
    
	/*返回新的头节点*/
    return head;
}

在这里插入图片描述

都看到这里了,确定不点个赞再走? (╯‵□′)╯︵┻━┻

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

AuthurLEE

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值