深圳幻海软件技术有限公司 欢迎您!

顺序表和链表经典面试题

2023-04-17

目录一.顺序表经典面试题1.移除元素 2.删除有序数组中的重复项3.合并两个有序数组二.链表经典面试题1.移除链表元素2.反转一个单链表3.链表的中间节点4.链表中倒数第K个节点5.合并两个有序链表6.链表分割7.链表的回文结构8.相交链表9.环形链表10.环形链表||一.顺序表经典面试题

目录

一.顺序表经典面试题

1.移除元素 

2.删除有序数组中的重复项

3.合并两个有序数组

二.链表经典面试题

1.移除链表元素

2.反转一个单链表

3.链表的中间节点

4.链表中倒数第K个节点

5.合并两个有序链表

6.链表分割

7.链表的回文结构

8.相交链表

9.环形链表

10.环形链表||


一.顺序表经典面试题

1.移除元素 

oj链接力扣

题目描述:

思路:如果可以开辟额外的数组空间 的话,那么我们可以将不是val值的元素依次赋值给新数组即可,但是题目要求不能使用额外的数组空间,我们只能将不是val值的元素赋值给原数组,将之前的值覆盖即可

代码:

  1. int removeElement(int* nums, int numsSize, int val){
  2. int arr[101];
  3. int i=0;//循环变量,以遍历数组
  4. int dst=0;//当前原数组被覆盖的值的数量
  5. for(i=0;i<numsSize;i++)
  6. {
  7. if(nums[i]!=val)//判断是否数值等于val
  8. {
  9. nums[dst]=nums[i];//将不相等的元素赋值给原数组
  10. dst++;
  11. }
  12. }
  13. return dst;
  14. }

2.删除有序数组中的重复项

OJ链接力扣

题目描述:

 思路:我们可以创建一个查找函数,参数是数组和数组大小以及相应的值,对原数组遍历时,调用查找函数判断当前元素是否在前面出现过,如果出现过则跳过该元素,如果没有出现过则赋值给原数组

代码;

  1. int Seach(int* nums, int numsSize,int x)//查找函数
  2. {
  3. int i=0;
  4. for(i=0;i<numsSize;i++)
  5. {
  6. if(nums[i]==x)
  7. return i;//出现过则返回该元素在数组中下标
  8. }
  9. return -1;//没有出现过则返回-1
  10. }
  11. int removeDuplicates(int* nums, int numsSize){
  12. int i=0;
  13. int dst=1;
  14. for(i=1;i<numsSize;i++)
  15. {
  16. int ret=Seach(nums,i,nums[i]);//调用查找函数判断是否是重复元素
  17. if(ret==-1)//返回-1说明不是重复元素
  18. {
  19. nums[dst]=nums[i];赋值给原数组
  20. dst++;
  21. }
  22. }
  23. return dst;
  24. }

3.合并两个有序数组

OJ链接力扣

题目描述:

 思路:这题可以充分利用双指针的思想,一个指针指向数组1,另一个指针指向数组2,由于此题将数组1的空间设置为两个数组元素数量和的大小,因此最好不要开辟额外的数组空间,两个指针从两个数组末尾开始遍历,将两个数组的元素相比较,比较大的元素则从数组最后一个位置往前赋值,等某个数组遍历完之后,判断数组2的指针是否大于等于0,是的话说明是数组1遍历结束,将数组2剩下的元素依次赋值给原数组。如果是数组1没有遍历完,则不用动它,因为这些元素本身就在数组1前面的位置有序排列

代码:

  1. void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
  2. int ret1=m-1;//数组1的下标,从末尾开始遍历
  3. int ret2=n-1;//数组2的下标,从末尾开始遍历
  4. int j=m+n-1;//总空间的下标,从末尾开始
  5. while(ret1>=0&&ret2>=0)//一个数组遍历完则结束
  6. {
  7. if(nums1[ret1]>nums2[ret2])//比较大的元素从总空间最后面依次赋值
  8. {
  9. nums1[j]=nums1[ret1];
  10. ret1--;
  11. j--;
  12. }
  13. else if(nums1[ret1]==nums2[ret2])
  14. {
  15. nums1[j]=nums2[ret2];
  16. ret2--;
  17. j--;
  18. }
  19. else
  20. {
  21. nums1[j]=nums2[ret2];
  22. ret2--;
  23. j--;
  24. }
  25. }
  26. while(ret2>=0)//数组2没有遍历完,将剩余的元素赋值给总空间
  27. {
  28. nums1[j]=nums2[ret2];
  29. ret2--;
  30. j--;
  31. }
  32. }

