DS:经典算法OJ题(2)

news2024/11/14 13:46:57

创作不易,友友们给个三连吧!!

一、旋转数组(力扣)

经典算法OJ题:旋转数组

思路1:每次挪动1位,右旋k次

时间复杂度:o(N^2)       

右旋最好情况:k是n的倍数,相当于不右旋,此时为o(1)

右旋最坏情况:k%n==n-1,此时为o(N^2)

空间复杂度:o(1)

void rotate(int* nums, int numsSize, int k) 
{
    k%=numsSize;
    while(k)
    {
        int temp=nums[numsSize-1];
        //从后往前挪 
        for(int i=numsSize-1;i>0;i--)
        {
             nums[i]=nums[i-1];//最后一个是nums[1]=num[0]
        }
        nums[0]=temp;
        k--;//旋转一次就减一次
    }
}

注:这是常规思路,但是由于空间复杂度太高,数组个数特别多的时候,在力扣运行的时候超出了时间限制!

思路2:创建一个和nums一样长度的新数组,将nums数组的后k个元素,先按顺序放进新数组里,然后剩下前面的n-k个元素,再按顺序放进新数组,最后再将新数组的数据拷贝到nums中

时间复杂度:o(N)

空间复杂度:o(N)

void rotate(int* nums, int numsSize, int k) 
{
   k%=numsSize;
   int arr[numsSize];//vs不支持变长数组,但是牛客支持,如果是vs只能使用动态数组。
   memcpy(arr,nums+numsSize-k,sizeof(int)*k);//nums的后k个按顺序拷贝到新数组的前面
   memcpy(arr+k,nums,sizeof(int)*(numsSize-k));//nums的前n-k个按顺序拷贝到新数组的后面
   memcpy(nums,arr,sizeof(int)*numsSize);//新数组完全拷贝到nums数组中
}

思路3:前n-k个元素逆置,后k个元素逆置,再整体逆置

时间复杂度:o(N)

空间复杂度:o(1)

void reverse (int *arr,int left,int right)//实现逆序函数
{
    int temp=0;
    while(left<right)
    {
        temp=arr[left];
        arr[left]=arr[right];
        arr[right]=temp;
        left++;
        right--;
    }
}
void rotate(int* nums, int numsSize, int k) 
{
    k%=numsSize;
   reverse(nums,0,numsSize-k-1);//前n-k个元素逆序
   reverse(nums,numsSize-k,numsSize-1);//后k个逆序
   reverse(nums,0,numsSize-1);//完全逆序
}

二、消失的数字(力扣)

经典算法OJ题:消失的数字

思路1:先进行排序,如果后一个不等于前一个+1,就可以找到消失的数据,但是目前掌握的排序中,冒泡排序的时间复杂度是o(N^2),而qsort的时间复杂度是o(logN+N),均不符合题意,这里不做考虑!

思路2:让0和0-numsSize的所有数都异或一遍,再和数组中的所有元素异或一边,最后得到的结果就是消失的数(利用了a^a=0,a^0=a的结论)

时间复杂度:o(N)

空间复杂度:o(1)

int missingNumber(int* nums, int numsSize)
{
int x=0;
for(int i=0;i<numsSize;i++)
{
    x^=i;
    x^=nums[i];
}
x^=numsSize;//还多了一个数
return x;
}

思路3:0-numsSize的所有数相加,然后减去数组中的所有元素之和,得到的就是消失的数字。

时间复杂度:o(N)

空间复杂度:o(1)

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

三、链表中倒数第k个结点(牛客)

经典算法OJ题:链表中倒数第k个结点

思路1:第一次循环计算结点的个数count,然后求倒数第k个就是正数的第count-k个结点

空间复杂度:o(N)

时间复杂度:o(1)

 typedef struct ListNode ListNode;
struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
 {
ListNode* pcur= pListHead;
int count=0;//统计结点
while(pcur)
{
    pcur=pcur->next;
    count++;
}
if(count<k)
return NULL;//考虑链表为NULL,以及k大于链表结点数
pcur= pListHead;
for(int i=0;i<count-k;i++)
pcur=pcur->next;
return pcur;
}

思路2:(快慢指针)fast指针先走k步,然后fast和slow同时走,始终保持k的距离,当fast走到NULL的时候,slow对应的恰好就是倒数第k个结点

空间复杂度:o(N)

