《力扣刷题笔记》除自身以外数组的乘积&& 复制带随机指针的链表

目录

一、本章重点

二、除自身以外数组的乘积

三、复制带随机指针的链表

四、最后


古岂无人,孤标凌云道为朋,剑宿吾命,亦狂亦侠亦超凡。

一、本章重点

题一:除自身以外数组的乘积

题二:复制带随机指针的链表

二、除自身以外数组的乘积

难度:中等

给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。

题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

请不要使用除法,且在 O(n) 时间复杂度内完成此题。

来源:力扣(LeetCode)

示例 1:

输入: nums = [1,2,3,4]

输出: [24,12,8,6]

示例 2:

输入: nums = [-1,1,0,-3,3]

输出: [0,0,9,0,0]

思路:

本来可以使用除法解决,即把所有数相乘得到总积,第一个新数组的值就是总积除以原数组的值。但这要考虑原数组中存在0的情况。

由于题目要求不要用除法,那么我们只能换一个思路了。

左积乘于右积:

除自身以外数组的乘积,即该元素的左积乘于右积。把每个元素的左积先放入开辟的新数组中,然后将每个元素的右积乘于新数组的值再把结果放入新数组,那么此时新数组的元素就是原数组的元素左积与右积之积,即除自身以外数组的乘积。

第一个元素的左积等于1

第二个元素的左积等于第一个元素的左积乘于第一个元素。

第三个元素的左积等于第二个元素的左积乘于第二个元素。

以此类推。。。。。。

因此我们可得出求每个元素的左积方法:

int left=1;
for(i=0;i<numsSize;i++)
    {
        if(i>0)
            left=left*nums[i-1];
        answer[i]=left;
    }

倒数第一个元素的右积等于1

倒数第二个元素的右积等于倒数第一个元素的右积乘于倒数第一个元素

倒数第三个元素的右积等于倒数第二个元素的右积乘于倒数第二个元素。

以此类推。。。。。。

因此我们可得出求每个元素的右积方法:

int right=1; 
for(i=numsSize-1;i>=0;i--)
    {
        if(i<numsSize-1)
            right=right*nums[i+1];
        answer[i]*=right;
    }

参考代码:

int* productExceptSelf(int* nums, int numsSize, int* returnSize)
{
    int* answer=(int*)malloc(sizeof(int)*numsSize);
    int i=0;
    int left=1;
    int right=1;
    for(i=0;i<numsSize;i++)
    {
        if(i>0)
            left=left*nums[i-1];
        answer[i]=left;
    }
    for(i=numsSize-1;i>=0;i--)
    {
        if(i<numsSize-1)
            right=right*nums[i+1];
        answer[i]*=right;
    }
    *returnSize=numsSize;
    return answer;
}

思路总结:

两步走:

第一步:

开辟一个新数组,将原数组每个元素的左积求出(如果为第一个数组元素,那么它的左积就是一)

并把它们的左积对应放入新数组中。

第二步:

求原数组每个元素的右积,求出后,乘于对应新数组的元素,并把乘后的结果放入对应新数组元素中。

三、复制带随机指针的链表

难度:中等

题目:给你一个长度为 n 的链表,每个节点包含一个额外增加的随机指针 random ,该指针可以指向链表中的任何节点或空节点。

构造这个链表的 深拷贝。 深拷贝应该正好由 n 个 全新 节点组成,其中每个新节点的值都设为其对应的原节点的值。新节点的 next 指针和 random 指针也都应指向复制链表中的新节点,并使原链表和复制链表中的这些指针能够表示相同的链表状态。复制链表中的指针都不应指向原链表中的节点 。

例如,如果原链表中有 X 和 Y 两个节点,其中 X.random --> Y 。那么在复制链表中对应的两个节点 x 和 y ,同样有 x.random --> y 。返回复制链表的头节点。

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

val:一个表示 Node.val 的整数。
random_index:随机指针指向的节点索引(范围从 0 到 n-1);如果不指向任何节点,则为  null 。
你的代码 只 接受原链表的头节点 head 作为传入参数。

来源:力扣(LeetCode)

题意:

以该链表为例,我们需要复制一份一模一样的该链表,类似于c++中的深拷贝。