二.链表经典面试题

1.移除链表元素

oj链接力扣

题目描述:

 思路:构建一个带头结点的空的新单链表,以方便尾插,新建一个cur指针依次遍历原链表,判断各个节点的值是否为要删除的值,如果不是,则将该节点尾插到新链表,指针往后走,如果是,定义一个next指针指向下一个节点,将当前节点删除,然后cur指针等于next,即指针继续往后走,直到遍历指针cur指针为空遍历则结束,最后将新链表的头节点删除,头指针指向第一个节点,最后返回新链表的头指针即可

代码:

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * struct ListNode *next;
  6. * };
  7. */
  8. struct ListNode* removeElements(struct ListNode* head, int val)
  9. {
  10. struct ListNode* cur=head;//定义遍历指针
  11. struct ListNode* guard,*tail;//定义带头节点的新链表
  12. guard=tail=(struct ListNode*)malloc(sizeof(struct ListNode));//开辟头结点
  13. while(cur)
  14. {
  15. if(cur->val!=val)//如果不是要删除的值,则尾插到新链表
  16. {
  17. tail->next=cur;
  18. tail=tail->next;
  19. cur=cur->next;
  20. }
  21. else//是则删除
  22. {
  23. struct ListNode *next=cur->next;
  24. free(cur);
  25. cur=next;
  26. }
  27. }
  28. tail->next=NULL;//将尾及诶点的next置空
  29. struct ListNode *newnode=guard->next;//将头指针指向第一个节点
  30. free(guard);//删除头结点
  31. return newnode;
  32. }

2.反转一个单链表

OJ链接力扣

题目描述:

 思路:首先判断原链表是否为空,为空则返回空,不为空则定义两个遍历指针遍历原链表,再定义一个空的新链表,依次遍历原链表的每一个节点,然后将每个节点头插到新链表,即形成反转效果,最后将新链表的头指针返回即可

代码:

  1. /**
  2. * Definition for singly-linked list.
  3. * struct ListNode {
  4. * int val;
  5. * struct ListNode *next;
  6. * };
  7. */
  8. struct ListNode* reverseList(struct ListNode* head){
  9. struct ListNode* newnode=NULL;//新建一个空链表
  10. struct ListNode* cur=head;//新建两个遍历指针
  11. struct ListNode* next=head;
  12. if(cur==NULL)//原链表为空则返回空
  13. return head;
  14. else
  15. {
  16. while(cur)//遍历原链表的节点,将每个节点头插到新链表
  17. {
  18. next=next->next;
  19. cur->next=newnode;
  20. newnode=cur;
  21. cur=next;
  22. }
  23. }
  24. return newnode;
  25. }

3.链表的中间节点

OJ链接力扣

题目描述:

思路1:首先判断原链表是否为空,为空则返回空,定义一个遍历指针遍历原链表,统计出原链表节点的数量num,然后再让遍历指针从头开始走num/2(无论原链表节点数量为奇数还是偶数都可)个节点,将此节点返回即可

思路2:首先判断原链表是否为空,为空则返回空,定义两个快慢指针low和fast,两个指针都从头开始走,慢指针一次走一步,快指针一次走两步,快指针走的路程是慢指针的2倍,当快指针走到链表末尾时,慢指针刚好走到链表中间,此时返回慢指针指向的节点即可

代码:

  1. 思路1
  2. struct ListNode* middleNode(struct ListNode* head){
  3. struct ListNode* cur=head;//定义一个遍历指针
  4. int num=0;//用来统计链表节点的数量
  5. while(cur)//遍历统计数量
  6. {
  7. num++;
  8. cur=cur->next;
  9. }
  10. cur=head;
  11. int i=0;
  12. for(i=0;i<num/2;i++)//让指针走到中间节点
  13. {
  14. cur=cur->next;
  15. }
  16. return cur;
  17. }
  18. 思路2
  19. struct ListNode* middleNode(struct ListNode* head){
  20. struct ListNode*low=head;//定义快慢指针
  21. struct ListNode*fast=head;
  22. if(head==NULL)//原链表为空则返回空
  23. return NULL;
  24. while(fast&&fast->next)//节点数量为奇数和偶数的两种结束条件
  25. {
  26. low=low->next;//慢指针一次走一步
  27. fast=fast->next->next;//快指针一次走两步
  28. }
  29. return low;
  30. }