时间复杂度:o(1)

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
 {
struct ListNode*fast=pListHead,*slow=pListHead;
while(k)
{
    //考虑k大于结点数的情况,此时链表很早就为空了
    if(fast==NULL)
    return NULL;
    fast=fast->next;
    k--;
}
//同时走,直到fast为NULL,此时slow指向倒数第k个结点
while(fast)
{
    fast=fast->next;
    slow=slow->next;
}
//如果k<=0.那么第一个while循环不会进入,
//fast和slow同时走,最后都会指向空,所以不需要额外判断
return slow;
}

思路3:直接反转链表,然后直接找第k个结点

该方法直接改变了链表结构,使得phead变成了一个尾结点,与其他结点建立不起联系,所以该思路不行(尽量不要去改变原先链表的结构)在力扣中过不了。

struct ListNode* FindKthToTail(struct ListNode* pListHead, int k )
 {
//直接反转链表,然后找第k个结点
if(pListHead==NULL)
return NULL;
struct ListNode*p1=NULL;
struct ListNode*p2=pListHead;
struct ListNode*p3=pListHead->next;
int count=0;//用来数数
while(p2)
{
    p2->next=p1;
    p1=p2;
    p2=p3;
    if(p3)
    p3=p3->next;
    ++count;
}
//此时的p1就是新链表的头结点
if(k<=count||k>count)
return NULL;
while(--k)
{
p1=p1->next;
}
return p1;
}

四、相交链表(力扣)

经典算法OJ题:相交链表

思路1:A链表逐个结点与B链表比较,如果存在相等,则就是相交结点(注:要比较指针而不能比较值,因为值是可以重复的)

空间复杂度:o(N^2)

时间复杂度:o(1)

typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
   ListNode* pcurA=headA;
   ListNode* pcurB=headB;
   while(pcurA)
   {
       while(pcurB)
       {
           if(pcurA==pcurB)
           return pcurA;
           pcurB=pcurB->next;
       }
       pcurB=headB;
       pcurA=pcurA->next;
   }
   return NULL;
}

思路2:长的链表往后走长度差,再同时走,直到相等就是相交点

空间复杂度:o(N)

时间复杂度:o(1)

typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
   ListNode * Apcur=headA;//用来遍历A链表
    ListNode * Bpcur=headB;//用来遍历A链表
    int a=0;//数a长度
    int b=0;//数b长度
while(Apcur)
{
Apcur=Apcur->next;
a++;
}
while(Bpcur)
{
Bpcur=Bpcur->next;
b++;
}
//找最小数,写俩while循环,只要大的数才可以走,小的走不了
int m=a>b?b:a;
while(a-m)
{
    headA=headA->next;
    a--;
}
while(b-m)
{
    headB=headB->next;
    b--;
}
while(headA)
{
    if(headA==headB)
    return headA;
    headA=headA->next;
    headB=headB->next;
}
return NULL;
}

五、链表的回文结构(牛客)

经典算法OJ题:链表的回文结构

思路1:找到中间结点,然后逆置后半段,然后将后续半段和前半段的链表同时走,如果其中一个走到空了值依旧是相等的,那么就是回文结构!!

空间复杂度:o(N)

时间复杂度:o(1)

ListNode *middleNode(struct ListNode *phead)
{
struct ListNode *fast,*slow;
fast=slow=phead;
while(fast!=NULL&&fast->next!=NULL)
{
fast=fast->next->next;
slow=slow->next;
}
return slow;
}
struct ListNode* reverseList(struct ListNode* head)
{
    //链表为空的时候
    if(head==NULL)
    return head;
    //链表不为空的时候,创建3个指针,分别指向前驱、当前、后继结点
struct ListNode*p1,*p2,*p3;
p1=NULL;//前驱
p2=head;//当前
p3=head->next;//后继
while(p2)
{
    //改变指向
p2->next=p1;
//向后挪动
p1=p2;
p2=p3;
//考虑p3为NULL的时候
if(p3)
p3=p3->next;
}
return p1;
}
class PalindromeList {
public:
    bool chkPalindrome(ListNode* A) 
    {
        struct ListNode *mid=middleNode(A);//找中间结点
           struct ListNode *rmid=reverseList(mid);//逆序后半段
           while(rmid)
           {
            if(A->val!=rmid->val)
            return false;
            A=A->next;
            rmid=rmid->next;
           }
           return true;
    }
};

六、随机链表的复制(力扣)

经典算法OJ题:随机链表的复制

思路1:1、插入拷贝结点到原结点的后面,2、控制拷贝结点的random,3、拷贝结点解下来,尾插到新链表上,同时恢复原链表