说白了,就是原节点指向某个原节点,那么拷贝节点的random必须指向某个原节点的拷贝节点。

原7的next指向原13,即拷贝的7要指向拷贝的13,原7的random指向NULL,即拷贝的7的random要指向NULL。原13的random指向原7,即拷贝13的random要指向拷贝7.

将7、13、11、10、1、NULL拷贝出来一份,用next连接起来容易。但它们random怎么指向呢?

图解:

连接新链表与拷贝链表: 

我们只有原链表的头指针,我们知道原7的random是指向NULL,那么拷贝7的random指向NULL是容易的,原13的random是指向7的,我们只知道random指向的地址空间。

即我们只知道0x11 31 24 31,而这块空间和新拷贝的空间是无关联的,这并不能很好的帮助我们拷贝13该指向拷贝链表的哪块地址。

所有比较好的办法是将原链表和新拷贝链表连接起来。

图解:

参考代码:

    Node* cur=head;
    //在原节点后拷贝节点并连接起来。
    while(cur)
    {
        Node* newnode=(Node*)malloc(sizeof(Node));
        newnode->val=cur->val;
        newnode->next=cur->next;
        cur->next=newnode;
        cur=newnode->next;
    }

分配拷贝链表的random指向:

当我们知道原7的random指向NULL,那么拷贝7也指向NULL。

知道原13的random指向原7,那么拷贝13的random要指向拷贝7,即指向原7的next。

参考代码:

    //给拷贝节点random指针分配指向。
    while(cur)
    {
        copynode=cur->next;
        if(cur->random==NULL)
        {
            copynode->random=NULL;
        }
        else
        {
            copynode->random=cur->random->next;
        }
        cur=cur->next->next;
    }

解开原链表和拷贝链表:

我们用头插拷贝链表的方式将拷贝链表头插至另一链表。

参考代码:

 cur=head;
    Node* newhead=NULL;
    Node* newtail=NULL;
    while(cur)
    {
        copynode=cur->next;
        if(newhead==NULL)
        {
            newhead=newtail=copynode;
        }
        else
        {
            newtail->next=copynode;
            newtail=copynode;
        }
        cur->next=copynode->next;
        cur=copynode->next;
    }

完整代码参考:

typedef struct Node Node;
struct Node* copyRandomList(struct Node* head) 
{
    Node* cur=head;
    //在原节点后拷贝节点并连接起来。
    while(cur)
    {
        Node* newnode=(Node*)malloc(sizeof(Node));
        newnode->val=cur->val;
        newnode->next=cur->next;
        cur->next=newnode;
        cur=newnode->next;
    }
    cur=head;
    Node* copynode=NULL;
    //给拷贝节点random指针分配指向。
    while(cur)
    {
        copynode=cur->next;
        if(cur->random==NULL)
        {
            copynode->random=NULL;
        }
        else
        {
            copynode->random=cur->random->next;
        }
        cur=cur->next->next;
    }
    //断开连接
    cur=head;
    Node* newhead=NULL;
    Node* newtail=NULL;
    while(cur)
    {
        copynode=cur->next;
        if(newhead==NULL)
        {
            newhead=newtail=copynode;
        }
        else
        {
            newtail->next=copynode;
            newtail=copynode;
        }
        cur->next=copynode->next;
        cur=copynode->next;
    }
    return newhead;
}

思路总结:

本题解法分三步:

第一步连接:

将原链表和拷贝链表依次连接起来,把每个拷贝节点连接到对应原节点的后面。

第二步分配拷贝链表节点的random指针:

如果原链表节点random的指向是NULL,则对应的拷贝节点的random指向也是空,如果原链表节点random的指向是另一原链表非空节点,那么对应拷贝的拷贝节点random指向的是另一原链表非空节点的拷贝节点,即另一原链表非空节点->next。

第三步解开原链表与拷贝链表:

恢复原链表并连接拷贝节点。

四、最后

希望本篇文章能够帮助到大家,如有疑问请评论或者私信我,我都会及时回复的,谢谢大家!

每一个不曾起舞的日子,都是对生命的辜负!

猜你喜欢

转载自blog.csdn.net/m0_62171658/article/details/123704456
今日推荐