LeetCode 练习(3.27-3.31)

本文介绍了多个编程题目,涉及数组和链表的操作,如移除特定值元素、合并有序数组、链表反转、查找中间节点、合并链表、数组分隔以及解决约瑟夫环问题和消失的数字问题。通过双指针、快慢指针等技巧实现高效的O(n)时间复杂度解决方案。
摘要由CSDN通过智能技术生成

2024.3.27

1.移除数组中的元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

提示:

  • 0 <= nums.length <= 100
  • 0 <= nums[i] <= 50
  • 0 <= val <= 100

双指针(快慢双指针法)

int removeElement(int* nums, int numsSize, int val) {
     int src = 0;
     int dst = 0;
     while(src<numsSize)
     {
        if(nums[src]==val)
        {
            src++;
        }
        else
        {
            nums[dst] = nums[src];
            src++;
            dst++;
        }
     }
     return dst;
}

题解:只需要把val元素覆盖即可。把不是val的元素保留下来,是val的元素被不是的元素覆盖。

2.合并两个有序数组

给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。

请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。

注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 

  • nums1.length == m + n
  • nums2.length == n
  • 0 <= m, n <= 200
  • 1 <= m + n <= 200
  • -109 <= nums1[i], nums2[j] <= 109

思路以及代码如下

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {
    //第一种方法是将num2中元素先全部插入Num1中,再用排序方法排位递增
    //时间复杂度O(N^2)
    //有没有一种方法,在合并的同时就进行排序呢?
    //要把数据都放在nums1中,就不能从其前向后排序,否则无法将nums2中的数据插入(会导致nums1中数据被覆盖)
   int l1 = m - 1;
   int l2 = n - 1;
   int l3 = m+n-1;
   while(l1>=0&&l2>=0)
   {
       //比较nums1与nums2的最大值,将最大值插入到nums1 的 l3位置
       if(nums1[l1]>nums2[l2])
       {
          nums1[l3--] = nums1[l1--];
       }
       else
       {
        nums1[l3--] = nums2[l2--];
       }
   }
   //如果l1小于0,l2仍大于0时,直接将l2中剩余元素全部插入
   while(l2>=0)
   {
    nums1[l3--] = nums2[l2--];
   }
   //如果l1>=0,l2小于0呢,那说明排序已经完成
}

3.移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

  • 列表中的节点数目在范围 [0, 104] 内
  • 1 <= Node.val <= 50
  • 0 <= val <= 50

方法一:建立头节点

typedef struct ListNode ListNode;
 ListNode* removeElements(ListNode* head, int val) {
    //方法一:循环一个一个删,时间复杂度O(n)
    //但是在没有头结点的情况下,河南区判断全部要删除和头指针是NULL\head->val==val的情况
    //创建一个头结点
    ListNode* pcur = head;
    ListNode* phead = (ListNode*)malloc(sizeof(ListNode));
    phead->next = head;
    head = phead;
    ListNode* prev = phead;
    while(pcur)
    {
        if(pcur->val==val)
        {
            prev->next = pcur->next;
            pcur = prev->next;
            continue;
        }
        prev = pcur;
        pcur = pcur -> next;
    }
    return phead->next;
}

方法二:建立新链表

struct ListNode* removeElements(struct ListNode* head, int val) {
    struct ListNode* newhead = NULL;
    struct ListNode* newtail = NULL;
    struct ListNode* pcur = head;
    while(pcur)
    {
        if(pcur->val!=val)
        {
            if(newhead==NULL)
            {
                newhead = newtail = pcur;
            }
            else{
                newtail->next = pcur;
                newtail = newtail->next;
            }
        }
            pcur = pcur->next;
    }
    if(newtail)
    {
        newtail->next = NULL;
    }
    return newhead;
}

4.反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

  • 链表中节点的数目范围是 [0, 5000]
  • -5000 <= Node.val <= 5000
 typedef struct ListNode ListNode;
struct ListNode* reverseList(struct ListNode* head) {
    //链表为空时
    if(head==NULL)
    {
        return head;
    }
     //创建3个指针发呢别记录前驱节点,当前系欸但,候集解带你,改变原链表指向
     //创建三个指针
     ListNode* n1,*n2,*n3;
     n1 = NULL,n2 = head,n3=head->next;
     //遍历原链表,修改指针指向
     while(n2)
     {
         //n2指向
         n2->next = n1;
         n1 = n2;
         n2 =n3;
         if(n3)
         {
            n3 = n3->next;
         }
     }
     return n1;
     
}

2024.3.30

1.链表的中间节点

给你单链表的头结点 head ,请你找出并返回链表的中间结点。