空间复杂度:o(N)

时间复杂度:o(N)

typedef struct Node Node;
Node* copyRandomList(Node* head) 
{
    if(head==NULL)
    return NULL;
	//将拷贝结点放在原结点的后面
      Node*pcur=head;
    while(pcur)
    {
       Node*copy=(Node*)malloc(sizeof(Node));//拷贝结点
       copy->val=pcur->val;
       //插入
       copy->next=pcur->next;
       pcur->next=copy;
       
       //迭代
       pcur=pcur->next->next;
    }
    //控制拷贝结点的random指针
    pcur=head;
    while(pcur)
    {
        //有可能random指向NULL
        Node* copy=pcur->next;
        if(pcur->random==NULL)
        copy->random=NULL;
        else
        //拷贝结点的random恰好在原结点的random后面
        copy->random=pcur->random->next;
        //迭代
        pcur=pcur->next->next;
    }
    //将拷贝结点解下来尾插到新链表上
    pcur=head;
    Node*newhead,*newtail,*temp;
    newhead=newtail=(struct Node*)malloc(sizeof(struct Node));
    temp=NULL;//用来记录遍历点
    while(pcur)
    {
        Node* copy=pcur->next;
        temp=copy->next;//记录遍历点
        newtail->next=copy;//尾插
        newtail=newtail->next;
     //修复原链表
     pcur->next=temp;
     //继续遍历
     pcur=pcur->next;
    }
    Node*ret=newhead->next;//销毁哨兵结点前记住头结点
    free(newhead);
    newhead=NULL;
    return ret;
}

思路2:暴力拷贝链表,然后看原结点的random是原链表的第几个结点,对应的就是拷贝链表的的第几个结点

空间复杂度:o(N^2)

时间复杂度:o(N)

typedef struct Node Node;
Node* copyRandomList(Node* head) 
{
    if(head==NULL)
    return NULL;
    Node*pcur=head;
    Node*newhead,*newtail;
    newhead=newtail=(Node*)malloc(sizeof(Node));//哨兵结点
   while(pcur)
   {
      Node*newnode=(Node*)malloc(sizeof(Node));
      newnode->val=pcur->val;
      newtail->next=newnode;
      newtail=newnode;
      //迭代
      pcur=pcur->next;
   }
   newtail->next=NULL;//要记住最后有个NULL;
   pcur=head;//回到链表头
   Node*newpcur=newhead->next;//用来遍历新链表头
   while(pcur)
   {
       int s=0;//记录节点与head的距离
       Node*flag=head,*temp=pcur->random;//temp记住random结点
       while(flag!=temp)
       {
           ++s;
           flag=flag->next;
       }
       flag=newhead->next;//回到新链表的头
       while(s--)
       flag=flag->next;
       //找到了,就接上
      newpcur->random=flag;
      pcur=pcur->next;
      newpcur=newpcur->next;
   }
   Node*ret=newhead->next;
   free(newhead);
   newhead=NULL;
   return ret;
}

七、带环链表的快慢指针追击问题(力扣)

7.1 判断链表中是否有环

经典算法OJ题:判断链表是否带环

思路:快慢指针追击

 typedef struct ListNode ListNode;
bool hasCycle(struct ListNode *head)
{
    ListNode*fast,*slow;
    fast=slow=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast)
        return true;
    }
    return false;
}

7.2 返回链表开始入环的第一个结点

思路1:利用相遇点到入口点距离等于链表头到入口点距离的结论

 typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head)
{
    ListNode*fast,*slow;
    fast=slow=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        //相等,说明相遇了,链表带环
        if(slow==fast)
    {
        ListNode*meet=slow;
        while(meet!=head)
        {
            meet=meet->next;
            head=head->next;
        }
        return meet;
    }
  }  
  return NULL;
}

思路2:在相遇点将带环链表拆开,转化成求链表相交结点的问题

typedef struct ListNode ListNode;
 struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) 
{
   ListNode * Apcur=headA;//用来遍历A链表
    ListNode * Bpcur=headB;//用来遍历A链表
    int a=0;//数a长度
    int b=0;//数b长度
while(Apcur)
{
Apcur=Apcur->next;
a++;
}
while(Bpcur)
{
Bpcur=Bpcur->next;
b++;
}
//找最小数,写俩while循环,只要大的数才可以走,小的走不了
int m=a>b?b:a;
while(a-m)
{
    headA=headA->next;
    a--;
}
while(b-m)
{
    headB=headB->next;
    b--;
}
while(headA)
{
    if(headA==headB)
    return headA;
    headA=headA->next;
    headB=headB->next;
}
return NULL;
}
struct ListNode *detectCycle(struct ListNode *head)
{
    ListNode*fast,*slow;
    fast=slow=head;
    while(fast&&fast->next)
    {
        slow=slow->next;
        fast=fast->next->next;
        if(slow==fast)
        {
            //将带环链表的环拆开
        ListNode*newhead=slow->next;
        slow->next=NULL;
        return getIntersectionNode(newhead,head);
        }
    }
    return NULL;
}

