当前位置: 首页 > news >正文

【数据结构】强化训练:从基础到入门到进阶(2)

1 链表分割

题目解析:

不能改变原来的数据顺序”,更准确地说,是不能改变原来节点值的相对顺序

结合这道链表题来理解:
假设原链表中,小于 x 的节点值依次是 a₁, a₂, ..., aₙ,它们在原链表中的出现顺序是 a₁ 先于 a₂a₂ 先于 a₃,以此类推;
大于等于 x 的节点值依次是 b₁, b₂, ..., bₘ,它们在原链表中的出现顺序是 b₁ 先于 b₂b₂ 先于 b₃,以此类推。

经过操作后,所有小于 x 的节点要排在所有大于等于 x 的节点之前,且在 “小于 x 的节点区域内”,a₁, a₂, ..., aₙ 的顺序必须和原链表中一致;在 “大于等于 x 的节点区域内”,b₁, b₂, ..., bₘ 的顺序也必须和原链表中一致。

思路:创建两个链表(小链表,大链表),遍历原链表,小的尾插到小链表中,大的尾插到大链表中,大链表和小链表首尾相连

ListNode* partition(ListNode* pHead, int x) {// write code hereListNode* lesshead,*lesstail;ListNode* greaterhead,*greatertail;lesshead=lesstail=(ListNode*)malloc(sizeof(ListNode));greaterhead=greatertail=(ListNode*)malloc(sizeof(ListNode));ListNode*pcur=pHead;while(pcur){if(pcur->val<x){lesstail->next=pcur;lesstail=lesstail->next;}else{greatertail->next=pcur;greatertail=greatertail->next;}pcur =pcur->next;}greatertail->next=NULL;lesstail->next=greaterhead->next;ListNode* prev=lesshead->next;free(lesshead);free(greaterhead);return prev;}

2 相交链表

思路:

(1)求两个链表的长度,求得两个链表的长度差为x

(2)让表较长的表头先走x步

(3)让两个指针一起走,看走到链表尾部前是否有两个链表指向相同的情况(找相同的节点)

 typedef struct ListNode ListNode;
struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {int la=0;int lb=0;ListNode* pcur=headA;ListNode* prev=headB;while(pcur){la++;pcur=pcur->next;}  while(prev){lb++;prev=prev->next;}int gap=abs(la-lb);ListNode* longList=headA;ListNode* shortList=headB;if(la<lb){shortList=headA;longList=headB;}while(gap--){longList=longList->next;}while(shortList){if(shortList==longList){return shortList;}shortList=shortList->next;longList=longList->next;}return NULL;
}

3 环形链表

思路:快慢指针,慢指针每次走一步,快指针每次走两步。如果slow和fast指向同一节点,说明该链表带环。

在环形链表中,慢指针每次走一步,快指针每次走2,3,4,5........步,快慢指针在环形链表中一定会相遇

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

易错点:

1. 为什么循环条件是 while(fast && fast->next)

这是为了避免避免空指针访问错误,同时确保快指针能正常移动:

  • 快指针每次要移动两步(fast->next->next),所以需要保证:
    • fast 本身不为 NULL(否则无法访问 fast->next
    • fast->next 不为 NULL(否则无法访问 fast->next->next
  • 如果链表无环,快指针会先到达终点(遇到 NULL),此时循环终止,返回 false

2. 为什么要先移动指针,再判断 if(slow == fast)

这是由初始状态决定的:

  • 初始时 slow 和 fast 都指向 head(起点相同)
  • 如果先判断再移动,会直接误判空链表或单节点链表有环(因为初始状态下 slow == fast

4 环形链表Ⅱ

思路:快慢指针,在环里一定会相遇。

相遇点到入环节点的距离 == 头节点到入环节点的距离

typedef struct ListNode ListNode;
struct ListNode *detectCycle(struct ListNode *head) {ListNode* fast=head;ListNode* slow=head;while(fast&&fast->next){fast=fast->next->next;slow=slow->next;if(fast==slow){ListNode* pcur=head;while(pcur!=slow){pcur=pcur->next;slow=slow->next;}return pcur;}}return NULL;}

5 移动零

思路:双指针

使用双指针,左指针指向当前已经处理好的序列的尾部,右指针指向待处理序列的头部。

右指针不断向右移动,每次右指针指向非零数,则将左右指针对应的数交换,同时左指针右移。

注意到以下性质:

左指针左边均为非零数;

右指针左边直到左指针处均为零。

因此每次交换,都是将左指针的零与右指针的非零数交换,且非零数的相对顺序并未改变。

right 指针的作用是遍历整个数组的所有元素,因此在每一轮循环结束时,无论当前元素是 0 还是非零,right 都会执行 right++,确保能依次检查数组中的每个元素

举例说明(以 nums = [0,1,0,3,12] 为例):

  1. 初始:left=0,right=0(元素为 0)→ 不交换,right++right=1)。
  2. right=1(元素为 1,非零)→ 交换后 left++left=1),right++right=2)。
  3. right=2(元素为 0)→ 不交换,right++right=3)。
  4. right=3(元素为 3,非零)→ 交换后 left++left=2),right++right=4)。
  5. right=4(元素为 12,非零)→ 交换后 left++left=3),right++right=5,循环结束)。

