数据结构初阶题解
- 1.移除元素
- 2.合并两个有序数组
- 3.移除链表元素
- 4.反转链表
- 5.合并两个有序链表
- 6.链表的中间结点
- 7.环形链表的约瑟夫问题
- 8.分割链表
- 有感:其实我早就死了,死在破碎的三观里;死在飘渺的理想里;死在幻想的感情里;死在虚无的回忆里。但我好像我还活着,活在生活的压力里;活在社会的角落里;活在亲人的期盼里;活在儿时的梦里。
最近笔者在初学数据结构时遇到几道题目,做完后有些吃力,很多经验还没有总结,笔者打算针对这几道题目进行系统性的总结。由于笔者是个菜鸟,思路还没有那么熟练清晰,后续还会加以完善,大佬轻喷。另外欢迎各位大佬指出不足。
1.移除元素
题目来源:https://leetcode.cn/problems/remove-element/description/
题目原文:
题意解析:
给定一个数组和一个数值,要求删除数组中等于该数值的元素,并返回删除后数组的新长度。
注意:不能创建新数组来操作,以及删除后数组里的元素可以任意位置排序。
个人思路:
首先最开始的思路是创建一个新数组,然后遍历原数组,将不为删除的值的元素放到新数组中。但是题目要求不能创建新数组,所以这种思路在本题不适用。
第二种思路是在原数组中进行操作遍历删除。此方法也叫双指针法。
(1)创建两个指针src、dist,它们都指向第一个元素
(2)src遍历原数组,若元素 = 删除的值,src++
若元素 != 删除的值,nums[dist] = nums[src],dist++,src++
(3)src遍历完数组,循环结束,此时数组为新的数组。
还需要注意一点的是本题目要求返回新数组的长度,需要返回一个整形,所以我们使用数组下标的形式来写代码,本质上无疑异,方便做题。
个人代码:
int removeElement(int* nums, int numsSize, int val)
{
int src,dst;
src = dst = 0;//初始化第一个元素下标为0
while(src < numsSize)
{
if(nums[src] == val)
{
src++;
}
else
{
nums[dst] = nums[src];
src++;
dst++;
}
}
return dst;//此时下标dst就是新数组的长度
}
2.合并两个有序数组
题目来源:https://leetcode.cn/problems/merge-sorted-array/description/
题目原文:
题意解析:
给定两个递增的数组,然后合并两个数组,要求新数组中元素也是递增的。
个人思路:
第一开始的思路是先把两个元素放到一个数组中去,然后在一个数组中进行冒泡排序。
但此方法代码量大,程序效率低。
第二种思路是比大小,两个数组中的元素比大小,从后往前比大小,元素大的往后放。在这个方法中不要遗漏了关键点,就是比大小的时候,两个数组都是递增的。
个人代码:
int l1 = m - 1;
int l2 = n - 1;
int l3 = m + n - 1;
while(l1 >= 0 && l2 >= 0)
{
if(nums1[l1] > nums2[l2])
{
nums1[l3--] = nums1[l1--];
}
else
{
nums1[l3--] = nums2[l2--];
}
}
while(l2 >= 0)//结束条件是第二个数组中的元素全都遍历到第一个元素中去
{
nums1[l3--] = nums2[l2--];
}
3.移除链表元素
题目来源:https://leetcode.cn/problems/remove-linked-list-elements/description/
题目原文:
题意解析:
给定一个链表和节点的值,删除给定节点,返回新链表。
个人思路:
第一种思路是创建新链表,将不为给定删除的节点放到新链表中。
第二种思路是在原链表中进行删除操作。
特别注意链表可以是空链表,这点需要特别思考。
个人代码:
1.创建新链表
struct ListNode* removeElements(struct ListNode* head, int val)
{
typedef struct ListNode ListNode;
ListNode* newhead,*newtail;
newhead = newtail = NULL;
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;
2.遍历原链表,删除指定位置链表
struct ListNode* removeElements(struct ListNode* head, int val)
{
typedef struct ListNode ListNode;
while ( NULL != head & head->val == val) //判断头节点是否为空节点
{
head = head->next;
}//此时头节点不可能是val了
if (NULL == head) {
return head;
}
ListNode* pcur = head->next;
ListNode* prev = head;
while(pcur)
{
if(pcur->val == val)
{
ListNode* Next = pcur->next;
prev->next = Next;
pcur = Next;
}
else
{
prev = pcur;
pcur = pcur->next;
}
}
return head;
}
4.反转链表
题目来源:https://leetcode.cn/problems/reverse-linked-list/description/
题目原文:
提示:
链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
题意解析:
给定一个链表,现将原链表的头尾顺序倒置,然后输出。
个人思路:
第一个思路是创建新链表,将原链表的节点头插到新链表中,这样就实现了
第二个思路是使用三个指针,完成链表翻转,此方法难以想到,需要多加注意。
个人代码:
2.三个指针,完成链表翻转
if(head == NULL)
{
return head;
}
ListNode* n1,*n2,*n3;
n1 = NULL;
n2 = head;
n3 = head->next;
while(n2)
{
n2->next = n1;//改变n2的指向
n1 = n2;
n2 = n3;
if(n3)
{
n3 = n3->next;
}
}
return n1;
1.创建新链表,进行头插操作
struct ListNode* reverseList(struct ListNode* head)
{
typedef struct ListNode ListNode;
//1.遍历头插
ListNode* pcur = head;
ListNode* newhead = NULL;
ListNode* newtail = NULL;
while(pcur)
{
if(newhead == NULL)
{
newhead = newtail = pcur;
pcur = pcur->next;
}
else
{
ListNode* next = pcur->next;
pcur->next = newhead;
newhead = pcur;
pcur = next;
}
}
if(newtail)
{
newtail->next = NULL;
}
return newhead;
5.合并两个有序链表
题目来源:https://leetcode.cn/problems/merge-two-sorted-lists/description/
题目原文:
提示:
两个链表的节点数目范围是 [0, 50]
-100 <= Node.val <= 100
l1 和 l2 均按 非递减顺序 排列
题意解析:
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
个人思路:
创建新的空的链表,遍历原链表,将节点值小的拿到新链表中进行尾插操作。遍历的结果只有两者情况:第一个链表为空或者第二个链表为空。
个人代码:
struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2)
{
typedef struct ListNode ListNode;
ListNode* l1 = list1;
ListNode* l2 = list2;
if(list1 == NULL)//判断原链表是否为空
{
return list2;
}
if(list2 == NULL)
{
return list1;
}
ListNode* newhead,*newtail;//创建新链表
newhead = newtail = (ListNode*)malloc(sizeof(ListNode));//创建哨兵位,不必再判断新链表是否为空
while(l1 && l2)
{
if(l1->val < l2->val)
{
newtail->next = l1;
newtail = newtail->next;
l1 = l1->next;
}
else
{
newtail->next = l2;
newtail = newtail->next;
l2 = l2->next;
}
}
if(l1)
{
newtail->next = l1;
}
if(l2)
{
newtail->next = l2;
}
ListNode* ret = newhead->next;
free(newhead);
newhead = NULL;
return ret;
}
6.链表的中间结点
题目来源:https://leetcode.cn/problems/middle-of-the-linked-list/description/
题目原文:
题意解析:
给定单链表的头结点 head ,请你找出并返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
个人思路:
快慢指针法:slow每次走一步,fast每次走两步
(1)若节点为奇数个,循环条件是fast->next== NULL,此时slow指向的是中间节点
(2)若节点为偶数个,循环条件是fast==NULL, 此时slow指向的是中间节点
个人代码:
struct ListNode* middleNode(struct ListNode* head)
{
typedef struct ListNode ListNode;
ListNode* slow = head;
ListNode* fast = head;
while(fast && fast->next)
{
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
7.环形链表的约瑟夫问题
题目来源:https://www.nowcoder.com/practice/41c399fdb6004b31a6cbb047c641ed8a
题目原文:
题意解析:
编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。
下一个人继续从 1 开始报数。n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?
个人思路:
创建环形链表,遍历链表,计数计到m的节点销毁。直到链表只剩一个节点时退出循环
个人代码:
typedef struct ListNode ListNode;
ListNode* buynode(int x)
{
ListNode* node = (ListNode*)malloc(sizeof(ListNode));
if(node == NULL)
{
exit(1);
}
node->val = x;
node->next = NULL;
return node;
}
ListNode* createcic(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 ptail;
}
int ysf(int n, int m )
{
//1.创建带环链表
ListNode* prev = createcic(n);
//2.遍历链表,删除节点
ListNode* pcur = prev->next;
int count = 1;
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;
}
8.分割链表
题目来源:https://leetcode.cn/problems/partition-list-lcci/description/
题目原文:
题意解析:
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。你不需要 保留 每个分区中各节点的初始相对位置。
个人思路:
第一种思路在原链表上进行修改,遍历原链表,若节点的值大于或等于x,尾插再原链表后,然后删除旧的链表。
此方法需要定义多个指针变量,删除链表需要定义prev指针,尾插需要定义新的尾部变量。
第二种思路是创建大小链表,分别遍历大、小链表,然后将小链表的尾节点和大节点的第一个节点首位相连。
个人代码:
struct ListNode* partition(struct ListNode* head, int x)
{
typedef struct ListNode ListNode;
if(head == NULL)
{
return head;
}
//创建两个带头链表
ListNode* lesshead,*lesstail;
ListNode* bighead,*bigtail;
lesshead = lesstail = (ListNode*)malloc(sizeof(ListNode));
bighead = bigtail = (ListNode*)malloc(sizeof(ListNode));
//遍历原链表,将原链表中的节点尾插到大小链表中
ListNode* pcur = head;
while(pcur)
{
if(pcur->val < x)
{
//尾插到小链表中
lesstail->next = pcur;
lesstail = lesstail->next;
}
else
{
//尾插到大链表中
bigtail->next = pcur;
bigtail = bigtail->next;
}
pcur = pcur->next;
}
//将bigtail->next初始化的同时还置为NULL,防止死循环
bigtail->next = NULL;
//首位相连
lesstail->next = bighead->next;
ListNode* ret = lesshead->next;
free(lesshead);
free(bighead);
lesshead = bighead = NULL;
return ret;
}
完。。。