顺序表算法题
不熟悉顺序表的可以先了解一下
移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
假设 nums 中不等于 val 的元素数量为 k,要通过此题,您需要执行以下操作:
- 更改 nums 数组,使 nums 的前 k 个元素包含不等于 val 的元素。nums 的其余元素和 nums 的大小并不重要。
- 返回 k。
很容易想到的是用双指针进行遍历和赋值即可
int removeElement(int* nums, int numsSize, int val) { int left = 0; for (int right = 0; right < numsSize; right++) { if (nums[right] != val) { nums[left] = nums[right]; left++; } } return left; }
- 注意题目中说的是元素的顺序可以发生改变,即不需要保持元素的相对顺序,可以进行优化
如果左指针 left 指向的元素等于 val,此时将右指针 right 指向的元素复制到左指针 left 的位置,然后右指针 right 左移一位。如果赋值过来的元素恰好也等于 val,可以继续把右指针 right 指向的元素的值赋值过来(左指针 left 指向的等于 val 的元素的位置继续被覆盖),直到左指针指向的元素的值不等于 val 为止。
注意while
结束条件不要少了等于
int removeElement(int* nums, int numsSize, int val) { int left = 0, right = numsSize-1; while (left <= right) { if (nums[left] == val) { nums[left] = nums[right]; right--; } else { left++; } } return left; }
注意:返回的是元素的个数
删除有序数组中的重复项
给你一个 非严格递增排列 的数组 nums ,请你** 原地** 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。
考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:
- 更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
- 返回 k
和上一道题思路大致相同,题目说了是非严格递增序列,双指针法比较前后两个元素即可
int removeDuplicates(int* nums, int numsSize) { int src = 0; int dest = 1; while (dest < numsSize) { if(nums[src]!=nums[dest]) { nums[++src]=nums[dest]; } dest++; } return ++src; }
合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
**注意:**最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
- 当然可以直接把第二个数组移到第一个数组尾部,然后进行排序
- 使用qsort函数
int cmp(int* a, int* b) { return *a - *b; } void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) { for (int i = 0; i != n; ++i) { nums1[m + i] = nums2[i]; } qsort(nums1, nums1Size, sizeof(int), cmp); }
- 三指针法,利用两个数组都是非递减排列
- 因为排序好的数组仍然是非递减序列,所以我们的两个指针依次从尾部开始向前遍历,谁大把谁放到nums1的尾部(若从前面开始需要新创建一个数组来存储元素最后再赋值给nums1)
- 最后出循环的时候l2和l3只可能有一个小于0
- 若是l2,说明nums2没有遍历完,需要将剩下的元素赋值给nums1
- 若是l3,则直接返回nums1即可
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) { int l1=nums1Size-1; int l2=m-1; int l3=n-1; while(l2>=0&&l3>=0) { if(nums1[l2]>nums2[l3]) { nums1[l1]=nums1[l2]; l2--; } else { nums1[l1]=nums2[l3]; l3--; } l1--; } if(l2<0) { while(l1>=0) nums1[l1--]=nums2[l3--]; } }
链表算法题
不熟悉链表的可以先了解一下
移除链表元素
给你一个链表的头节点
head
和一个整数val
,请你删除链表中所有满足Node.val == val
的节点,并返回 新的头节点 。
最直观的办法就是创建一个新链表
注:不是开辟空间的深拷贝,而只是定义了指向同一结点的指针
在最后需要先判断newtail是否为空(否则链表为空链表时会报错)再将其中的next指针置为空(否则可能会出现循环)
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ typedef struct ListNode ListNode; ListNode* removeElements(ListNode* head, int val) { ListNode* pcur = head; ListNode* newhead = NULL; ListNode* newtail = NULL; 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; }
- 在leetcode上给出了递归的方法,链表的定义具有递归的性质,因此链表题目常可以用递归的方法求解。这道题要求删除链表中所有节点值等于特定值的节点,可以用递归实现。
struct ListNode* removeElements(struct ListNode* head, int val) { if (head == NULL) { return head; } head->next = removeElements(head->next, val); return head->val == val ? head->next : head; }
反转链表
给你单链表的头节点
head
,请你反转链表,并返回反转后的链表。
- 可以和上一道题一样创建新链表,不多赘述
- 这里介绍一种比较巧妙的方法->三指针法
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ typedef struct ListNode ListNode; struct ListNode* reverseList(struct ListNode* head) { if(head==NULL) return head; ListNode* n1,*n2,*n3; n1=NULL,n2=head,n3=head->next; while(n3){ n2->next=n1; n1=n2; n2=n3; n3=n3->next; } n2->next=n1; return n2; }
链表的中间结点
给你单链表的头结点
head
,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点
- 快慢指针法
- 注意为偶数时返回第二个结点
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ typedef struct ListNode ListNode; struct ListNode* middleNode(struct ListNode* head) { ListNode* slow, *fast; slow=fast=head; while(fast&&fast->next) { slow=slow->next; fast=fast->next->next; } return slow; }
合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
- 和合并数组大致思路相同
- 可以在创建新链表的一开始申请头结点(哨兵位),避免对于newtail和newhead为空的情况进行讨论
- 记得最后释放空间!!!
/** * Definition for singly-linked list. * struct ListNode { * int val; * struct ListNode *next; * }; */ typedef struct ListNode ListNode; struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2) { ListNode* newhead,*newtail; newhead=newtail=(ListNode*)malloc(sizeof(ListNode)); while(list1&&list2) { if(list1->val<list2->val) { newtail->next=list1; list1=list1->next; } else { newtail->next=list2; list2=list2->next; } newtail=newtail->next; } if(list1) { newtail->next=list1; } else { newtail->next=list2; } ListNode* ret=newhead->next; free(newhead); newhead=newtail=NUll; return ret; }
链表分割
有一链表的头指针 ListNode* pHead,给一定值x,编写一段代码将所有小于x的结点排在其余结点之前,且不能改变原来的数据顺序,返回重新排列后的链表的头指针
- 解题思路:创建两个链表,分别存放小于x的节点和大于等于x的节点,分别进行尾插
- 最后别忘了把大链表的尾节点置为空,否则可能会出现死循环
- 还是别忘了释放空间
ListNode* partition(ListNode* pHead, int x) { if(pHead == NULL) return NULL; struct ListNode* lessHead, *lessTail,*greaterHead, *greaterTail; //创建链表表头 lessHead = lessTail = (struct ListNode*)malloc(sizeof(struct ListNode)); greaterHead = greaterTail = (struct ListNode*)malloc(sizeof(struct ListNode)); struct ListNode* cur = pHead; while(cur) { //小于x的尾插到lessTail if(cur->val < x) { lessTail->next = cur; lessTail = lessTail->next; } //大于等于x的尾插到greaterTail else { greaterTail->next = cur; greaterTail = greaterTail->next; } cur = cur->next; } //链接两个链表 lessTail->next = greaterHead->next; greaterTail->next = NULL; //获取表头 pHead = lessHead->next; free(lessHead); free(greaterHead); return pHead; }
链表的回文结构
对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
给定一个链表的头指针A,请返回一个bool值,代表其是否为回文结构。保证链表长度小于等于900
- 不推荐的解法,类似数组字符串的回文解法
- 先把链表中的元素值全部保存到数组中,然后再判断数组是否为回文。不建议使用这种解法,因为如果没有告诉链表最大长度,则不能同此解法
bool chkPalindrome(ListNode* A) { // write code here int a[900] = {0}; ListNode* cur = A; int n = 0; //保存链表元素 while(cur) { a[n++] = cur->val; cur = cur->next; } //判断数组是否为回文结构 int begin = 0, end = n-1; while(begin < end) { if(a[begin] != a[end]) return false; ++begin; --end; } return true; }
- 解题思路:此题可以先找到中间节点,然后把后半部分逆置,最近前后两部分一一比对,如果节点的值全部相同,则即为回文。
- 快慢指针找中间结点
- 三指针逆置后半部分
bool chkPalindrome(ListNode* A) { if (A == NULL || A->next == NULL) return true; ListNode* slow, *fast, *prev, *cur, *nxt; slow = fast = A; //找到中间节点 while (fast && fast->next) { slow = slow->next; fast = fast->next->next; } prev = NULL; //后半部分逆置 cur = slow; while (cur) { nxt = cur->next; cur->next = prev; prev = cur; cur = nxt; } //逐点比对 while (A && prev) { if (A->val != prev->val) return false; A = A->next; prev = prev->next; } return true; }