7.3 追击问题扩展

根据前两题可以知道对于带环的链表,fast走2步,slow走1步

1、必然会相遇,不会错过

2、L=(n-1)*C+(C-x)   一个指针从相遇点走,一个指针从链表头走,最后会在入口点相遇

如果fast走3步,slow走1步,可以得到什么结论??

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1431582.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

‘begin_code.h‘ file not found 问题解决办法

/usr/include/x86_64-linux-gnu/SDL2/SDL_platform.h:179:10: fatal error: ‘begin_code.h’ file not found 问题解决办法 问题 在使用SDL2库时编译程序出现如下问题&#xff1a; 解决办法 在Google搜索未果后&#xff0c;考虑到对于头文件找不到问题&#xff0c;可以通…

clickhouse在MES中的应用-跟踪扫描

开发的MES&#xff0c;往往都要做生产执行跟踪扫描&#xff0c;这样会产生大量的扫描数据&#xff0c;用关系型数据库&#xff0c;很容易造成查询冲突的问题。 生产跟踪扫描就发生的密度是非常高的&#xff0c;每个零部件的加工过程&#xff0c;都要被记录下来&#xff0c;特别…

老师没收学生东西违法吗怎么处理

身为一名老师&#xff0c;面对没收学生东西这个话题&#xff0c;确实让人感到有些尴尬和无奈。毕竟&#xff0c;我们教育的初衷是为了引导学生健康成长&#xff0c;而不是与他们产生矛盾和冲突。 老师在未经允许的情况下没收学生的物品&#xff0c;是违法的。学生的个人财物&a…

python+flask人口普查数据的应用研究及实现django

作为一款人口普查数据的应用研究及实现&#xff0c;面向的是大多数学者&#xff0c;软件的界面设计简洁清晰&#xff0c;用户可轻松掌握使用技巧。在调查之后&#xff0c;获得用户以下需求&#xff1a; &#xff08;1&#xff09;用户注册登录后&#xff0c;可进入系统解锁更多…

微服务的幂等性

微服务架构设计的中心思想是将服务进行拆分&#xff0c;但是在这个过程中&#xff0c;如果被依赖的服务发生奔溃&#xff0c;就会引起一系列问题。为了解决这个问题&#xff0c;就会引入重试的机制&#xff0c;重试又会引入幂等性的问题&#xff0c;下面我们就分析这个过程&…

面试150 颠倒二进制位 位运算分治 逻辑右移

Problem: 190. 颠倒二进制位 文章目录 思路复杂度位运算分治法 思路 &#x1f468;‍&#x1f3eb; 参考题解 >>>&#xff1a;逻辑右移&#xff08;符号位一起移动&#xff0c;高位补零&#xff09; 复杂度 时间复杂度: O ( log ⁡ n ) O(\log{n}) O(logn) 空间…

Quartus IP学习之ISSP(In-System Sources Probes)

一、ISSP IP概要&#xff1a; ISSP&#xff1a;In-System Sources & Probes Intel FPGA IP 作用&#xff1a; 分为In-System Sources与In-System Probesn-System Sources&#xff0c;输入端&#xff0c;等价于拨码开关&#xff0c;通过输入板载FPGA上的拨码开关状态改变…

如何结合ChatGPT生成个人魔法咒语词库

3.6.1 ChatGPT辅助力AI绘画 3.6.1.1 给定主题让ChatGPT直接描述 上面给了一个简易主题演示一下&#xff0c;这是完全我没有细化的提问&#xff0c;然后把直接把这些关键词组合在一起。 关键词&#xff1a; 黄山的美景&#xff0c;生机勃勃&#xff0c;湛蓝天空&#xff0c;青…

鲜花销售|鲜花销售小程序|基于微信小程序的鲜花销售系统设计与实现(源码+数据库+文档)