如果有两个中间结点,则返回第二个中间结点

  • 链表的结点数范围是 [1, 100]
  • 1 <= Node.val <= 100

方法一:记录来链表中元素个数,找到中

 typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {
    ListNode* pcur = head;
    ListNode* mid = head;
    int sum = 0;
    int midd = 0;
    while(pcur)
    {
        pcur = pcur ->next;
        sum++;
    }
    midd = sum/2;
    for(int i = 0;i<midd;i++)
    {
         mid = mid->next;
    }
    return mid;
}

方法二:快慢指针法,l2速度是l1的二倍,当l2走到末尾时,l1刚好走到中间。

 typedef struct ListNode ListNode;
struct ListNode* middleNode(struct ListNode* head) {

    ListNode *p1,*p2;
    p1 = p2 = head;
    while(p2&&p2->next)
    {
        p1 = p1->next;
        p2 = p2->next->next;
    }
    return p1;

}

2.合并两个有序链表

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

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • l1 和 l2 均按 非递减顺序 排列

 方法:创建一个新链表,比较两个原链表,谁小谁插入

typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) {
    if(list1==NULL)
    {
        return list2;
    }
      if(list2==NULL)
    {
        return list1;
    }
    ListNode *newhead , *newtail;
    newhead = newtail = NULL;
    while(list1 && list2)
    {
        if(list1->val < list2->val)
        {
            if(newhead == NULL)
            {
                newhead = newtail = list1;
            }
            else{
                newtail->next = list1;
                newtail = list1;
            }
            list1 = list1->next;
        }
        else{
            if(newhead == NULL)
            {
                newhead = newtail = list2;
            }
            else{
                newtail->next = list2;
                newtail = list2;
            }
            list2 = list2->next;
        }
    }
    if(list1)
    {
        newtail->next = list1;//自动连接
    }
     if(list2)
    {
        newtail->next = list2;
    }
    return newhead;
}

3.分割链表

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你不需要 保留 每个分区中各节点的初始相对位置。

提示:

  • 两个链表的节点数目范围是 [0, 50]
  • -100 <= Node.val <= 100
  • l1 和 l2 均按 非递减顺序 排列

 方法一:创建两个新链表,一个保存比x小的元素,另一个存储比x大的元素,最后将两个链表连接起来,可以创建哨兵位也可以不创建,创建的优点在于不用if判断存储大数据链表为空/存储小数据链表为空。

1.不使用哨兵位

typedef struct ListNode ListNode;
struct ListNode* partition(struct ListNode* head, int x){
    ListNode* pcur = head;
    ListNode *Shead,*Stail;
    Shead = Stail = NULL;
    ListNode *Lhead,*Ltail;
    Lhead = Ltail = NULL;
    if(pcur==NULL)
    {
        return head;
    }
    while(pcur)
    {
        if(pcur->val<x)
        {
            //搞好头
            if(Shead==NULL)
            {
                Shead = Stail = pcur;
            }
            else{
                Stail->next = pcur;
                Stail = pcur;
            }
        }
        else
        {
            //搞好头
            if(Lhead==NULL)
            {
                Lhead = Ltail = pcur;
            }
            else{
                Ltail->next = pcur;
                Ltail = pcur;
            }
        }
        pcur = pcur->next;
    }
    if(Lhead==NULL)
    {
        return Shead;
    }
    if(Shead==NULL)
    {
        return Lhead;
    }
    Ltail->next = NULL;
    Stail->next = Lhead;
    return Shead;
}

2.使用哨兵位

//不使用头节点创建一个全新链表会使得我们还要判断Shead==NULL,Lhead==NULL
    //使用的化大小链表一定不会是空链表了
    ListNode* pcur = head;
    if(pcur==NULL)
    {
        return head;
    }
    ListNode *Shead,*Stail;
    ListNode *Lhead,*Ltail;
    Shead = Stail = (ListNode*)malloc(sizeof(ListNode));
    Lhead = Ltail = (ListNode*)malloc(sizeof(ListNode));
    while(pcur)
    {
        if(pcur->val<x)
        {
            Stail->next = pcur;
            Stail = pcur;
        }else{
            Ltail->next = pcur;
            Ltail = pcur;
        }
        pcur = pcur->next;
    }
    Ltail->next = NULL;
    //大小链表首尾相连
    Stail->next = Lhead->next;
    ListNode *ret = Shead->next;
    free(Shead);
    free(Lhead);
    return ret;

}

4.环形链表的约瑟夫问题

编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。

下一个人继续从 1 开始报数。

n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?

数据范围:1≤n,m≤10000

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