4.链表中倒数第K个节点

OJ链接链表中倒数第k个结点_牛客题霸_牛客网

题目描述:

思路1:首先判断链表是否为空,为空则返回空,定义一个遍历指针统计链表中节点的数量,当k小于等于或者大于节点的数量时返回空,然后让遍历指针从头开始走n-k步,此时指向的便是倒数第K个节点,返回即可

思路2:首先判断链表是否为空,为空则返回空,定义一个遍历指针统计链表中节点的数量,当k小于等于或者大于节点的数量时返回空,定义两个指针p1和p2都指向头结点,两个节点每次都只走一步,让p1指针先走K-1步,之后两个指针一起走,当p1指针走到最后一个节点时,p2此时刚好指向倒数第K个节点,返回p2即可

代码:

  1. 思路1
  2. struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
  3.     int i=0;
  4.     struct ListNode*cur=pListHead;//遍历指针统计节点数量
  5.     int n=0;
  6.     while(cur)
  7.     {
  8.         n++;
  9.         cur=cur->next;
  10.     }
  11.     cur=pListHead;
  12.     if(k>0&&k<=n)//判断K的合法性,合法则让遍历指针走n-k步
  13.     {
  14.         for(i=0;i<n-k;i++)//让遍历指针走n-k步
  15.         {
  16.             cur=cur->next;
  17.         }
  18.         return cur;
  19.     }
  20.     else//不合法返回空
  21.     {
  22.         return NULL;
  23.     }
  24. }
  25. 思路2
  26. struct ListNode* FindKthToTail(struct ListNode* pListHead, int k ) {
  27. if(pListHead==NULL)//判断链表是否为空,为空则返回为空
  28. return NULL;
  29. struct ListNode*cur=pListHead;//定义三个遍历指针
  30. struct ListNode*p1=pListHead;
  31. struct ListNode*p2=pListHead;
  32. int num=0;//记录节点数量
  33. while(cur)//遍历统计节点数量
  34. {
  35. num++;
  36. cur=cur->next;
  37. }
  38. if(k<=0||k>num)//判断k的合法性
  39. return NULL;
  40. k=k-1;
  41. while(k--)//让p1先走k-1步
  42. {
  43. p1=p1->next;
  44. }
  45. while(p1->next)//之后两个指针一起走
  46. {
  47. p1=p1->next;
  48. p2=p2->next;
  49. }
  50. return p2;
  51. }

5.合并两个有序链表

OJ链接力扣

题目描述:

思路:定义一个带头结点的新链表,定义两个指针分别指向两个链表,将两个链表的节点的值进行比较,较小的值则尾插到新链表中,然后指向该值地指针继续往后走,较大的值地指针则不动,当某个链表遍历结束后则结束比较,然后检查哪个链表没有遍历完,没有遍历完的元素一定是比较大的元素,直接尾插到新链表中即可,最后将新链表的头指针指向第一个节点,删除头结点返回头指针即可

代码:

  1. struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){
  2. struct ListNode *cur1=list1;//两个指针分别指向两个链表
  3. struct ListNode *cur2=list2;
  4. struct ListNode *guard,*tail;//新建一个带头结点的单链表
  5. guard=tail=(struct ListNode*)malloc(sizeof(struct ListNode));//开辟头结点
  6. while(cur1&&cur2)//遍历两个链表,当有一个链表遍历完则结束
  7. {
  8. if(cur1->val<cur2->val)//节点的值比较,较小的值尾插到新链表
  9. {
  10. tail->next=cur1;
  11. tail=tail->next;
  12. cur1=cur1->next;
  13. }
  14. else
  15. {
  16. tail->next=cur2;
  17. tail=tail->next;
  18. cur2=cur2->next;
  19. }
  20. }
  21. //检查哪个链表没有遍历完,没有遍历完的链表的节点则尾插到新链表中
  22. if(cur1==NULL)
  23. {
  24. while(cur2)
  25. {
  26. tail->next=cur2;
  27. tail=tail->next;
  28. cur2=cur2->next;
  29. }
  30. tail->next=NULL;
  31. }
  32. else if(cur2==NULL)
  33. {
  34. while(cur1)
  35. {
  36. tail->next=cur1;
  37. tail=tail->next;
  38. cur1=cur1->next;
  39. }
  40. tail->next=NULL;
  41. }
  42. else{
  43. return NULL;
  44. }
  45. struct ListNode*newnode=guard->next;//头指针指向第一个节点
  46. free(guard);//删除头结点
  47. return newnode;
  48. }

