目录
一.顺序表经典面试题
1.移除元素
2.删除有序数组中的重复项
3.合并两个有序数组
二.链表经典面试题
1.移除链表元素
2.反转一个单链表
3.链表的中间节点
4.链表中倒数第K个节点
5.合并两个有序链表
6.链表分割
7.链表的回文结构
8.相交链表
9.环形链表
10.环形链表||
一.顺序表经典面试题
1.移除元素
oj链接力扣
题目描述:
思路:如果可以开辟额外的数组空间 的话,那么我们可以将不是val值的元素依次赋值给新数组即可,但是题目要求不能使用额外的数组空间,我们只能将不是val值的元素赋值给原数组,将之前的值覆盖即可
代码:
- int removeElement(int* nums, int numsSize, int val){
- int arr[101];
- int i=0;//循环变量,以遍历数组
- int dst=0;//当前原数组被覆盖的值的数量
- for(i=0;i<numsSize;i++)
- {
- if(nums[i]!=val)//判断是否数值等于val
- {
- nums[dst]=nums[i];//将不相等的元素赋值给原数组
- dst++;
- }
- }
- return dst;
- }
2.删除有序数组中的重复项
OJ链接力扣
题目描述:
思路:我们可以创建一个查找函数,参数是数组和数组大小以及相应的值,对原数组遍历时,调用查找函数判断当前元素是否在前面出现过,如果出现过则跳过该元素,如果没有出现过则赋值给原数组
代码;
- int Seach(int* nums, int numsSize,int x)//查找函数
- {
- int i=0;
- for(i=0;i<numsSize;i++)
- {
- if(nums[i]==x)
- return i;//出现过则返回该元素在数组中下标
- }
- return -1;//没有出现过则返回-1
- }
- int removeDuplicates(int* nums, int numsSize){
- int i=0;
- int dst=1;
- for(i=1;i<numsSize;i++)
- {
- int ret=Seach(nums,i,nums[i]);//调用查找函数判断是否是重复元素
- if(ret==-1)//返回-1说明不是重复元素
- {
- nums[dst]=nums[i];赋值给原数组
- dst++;
- }
- }
- return dst;
- }
3.合并两个有序数组
OJ链接力扣
题目描述:
思路:这题可以充分利用双指针的思想,一个指针指向数组1,另一个指针指向数组2,由于此题将数组1的空间设置为两个数组元素数量和的大小,因此最好不要开辟额外的数组空间,两个指针从两个数组末尾开始遍历,将两个数组的元素相比较,比较大的元素则从数组最后一个位置往前赋值,等某个数组遍历完之后,判断数组2的指针是否大于等于0,是的话说明是数组1遍历结束,将数组2剩下的元素依次赋值给原数组。如果是数组1没有遍历完,则不用动它,因为这些元素本身就在数组1前面的位置有序排列
代码:
- void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
- int ret1=m-1;//数组1的下标,从末尾开始遍历
- int ret2=n-1;//数组2的下标,从末尾开始遍历
- int j=m+n-1;//总空间的下标,从末尾开始
- while(ret1>=0&&ret2>=0)//一个数组遍历完则结束
- {
- if(nums1[ret1]>nums2[ret2])//比较大的元素从总空间最后面依次赋值
- {
- nums1[j]=nums1[ret1];
- ret1--;
- j--;
- }
- else if(nums1[ret1]==nums2[ret2])
- {
- nums1[j]=nums2[ret2];
- ret2--;
- j--;
- }
- else
- {
- nums1[j]=nums2[ret2];
- ret2--;
- j--;
- }
- }
- while(ret2>=0)//数组2没有遍历完,将剩余的元素赋值给总空间
- {
- nums1[j]=nums2[ret2];
- ret2--;
- j--;
- }
- }
二.链表经典面试题
1.移除链表元素
oj链接力扣
题目描述:
思路:构建一个带头结点的空的新单链表,以方便尾插,新建一个cur指针依次遍历原链表,判断各个节点的值是否为要删除的值,如果不是,则将该节点尾插到新链表,指针往后走,如果是,定义一个next指针指向下一个节点,将当前节点删除,然后cur指针等于next,即指针继续往后走,直到遍历指针cur指针为空遍历则结束,最后将新链表的头节点删除,头指针指向第一个节点,最后返回新链表的头指针即可
代码:
- /**
- * Definition for singly-linked list.
- * struct ListNode {
- * int val;
- * struct ListNode *next;
- * };
- */
-
-
- struct ListNode* removeElements(struct ListNode* head, int val)
- {
- struct ListNode* cur=head;//定义遍历指针
- struct ListNode* guard,*tail;//定义带头节点的新链表
- guard=tail=(struct ListNode*)malloc(sizeof(struct ListNode));//开辟头结点
- while(cur)
- {
- if(cur->val!=val)//如果不是要删除的值,则尾插到新链表
- {
- tail->next=cur;
- tail=tail->next;
- cur=cur->next;
- }
- else//是则删除
- {
- struct ListNode *next=cur->next;
- free(cur);
- cur=next;
- }
- }
- tail->next=NULL;//将尾及诶点的next置空
- struct ListNode *newnode=guard->next;//将头指针指向第一个节点
- free(guard);//删除头结点
- return newnode;
- }
2.反转一个单链表
OJ链接力扣
题目描述:
思路:首先判断原链表是否为空,为空则返回空,不为空则定义两个遍历指针遍历原链表,再定义一个空的新链表,依次遍历原链表的每一个节点,然后将每个节点头插到新链表,即形成反转效果,最后将新链表的头指针返回即可
代码:
- /**
- * Definition for singly-linked list.
- * struct ListNode {
- * int val;
- * struct ListNode *next;
- * };
- */
-
-
- struct ListNode* reverseList(struct ListNode* head){
- struct ListNode* newnode=NULL;//新建一个空链表
- struct ListNode* cur=head;//新建两个遍历指针
- struct ListNode* next=head;
- if(cur==NULL)//原链表为空则返回空
- return head;
- else
- {
- while(cur)//遍历原链表的节点,将每个节点头插到新链表
- {
- next=next->next;
-
- cur->next=newnode;
- newnode=cur;
- cur=next;
- }
- }
- return newnode;
- }
3.链表的中间节点
OJ链接力扣
题目描述:
思路1:首先判断原链表是否为空,为空则返回空,定义一个遍历指针遍历原链表,统计出原链表节点的数量num,然后再让遍历指针从头开始走num/2(无论原链表节点数量为奇数还是偶数都可)个节点,将此节点返回即可
思路2:首先判断原链表是否为空,为空则返回空,定义两个快慢指针low和fast,两个指针都从头开始走,慢指针一次走一步,快指针一次走两步,快指针走的路程是慢指针的2倍,当快指针走到链表末尾时,慢指针刚好走到链表中间,此时返回慢指针指向的节点即可
代码:
- 思路1:
- struct ListNode* middleNode(struct ListNode* head){
- struct ListNode* cur=head;//定义一个遍历指针
- int num=0;//用来统计链表节点的数量
- while(cur)//遍历统计数量
- {
- num++;
- cur=cur->next;
- }
- cur=head;
- int i=0;
- for(i=0;i<num/2;i++)//让指针走到中间节点
- {
- cur=cur->next;
- }
- return cur;
- }
-
- 思路2:
- struct ListNode* middleNode(struct ListNode* head){
- struct ListNode*low=head;//定义快慢指针
- struct ListNode*fast=head;
- if(head==NULL)//原链表为空则返回空
- return NULL;
- while(fast&&fast->next)//节点数量为奇数和偶数的两种结束条件
- {
- low=low->next;//慢指针一次走一步
- fast=fast->next->next;//快指针一次走两步
- }
- return low;
- }
4.链表中倒数第K个节点
OJ链接链表中倒数第k个结点_牛客题霸_牛客网
题目描述:
思路1:首先判断链表是否为空,为空则返回空,定义一个遍历指针统计链表中节点的数量,当k小于等于或者大于节点的数量时返回空,然后让遍历指针从头开始走n-k步,此时指向的便是倒数第K个节点,返回即可
思路2:首先判断链表是否为空,为空则返回空,定义一个遍历指针统计链表中节点的数量,当k小于等于或者大于节点的数量时返回空,定义两个指针p1和p2都指向头结点,两个节点每次都只走一步,让p1指针先走K-1步,之后两个指针一起走,当p1指针走到最后一个节点时,p2此时刚好指向倒数第K个节点,返回p2即可
代码:
-
- 思路1:
- struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
- int i=0;
- struct ListNode*cur=pListHead;//遍历指针统计节点数量
- int n=0;
- while(cur)
- {
- n++;
- cur=cur->next;
- }
- cur=pListHead;
- if(k>0&&k<=n)//判断K的合法性,合法则让遍历指针走n-k步
- {
- for(i=0;i<n-k;i++)//让遍历指针走n-k步
- {
- cur=cur->next;
- }
- return cur;
- }
- else//不合法返回空
- {
- return NULL;
- }
- }
- 思路2:
- struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
- if(pListHead==NULL)//判断链表是否为空,为空则返回为空
- return NULL;
- struct ListNode*cur=pListHead;//定义三个遍历指针
- struct ListNode*p1=pListHead;
- struct ListNode*p2=pListHead;
- int num=0;//记录节点数量
- while(cur)//遍历统计节点数量
- {
- num++;
- cur=cur->next;
- }
- if(k<=0||k>num)//判断k的合法性
- return NULL;
- k=k-1;
- while(k--)//让p1先走k-1步
- {
- p1=p1->next;
- }
- while(p1->next)//之后两个指针一起走
- {
- p1=p1->next;
- p2=p2->next;
- }
- return p2;
- }
5.合并两个有序链表
OJ链接力扣
题目描述:
思路:定义一个带头结点的新链表,定义两个指针分别指向两个链表,将两个链表的节点的值进行比较,较小的值则尾插到新链表中,然后指向该值地指针继续往后走,较大的值地指针则不动,当某个链表遍历结束后则结束比较,然后检查哪个链表没有遍历完,没有遍历完的元素一定是比较大的元素,直接尾插到新链表中即可,最后将新链表的头指针指向第一个节点,删除头结点返回头指针即可
代码:
- struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
- struct ListNode *cur1=list1;//两个指针分别指向两个链表
- struct ListNode *cur2=list2;
- struct ListNode *guard,*tail;//新建一个带头结点的单链表
- guard=tail=(struct ListNode*)malloc(sizeof(struct ListNode));//开辟头结点
- while(cur1&&cur2)//遍历两个链表,当有一个链表遍历完则结束
- {
- if(cur1->val<cur2->val)//节点的值比较,较小的值尾插到新链表
- {
- tail->next=cur1;
- tail=tail->next;
- cur1=cur1->next;
- }
- else
- {
- tail->next=cur2;
- tail=tail->next;
- cur2=cur2->next;
- }
- }
- //检查哪个链表没有遍历完,没有遍历完的链表的节点则尾插到新链表中
- if(cur1==NULL)
- {
- while(cur2)
- {
- tail->next=cur2;
- tail=tail->next;
- cur2=cur2->next;
- }
- tail->next=NULL;
- }
- else if(cur2==NULL)
- {
- while(cur1)
- {
- tail->next=cur1;
- tail=tail->next;
- cur1=cur1->next;
- }
- tail->next=NULL;
- }
- else{
- return NULL;
- }
- struct ListNode*newnode=guard->next;//头指针指向第一个节点
- free(guard);//删除头结点
- return newnode;
- }
6.链表分割
OJ链接链表分割_牛客题霸_牛客网
题目描述:
思路:先判断原链表是否为空,为空则返回空,定义一个遍历指针遍历原链表,定义两个带头结点的空链表,将原链表值小于x的节点尾插到一个新链表中,将值大于x的节点尾插到新一个新链表中,将两个新链表的头节点删除,头指针指向第一个节点,然后将两个新链表链接在一起即可,最后返回链接后链表的头指针
代码:
- ListNode* partition(ListNode* pHead, int x) {
- // write code here
- if(pHead==NULL)//判断原链表是否为空
- return NULL;
- ListNode* cur=pHead;//定义遍历指针
- ListNode* guard1,*tail1,*guard2,*tail2;//定义两个新链表
- //开辟头结点
- guard1=tail1=(ListNode*)malloc(sizeof(ListNode));
- guard1->next=NULL;
- guard2=tail2=(ListNode*)malloc(sizeof(ListNode));
- guard2->next=NULL;
- //遍历原链表,值小于x的节点尾插到一个新链表中,大于x的节点尾插到另一个新链表中
- while(cur)
- {
- if(cur->val<x)
- {
- tail1->next=cur;
- tail1=tail1->next;
- cur=cur->next;
- }
- else
- {
- tail2->next=cur;
- tail2=tail2->next;
- cur=cur->next;
- }
- }
- tail1->next=guard2->next;//两个新链表链接在一起
- tail2->next=NULL;//链表的最后一个节点next置空
- ListNode* newnode1=guard1->next;//头指针指向第一个节点
- ListNode* newnode2=guard2->next;
- //删除头结点
- free(guard1);
- free(guard2);
- return newnode1;
- }
- }
7.链表的回文结构
OJ链接链表的回文结构_牛客题霸_牛客网
题目描述:
思路:这道题是前面几道题的综合,先判断原链表是否为空,为空则返回,调用第3题的函数找到原链表的中间链表,然后再调用第2题的函数将原链表后半部分反转,然后原链表和后部分反转链表遍历每个节点相比较,如果有节点不相等则返回false,相等则继续往下遍历,直到遍历结束然后返回true
代码:
- //返回中间节点
- struct ListNode* middleNode(struct ListNode* head){
- struct ListNode* cur=head;
- int num=0;
- while(cur)
- {
- num++;
- cur=cur->next;
- }
- cur=head;
- int i=0;
- for(i=0;i<num/2;i++)
- {
- cur=cur->next;
- }
- return cur;
- }
- //反转链表
- struct ListNode* reverseList(struct ListNode* head){
- struct ListNode* newnode=NULL;
- struct ListNode* cur=head;
- struct ListNode* next=head;
- if(cur==NULL)
- return head;
- else
- {
- while(cur)
- {
- next=next->next;
- cur->next=newnode;
- newnode=cur;
- cur=next;
- }
- }
- return newnode;
- }
- bool chkPalindrome(ListNode* A) {
- // write code here
- ListNode *mid=middleNode(A);//返回原链表的中间节点
- ListNode *rhead=reverseList(mid);//翻转原链表的后半部分
- while(A&&rhead)//遍历两个链表,比较节点的值
- {
- if(A->val!=rhead->val)
- return false;
- A=A->next;
- rhead=rhead->next;
- }
- return true;
- }
- }
8.相交链表
OJ链接力扣
题目描述:
思路:定义两个遍历指针,依次遍历两个链表统计出两个链表节点的个数,计算出两个链表节点数量的差值,让节点数量多的链表遍历走差值的步数,然后两个链表一起遍历,如果两个指针指向的节点相同,则该节点则为相交节点返回该节点,否则两个指针继续往后遍历直到遍历结束,遍历结束依然没有找到相交节点则返回空
代码:
- struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
- //定义两个遍历指针
- struct ListNode *cur1=headA;
- struct ListNode *cur2=headB;
- //分别统计两个链表节点数量
- int num1=0;
- int num2=0;
- //遍历统计两个链表节点数量
- while(cur1)
- {
- num1++;
- cur1=cur1->next;
- }
- while(cur2)
- {
- num2++;
- cur2=cur2->next;
- }
- cur1=headA;
- cur2=headB;
- //让节点数量多的链表遍历指针先走差值的步数
- if(num1>num2)
- {
- int k=num1-num2;
- while(k--)
- cur1=cur1->next;
- }
- else if(num1<num2)
- {
- int k=num2-num1;
- while(k--)
- cur2=cur2->next;
- }
- //两个链表一起遍历
- while(cur1)
- {
- //两个链表的节点相比较,相同则返回
- if(cur1==cur2)
- return cur1;
- if(cur1->next==cur2->next)
- return cur1->next;
- //不同则继续遍历
- else
- {
- cur1=cur1->next;
- cur2=cur2->next;
- }
- }
- return NULL;
- }
9.环形链表
OJ链接力扣
题目描述:
思路:这道题可以充分利用快慢指针的思想,定义一个慢指针low和快指针fast,分别一次走一步和两步,链表分为环区和直线区,当fast指针走到直线区末尾即环区入口时,low指针刚好走到直线区的中间,当慢指针入环时,快指针已经在环区走了若干圈,当两个指针一起走时,每走一次两个指针的距离便减小1,若干次后两个指针必会相遇,则证明该链表是环形链表,如果追不上或者指针指向空则证明该链表不是环形链表
代码:
- bool hasCycle(struct ListNode *head)
- {
- if(head==NULL)//判断原链表是否为空,为空则返回空
- return false;
- //定义两个快慢指针
- struct ListNode* low=head;
- struct ListNode* fast=head;
- low=low->next;//慢指针每次走一步
- fast=fast->next->next;//快指针每次走两步
- while(low!=fast&&fast!=NULL)//两个指针一直走,直到相遇或者为空结束
- {
- low=low->next;
- fast=fast->next->next;
- }
- if(low==fast)//相遇则返回真
- return true;
- if(fast==NULL)//为空则返回假
- return false;
- }
10.环形链表||
oj链接力扣
题目描述:
思路(结论):先判断链表是否为空或者只有一个节点,是则返回NULL,在第9题快慢指针相遇的基础上,我们让一个指针从头开始走,另一个指针从相遇点开始走,每个指针每次走一步,两个指针相遇的地方便是入环的第一个节点
证明:假设直线区长度为L,环的长度为C,快慢指针相遇点至入环口的距离为N,第9题的快指针走的路程是慢指针的两倍
慢指针走的路程:L+N
快指针走的路程:假设快指针已经在环走了K圈,则路程为L+KC+N
则由(L+N)*2=L+KC+N,化解得L=KC-N=(K-1)C+C-N,(K-1)C可以认为指针走了K圈又回到原点,故得证L=C-N
代码:
- struct ListNode *detectCycle(struct ListNode *head) {
- if(head==NULL||head->next==NULL)//判断链表是否为空或者只有一个节点
- return NULL;
- //定义两个快慢指针
- struct ListNode *low=head;
- struct ListNode *fast=head;
- while(fast&&fast->next)//快慢指针遍历
- {
-
- low=low->next;
- fast=fast->next->next;
- //如果快慢指针相遇则让一个指针从头开始走,另一个指针从相遇点开始走
- if(low==fast)
- {
- struct ListNode *meet=low;
- while(head!=meet)
- {
- head=head->next;
- meet=meet->next;
- }
- return meet;
- }
- }
- //上面的循环结束到这里说明链表没有环,返回空
- return NULL;
- }
好啦,顺序表和链表经典面试题就先学习到这里,如果文章对您有帮助,欢迎一键三连~
最后附上寄语:种一棵树最好的时间是十年前,其次是现在