法:从1--n创建一个单向循环链表,分别存储1----n,遍历整个链表,每次++count,当count为m时删除链表元素,再将count重新至为1.最后返回pcur->val;

/**
 * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
 *
 * 
 * @param n int整型 
 * @param m int整型 
 * @return int整型
 */
 #include <stdlib.h>
 typedef struct ListNode ListNode;
 ListNode* BuyNode(int x)
 { 
    ListNode* newNode = (ListNode*)malloc(sizeof(ListNode));
    newNode ->val = x;
    newNode->next = NULL;
    return newNode;
 }
 ListNode* CreateList(int n)
 {
    ListNode* phead = BuyNode(1);
    ListNode* ptail = phead;
    for(int i = 2;i<=n;i++)
    {
        ptail->next = BuyNode(i);
        ptail = ptail->next;
    }
    //首尾相连
    ptail ->next  =phead;
    return phead;
 }
int ysf(int n, int m ) {
     int count = 1;
     ListNode *phead = CreateList(n);
     ListNode *pcur = phead;
     ListNode *prev = NULL;
     while(pcur->next!=pcur)
     {
        if(count==m)
        {
            prev->next = pcur->next;
            free(pcur);
            pcur = prev->next;
            count = 1;
        }
        else {
        {
            prev = pcur;
            pcur = pcur->next;
            count++;
        }
        }
     }
     return pcur->val;
}







2024.3.31

1.消失的数字

数组nums包含从0n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗?

 法1:0-n的和减去 循环数组得到所有元素的值,即为缺失的数字。



int missingNumber(int* nums, int numsSize){
    int sum = 0;
    for(int i = 0;i < numsSize;i++)
    {
        sum = sum + i - nums[i];
    }
    return sum+numsSize;
}

法2:(单身狗)使用异或

结论1:两数16进制位数 相异得到1,相同得到0,例如3^4=7.

结论2:0与任何数异或都为任何数,例如0^4=4,0^3=0

结论3:连续异或符合交换律。例如 x = 3^3^4 = 4;   x = 3^4^3 = 4; 可以自己去算一下。

那么,先用0异或数组中的所有元素,再异或0-n的所有元素,的大排档结果就是单身狗

int missingNumber(int* nums, int numsSize){
    int x = 0;
    for(int i = 0;i<numsSize;i++)
    {
        x = x^nums[i];
        x = x^i;
    }
    x = x^numsSize;
    return x;
}

2.轮转数组

给定一个整数数组 nums,将数组中的元素向右轮转 k 个位置,其中 k 是非负数。

示例 1:

输入: nums = [1,2,3,4,5,6,7], k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右轮转 1 步: [7,1,2,3,4,5,6]
向右轮转 2 步: [6,7,1,2,3,4,5]
向右轮转 3 步: [5,6,7,1,2,3,4]

提示:

  • 1 <= nums.length <= 105
  • -231 <= nums[i] <= 231 - 1
  • 0 <= k <= 105

法一:现将数组最后一个元素保存下来,再将所有数据向后移动1位,再将保存的元素放进数组前k位,循环k次,复杂度O(N^2),会超时,不采用。

法二:数组的三段逆置法:先将前n-k个逆置,再将后k个逆置,最后将整体逆置。

注意:最好情况:当k是numsSize的整数倍,相当于不动。

           最坏情况:当k%numsSize = numsSize - 1;

void Reverse(int* nums,int left,int right)
{
    //二者相等是停下
    while(left<right)
    {
       int tmp = nums[left];
       nums[left] = nums[right];
       nums[right] = tmp;
       left++;
       right--;
    }
}


void rotate(int* nums, int numsSize, int k) {
       k = k%numsSize;
       //先将前n-k个逆置
       Reverse(nums,0,numsSize-k-1);
       //再将后K个逆置
       Reverse(nums,numsSize-k,numsSize- 1);
       //整体逆置
       Reverse(nums,0,numsSize-1);
}

法三:法二比较难想出来。我们可以创建一个新数组(大小为numsSize,LeetCode 支持C99变长数组),将原数组的(n-k,n-1)复制到新数组的(0,k-1),再将原数组的(0,n-k-1)复制到新数组的(k,n-1).最后将新数组的每一个元素赋给旧数组即可(memcpy/for循环)

void rotate(int* nums, int numsSize, int k) {
    k = k%numsSize;
    int a[numsSize];
    int n = numsSize;
    memcpy(a,nums+n-k,k*sizeof(int));
    memcpy(a+k,nums,sizeof(int)*(n-k));
    memcpy(nums,a,sizeof(int)*n);
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值