6.链表分割

OJ链接链表分割_牛客题霸_牛客网

题目描述:

思路:先判断原链表是否为空,为空则返回空,定义一个遍历指针遍历原链表,定义两个带头结点的空链表,将原链表值小于x的节点尾插到一个新链表中,将值大于x的节点尾插到新一个新链表中,将两个新链表的头节点删除,头指针指向第一个节点,然后将两个新链表链接在一起即可,最后返回链接后链表的头指针

代码:

  1. ListNode* partition(ListNode* pHead, int x) {
  2. // write code here
  3. if(pHead==NULL)//判断原链表是否为空
  4. return NULL;
  5. ListNode* cur=pHead;//定义遍历指针
  6. ListNode* guard1,*tail1,*guard2,*tail2;//定义两个新链表
  7. //开辟头结点
  8. guard1=tail1=(ListNode*)malloc(sizeof(ListNode));
  9. guard1->next=NULL;
  10. guard2=tail2=(ListNode*)malloc(sizeof(ListNode));
  11. guard2->next=NULL;
  12. //遍历原链表,值小于x的节点尾插到一个新链表中,大于x的节点尾插到另一个新链表中
  13. while(cur)
  14. {
  15. if(cur->val<x)
  16. {
  17. tail1->next=cur;
  18. tail1=tail1->next;
  19. cur=cur->next;
  20. }
  21. else
  22. {
  23. tail2->next=cur;
  24. tail2=tail2->next;
  25. cur=cur->next;
  26. }
  27. }
  28. tail1->next=guard2->next;//两个新链表链接在一起
  29. tail2->next=NULL;//链表的最后一个节点next置空
  30. ListNode* newnode1=guard1->next;//头指针指向第一个节点
  31. ListNode* newnode2=guard2->next;
  32. //删除头结点
  33. free(guard1);
  34. free(guard2);
  35. return newnode1;
  36. }
  37. }

7.链表的回文结构

OJ链接链表的回文结构_牛客题霸_牛客网

题目描述:

思路:这道题是前面几道题的综合,先判断原链表是否为空,为空则返回,调用第3题的函数找到原链表的中间链表,然后再调用第2题的函数将原链表后半部分反转,然后原链表和后部分反转链表遍历每个节点相比较,如果有节点不相等则返回false,相等则继续往下遍历,直到遍历结束然后返回true

代码:

  1. //返回中间节点
  2. struct ListNode* middleNode(struct ListNode* head){
  3. struct ListNode* cur=head;
  4. int num=0;
  5. while(cur)
  6. {
  7. num++;
  8. cur=cur->next;
  9. }
  10. cur=head;
  11. int i=0;
  12. for(i=0;i<num/2;i++)
  13. {
  14. cur=cur->next;
  15. }
  16. return cur;
  17. }
  18. //反转链表
  19. struct ListNode* reverseList(struct ListNode* head){
  20. struct ListNode* newnode=NULL;
  21. struct ListNode* cur=head;
  22. struct ListNode* next=head;
  23. if(cur==NULL)
  24. return head;
  25. else
  26. {
  27. while(cur)
  28. {
  29. next=next->next;
  30. cur->next=newnode;
  31. newnode=cur;
  32. cur=next;
  33. }
  34. }
  35. return newnode;
  36. }
  37. bool chkPalindrome(ListNode* A) {
  38. // write code here
  39. ListNode *mid=middleNode(A);//返回原链表的中间节点
  40. ListNode *rhead=reverseList(mid);//翻转原链表的后半部分
  41. while(A&&rhead)//遍历两个链表,比较节点的值
  42. {
  43. if(A->val!=rhead->val)
  44. return false;
  45. A=A->next;
  46. rhead=rhead->next;
  47. }
  48. return true;
  49. }
  50. }