鲜花销售小程序目录 目录 基于微信小程序的鲜花销售系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、前台功能模块 2、后台功能模块 (1) 后台登录 (2) 管理员功能模块 用户管理 商家管理 鲜花信息管理 鲜花分类管理 管理员管理 系统管理 (3) 商家功…

SerDes PoC 电感网络工作原理详解

如下图所示,PoC的工作原理可以描述如下: 1. 直流状态时,电感处于短路状态,电容处于开路状态,因此,接收端的电源能够通过电感注入到信号传输系统中,并在另一端通过电感为本地电路供电,而不会透过电容影响到两端的高速收发器; 2. 交流状态时,即高频信号注入时,电容器是…

华为机考入门python3--(5)牛客5-进制转换

分类&#xff1a;数字 知识点&#xff1a; 十六进制转int num int(hex_num, 16) int转十六进制 hex_num hex(num) 题目来自【牛客】 hex_num input().strip() dec_num int(hex_num, 16) print(dec_num) by 软件工程小施同学

【OpenCV学习笔记28】- OpenCV 中的直方图 - 直方图 - 2:直方图均衡

这是对于 OpenCV 官方文档中 图像处理 的学习笔记。学习笔记中会记录官方给出的例子&#xff0c;也会给出自己根据官方的例子完成的更改代码&#xff0c;同样彩蛋的实现也会结合多个知识点一起实现一些小功能&#xff0c;来帮助我们对学会的知识点进行结合应用。 如果有喜欢我笔…

【Java并发】聊聊Disruptor背后高性能的原理

为什么需要Disruptor 对于单机生产者消费者来说&#xff0c;JUC本身提供了阻塞队列&#xff0c;ArrayBlockingQueue、LinkedBlockingQueue 等&#xff0c;但是为了保证数据安全&#xff0c;使用了reentrantLock进行加锁操作会影响性能&#xff0c;另一方面&#xff0c;本身如果…

Kafka相关内容复习

为什么要用消息队列 解耦 允许你独立的扩展或修改两边的处理过程&#xff0c;只要确保它们遵守同样的接口约束。 可恢复性 系统的一部分组件失效时&#xff0c;不会影响到整个系统。消息队列降低了进程间的耦合度&#xff0c;所以即使一个处理消息的进程挂掉&#xff0c;加入队…

XCTF:3-1[WriteUP]

从题目中获取文件 使用file命令查看文件类型 修改后缀为.rar后进行解压缩 再次使用file命令查询该文件的类型 再次修改后缀为.pcap或者.pcapng 使用wireshark打开&#xff0c;直接搜索flag字样 在多个数据包里发现了flag.rar、flag.txt等文件 尝试使用http导出文件 有一个fl…

小程序<scroll-view/>组件详解及使用指南

目录 引言小程序的流行和重要性scroll-view 组件在小程序中的作用和优势什么是 scroll-view 组件scroll-view 组件的基本概念scroll-view 组件的基本功能scroll-view 组件的属性与用法scroll-view 组件的常用属性参考代码scroll-view 组件的滚动事件scrolltoupper

【开源】SpringBoot框架开发大学计算机课程管理平台

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 实验课程档案模块2.2 实验资源模块2.3 学生实验模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 实验课程档案表3.2.2 实验资源表3.2.3 学生实验表 四、系统展示五、核心代码5.1 一键生成实验5.2 提交实验5.3 批阅实…

ESU毅速丨为什么增材制造广受关注?

随着科技的飞速发展&#xff0c;增材制造3D打印技术逐渐成为制造业的新宠。包括航空航天、汽车、家电、电子等各行业都在积极拥抱3D打印&#xff0c;为什么3D打印能引起制造业广泛关注与应用&#xff1f;它的主要优势有哪些&#xff1f; 首先&#xff0c;3D打印减少浪费。3D打印…

私有化部署的局域网即时通讯工具

在当今快节奏的企业环境中&#xff0c;高效的内部通信成为提高团队协作和工作效率的关键。而企业内部通信软件作为实现实时协作和快速沟通的利器&#xff0c;WorkPlus以其领先的功能和卓越的性能&#xff0c;助力企业打造高效团队沟通协作的新时代。 为何选择WorkPlus作为企业内…

简单说说-docker网络类型

概述 容器网络是指容器之间或非 Docker 工作负载之间连接和通信的能力。容器默认启用网络&#xff0c;并且可以建立传出连接。容器不知道它所连接的网络类型&#xff0c;容器只能看到带有 IP 地址、网关、路由表、DNS 服务和其他网络详细信息的网络接口。也就是说&#xff0c;…