最终数组变为 [1,3,12,0,0],符合预期。

代码实现:

void swap(int *a,int *b)
{int tmp=*a;*a=*b;*b=tmp;
}
void moveZeroes(int* nums, int numsSize) {int left=0,right=0;while(right<numsSize){if(nums[right]){swap(&nums[left],&nums[right]);left++;}right++;}
}

时间复杂度:O(n),其中 n 为序列长度。每个位置至多被遍历两次。

空间复杂度:O(1)。只需要常数的空间存放若干变量。

循环语句中的swap(&nums[left], &nums[right]),也可以表示为wap(nums + left, nums + right),

都是将数组中索引为 left 和 right 的两个元素进行交换。

样写的原因与 指针和数组的关系 有关:

  • 在 C 语言中,数组名 nums 本质上是指向数组首元素的指针(nums 等价于 &nums[0])。
  • nums + left 表示指针向后偏移 left 个位置,指向数组中索引为 left 的元素(等价于 &nums[left])。
  • 同理,nums + right 等价于 &nums[right],指向索引为 right 的元素。

而 swap 函数的参数要求是两个 int* 类型的指针(即整数的地址),因此传递 nums + left 和 nums + right 正好符合函数的参数要求,实现了交换 nums[left] 和 nums[right] 的效果。

六 复写零

方法一:双指针
思路与算法

首先如果没有原地修改的限制,那么我们可以另开辟一个栈来进行模拟放置:
而实际上我们可以不需要开辟栈空间来模拟放置元素,我们只需要用两个指针来进行标记栈顶位置和现在需要放置的元素位置即可。我们用 top 来标记栈顶位置,用 i 来标记现在需要放置的元素位置,那么我们找到原数组中对应放置在最后位置的元素位置,然后在数组最后从该位置元素往前来进行模拟放置即可。

void duplicateZeros(int* arr, int arrSize){int top = 0;int i = -1;while (top < arrSize) {i++;if (arr[i] != 0) {top++;} else {top += 2;}}int j = arrSize - 1;if (top == arrSize + 1) {arr[j] = 0;j--;i--;} while (j >= 0) {arr[j] = arr[i];j--;if (!arr[i]) {arr[j] = arr[i];j--;} i--;}
}

http://www.dtcms.com/a/473400.html

相关文章:

  • python异步编程 -什么是python的异步编程, 与多线程和多进程的区别
  • Linux系统--进程间通信--共享内存相关指令
  • 网站开发的实践报告石家庄市工程勘察设计咨询业协会
  • TensorFlow深度学习实战——图分类
  • SAP MM采购信息记录维护接口分享
  • 网站搭建装修风格大全2021新款简约
  • Mysql初阶第八讲:Mysql表的内外连接
  • SpringCloud 入门 - Gateway 网关与 OpenFeign 服务调用
  • uniapp 选择城市(城市列表选择)
  • AR小白入门指南:从零开始开发增强现实应用
  • 02_k8s资源清单
  • 2025年渗透测试面试题总结-109(题目+回答)
  • uniapp配置自动导入uni生命周期等方法
  • flink的Standalone-HA模式安装
  • Flink时态表关联:实现数据“时间旅行”的终极方案
  • 做哪类英文网站赚钱wordpress 页面 列表
  • nginx + spring cloud + redis + mysql + ELFK 部署
  • 【黑马点评 - 实战篇01】Redis项目实战(Windows安装Redis6.2.6 + 发送验证码 + 短信验证码登录注册 + 拦截器链 - 登录校验)
  • 汕头市通信建设管理局网站二网站手
  • FreeRTOS小记
  • 数据结构实战:顺序表全解析 - 从零实现到性能分析
  • 【C++进阶】继承上 概念及其定义 赋值兼容转换 子类默认成员函数的详解分析
  • 华为matebook16s 2022禁用触摸板和触摸屏操作
  • GridRow 和 Column 有啥区别
  • 030159网站建设与维护中国科技成就素材
  • Echarts 5.6.0 Grid 坐标系中 Y 轴可视化的优化之路
  • Java 线程池如何知道一个线程的任务已经执行完成
  • JVM字节码与类的加载(一):类的加载过程详解
  • 强军网网站建设网站需要备案才能建设吗
  • 耄大厨——AI厨师智能体(3-工具调用)