8.相交链表

OJ链接力扣

题目描述:

思路:定义两个遍历指针,依次遍历两个链表统计出两个链表节点的个数,计算出两个链表节点数量的差值,让节点数量多的链表遍历走差值的步数,然后两个链表一起遍历,如果两个指针指向的节点相同,则该节点则为相交节点返回该节点,否则两个指针继续往后遍历直到遍历结束,遍历结束依然没有找到相交节点则返回空

代码:

  1. struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
  2. //定义两个遍历指针
  3. struct ListNode *cur1=headA;
  4. struct ListNode *cur2=headB;
  5. //分别统计两个链表节点数量
  6. int num1=0;
  7. int num2=0;
  8. //遍历统计两个链表节点数量
  9. while(cur1)
  10. {
  11. num1++;
  12. cur1=cur1->next;
  13. }
  14. while(cur2)
  15. {
  16. num2++;
  17. cur2=cur2->next;
  18. }
  19. cur1=headA;
  20. cur2=headB;
  21. //让节点数量多的链表遍历指针先走差值的步数
  22. if(num1>num2)
  23. {
  24. int k=num1-num2;
  25. while(k--)
  26. cur1=cur1->next;
  27. }
  28. else if(num1<num2)
  29. {
  30. int k=num2-num1;
  31. while(k--)
  32. cur2=cur2->next;
  33. }
  34. //两个链表一起遍历
  35. while(cur1)
  36. {
  37. //两个链表的节点相比较,相同则返回
  38. if(cur1==cur2)
  39. return cur1;
  40. if(cur1->next==cur2->next)
  41. return cur1->next;
  42. //不同则继续遍历
  43. else
  44. {
  45. cur1=cur1->next;
  46. cur2=cur2->next;
  47. }
  48. }
  49. return NULL;
  50. }

9.环形链表

OJ链接力扣

题目描述:

思路:这道题可以充分利用快慢指针的思想,定义一个慢指针low和快指针fast,分别一次走一步和两步,链表分为环区和直线区,当fast指针走到直线区末尾即环区入口时,low指针刚好走到直线区的中间,当慢指针入环时,快指针已经在环区走了若干圈,当两个指针一起走时,每走一次两个指针的距离便减小1,若干次后两个指针必会相遇,则证明该链表是环形链表,如果追不上或者指针指向空则证明该链表不是环形链表

代码:

  1. bool hasCycle(struct ListNode *head)
  2. {
  3. if(head==NULL)//判断原链表是否为空,为空则返回空
  4. return false;
  5. //定义两个快慢指针
  6. struct ListNode* low=head;
  7. struct ListNode* fast=head;
  8. low=low->next;//慢指针每次走一步
  9. fast=fast->next->next;//快指针每次走两步
  10. while(low!=fast&&fast!=NULL)//两个指针一直走,直到相遇或者为空结束
  11. {
  12. low=low->next;
  13. fast=fast->next->next;
  14. }
  15. if(low==fast)//相遇则返回真
  16. return true;
  17. if(fast==NULL)//为空则返回假
  18. return false;
  19. }

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

 代码:

  1. struct ListNode *detectCycle(struct ListNode *head) {
  2. if(head==NULL||head->next==NULL)//判断链表是否为空或者只有一个节点
  3. return NULL;
  4. //定义两个快慢指针
  5. struct ListNode *low=head;
  6. struct ListNode *fast=head;
  7. while(fast&&fast->next)//快慢指针遍历
  8. {
  9. low=low->next;
  10. fast=fast->next->next;
  11. //如果快慢指针相遇则让一个指针从头开始走,另一个指针从相遇点开始走
  12. if(low==fast)
  13. {
  14. struct ListNode *meet=low;
  15. while(head!=meet)
  16. {
  17. head=head->next;
  18. meet=meet->next;
  19. }
  20. return meet;
  21. }
  22. }
  23. //上面的循环结束到这里说明链表没有环,返回空
  24. return NULL;
  25. }

好啦,顺序表和链表经典面试题就先学习到这里,如果文章对您有帮助,欢迎一键三连~

最后附上寄语:种一棵树最好的时间是十年前,其次是现在

文章知识点与官方知识档案匹配,可进一步学习相关知识
算法技能树首页概览44229 人正在系统学习中