当前位置:   article > 正文

(涛菜菜)c++及数据结构及算法经典题目总结_c++ 算法 数据结构题目

c++ 算法 数据结构题目

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

目录

一、链表相加(二)

二、反转链表

三,链表内指定区间反转

四,合并两个排序的链表

五,字符串变形

 六,二叉树的前序遍历(根左右)

  七,二叉树的中序遍历(左根右)

八. 二叉树的后序遍历(左右根)

 九,二叉树的最大深度

十,二分查找

 十一,最长公共前缀

十二,大数加法 

十三,判断一个链表是否为回文结构

 十四,兑换零钱

总结


一、链表相加(二)

假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。

给定两个这种链表,请生成代表两个整数相加值的结果链表。

数据范围:0≤n,m≤10000000,链表任意值 0≤val≤9
要求:空间复杂度 O(n),时间复杂度 O(n)

具体做法:

  • step 1:任意一个链表为空,返回另一个链表就行了,因为链表为空相当于0,0加任何数为0,包括另一个加数为0的情况。
  • step 2:相继反转两个待相加的链表,反转过程可以参考反转链表
  • step 3:设置返回链表的链表头,设置进位carry=0.
  • step 4:从头开始遍历两个链表,直到两个链表节点都为空且carry也不为1. 每次取出不为空的链表节点值,为空就设置为0,将两个数字与carry相加,然后查看是否进位,将进位后的结果(对10取模)加入新的链表节点,连接在返回链表后面,并继续往后遍历。
  • step 5:返回前将结果链表再反转回来

  1. class Solution {
  2. public:
  3. //反转链表
  4. ListNode* ReverseList(ListNode* pHead) {
  5. if(pHead == NULL)
  6. return NULL;
  7. ListNode* cur = pHead;
  8. ListNode* pre = NULL;
  9. while(cur != NULL){
  10. //断开链表,要记录后续一个
  11. ListNode* temp = cur->next;
  12. //当前的next指向前一个
  13. cur->next = pre;
  14. //前一个更新为当前
  15. pre = cur;
  16. //当前更新为刚刚记录的后一个
  17. cur = temp;
  18. }
  19. return pre;
  20. }
  21. ListNode* addInList(ListNode* head1, ListNode* head2) {
  22. //任意一个链表为空,返回另一个
  23. if(head1 == NULL)
  24. return head2;
  25. if(head2 == NULL)
  26. return head1;
  27. //反转两个链表
  28. head1 = ReverseList(head1);
  29. head2 = ReverseList(head2);
  30. //添加表头
  31. ListNode* res = new ListNode(-1);
  32. ListNode* head = res;
  33. //进位符号
  34. int carry = 0;
  35. //只要某个链表还有或者进位还有
  36. while(head1 != NULL || head2 != NULL || carry != 0){
  37. //链表不为空则取其值
  38. int val1 = head1 == NULL ? 0 : head1->val;
  39. int val2 = head2 == NULL ? 0 : head2->val;
  40. //相加
  41. int temp = val1 + val2 + carry;
  42. //获取进位
  43. carry = temp / 10;
  44. temp %= 10;
  45. //添加元素
  46. head->next = new ListNode(temp);
  47. head = head->next;
  48. //移动下一个
  49. if(head1 != NULL)
  50. head1 = head1->next;
  51. if(head2 != NULL)
  52. head2 = head2->next;
  53. }
  54. //结果反转回来
  55. return ReverseList(res->next);
  56. }
  57. };

二、反转链表

给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。

数据范围: 0≤n≤1000

要求:空间复杂度 O(1) ,时间复杂度 O(n) 。

方法一: vector向量构造链表

可以先用一个vector将单链表的指针都存起来,然后再构造链表。 

  1. class Solution {
  2. public:
  3. //1.使用栈解决
  4. //把节点链表一个一个入栈,全部入栈后再一个一个输出
  5. //2.构造链表
  6. //可以先用一个vector将单链表的指针都存起来,然后再构造链表。
  7. ListNode* ReverseList(ListNode* pHead) {
  8. if(!pHead)
  9. return nullptr;
  10. vector<ListNode*>v;
  11. while(pHead)
  12. {
  13. v.push_back(pHead);
  14. pHead=pHead->next;
  15. }
  16. reverse(v.begin(),v.end());//反转vector,也可以逆向遍历
  17. ListNode *head=v[0];
  18. ListNode *cur=head;
  19. for(int i=1;i<v.size();i++)//构造链表
  20. {
  21. cur->next=v[i];//当前节点的下一个指针指向下一个节点
  22. cur=cur->next;//当前节点后裔
  23. }
  24. cur->next=nullptr;//切记最后一个节点的下一个指针指向nullptr
  25. return head;
  26. }
  27. };

 方法二:正规双向链表

  1. class Solution {
  2. public:
  3. //反转链表
  4. ListNode* ReverseList(ListNode* pHead) {
  5. if(pHead == NULL)
  6. return NULL;
  7. ListNode* cur = pHead;
  8. ListNode* pre = NULL;
  9. while(cur != NULL){
  10. //断开链表,要记录后续一个
  11. ListNode* temp = cur->next;
  12. //当前的next指向前一个
  13. cur->next = pre;
  14. //前一个更新为当前
  15. pre = cur;
  16. //当前更新为刚刚记录的后一个
  17. cur = temp;
  18. }
  19. return pre;
  20. }
  21. };

三,链表内指定区间反转

将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n),空间复杂度 O(1)。

数据范围: 链表长度 0<size≤1000,0<m≤n≤size,链表中每个节点的值满足∣val∣≤10000

给出的链表为 1→2→3→4→5→NULL, m=2,n=4,
返回 1→4→3→2→5→NULL.

方法一:栈

解题思路:

  • 判断参数head是否为空,若空则直接返回;判断m是否等于n,相等则无需进行操作,直接返回;
  • 由于题目给出的链表是不带头结点的,所以令i=1,work=head,首先用work指针,从链表头部开始进行遍历,遍历前m-1元素,最后一次遍历时,work指向第m个元素,同时以start指针记录work的当前位置以便下一次遍历时能够直接从第m个元素开始;
  • 顺序遍历第m到第n位置的元素,并用栈进行存储;
  • 再次从start出开始进行顺序遍历直到栈空,每遍历一个新结点时将栈顶元素赋值给当前结点,实现反转链表。
  1. ListNode* reverseBetween(ListNode* head, int m, int n)
  2. {
  3. if(!head || m == n)
  4. return head;
  5. ListNode *work = head;
  6. stack<int> stk;
  7. int i = 1;
  8. while(i <= m-1) { // 找到第m个结点
  9. work = work->next;
  10. ++i;
  11. }
  12. ListNode *start = work; // 记录第m个结点的指针
  13. while(i <= n) { // 记录第m到第n个结点的值
  14. stk.push(work->val);
  15. work = work->next;
  16. ++i;
  17. }
  18. work = start; // 从第m个结点开始遍历
  19. while(!stk.empty()) { // 赋值实现反转
  20. work->val = stk.top();
  21. stk.pop();
  22. work = work->next;
  23. }
  24. return head;
  25. }

四,合并两个排序的链表

输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。

要求:空间复杂度 O(1),时间复杂度 O(n)

思路:

这道题既然两个链表已经是排好序的,都是从小到大的顺序,那我们要将其组合,可以使用归并排序的思想:每次比较两个头部,从中取出最小的元素,然后依次往后。这样两个链表依次往后,我们肯定需要两个指针同方向访问才能实现。

具体过程:

  • step 1:判断空链表的情况,只要有一个链表为空,那答案必定就是另一个链表了,就算另一个链表也为空。
  • step 2:新建一个空的表头后面连接两个链表排序后的节点,两个指针分别指向两链表头。
  • step 3:遍历两个链表都不为空的情况,取较小值添加在新的链表后面,每次只把被添加的链表的指针后移。
  • step 4:遍历到最后肯定有一个链表还有剩余的节点,它们的值将大于前面所有的,直接连在新的链表后面即可。
  1. class Solution {
  2. public:
  3. ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
  4. //一个已经为空了,直接返回另一个
  5. if(pHead1 == NULL)
  6. return pHead2;
  7. if(pHead2 == NULL)
  8. return pHead1;
  9. //加一个表头
  10. ListNode* head = new ListNode(0);
  11. ListNode* cur = head;
  12. //两个链表都要不为空
  13. while(pHead1 && pHead2){
  14. //取较小值的节点
  15. if(pHead1->val <= pHead2->val){
  16. cur->next = pHead1;
  17. //只移动取值的指针
  18. pHead1 = pHead1->next;
  19. }else{
  20. cur->next = pHead2;
  21. //只移动取值的指针
  22. pHead2 = pHead2->next;
  23. }
  24. //指针后移
  25. cur = cur->next;
  26. }
  27. //哪个链表还有剩,直接连在后面
  28. if(pHead1)
  29. cur->next = pHead1;
  30. else
  31. cur->next = pHead2;
  32. //返回值去掉表头
  33. return head->next;
  34. }
  35. };

五,字符串变形

对于一个长度为 n 字符串,我们需要对它做一些变形。

首这个字符串中包含着一些空格,就像"Hello World"一样,然后我们要做的是把这个字符串中由空格隔开的单词反序,同时反转每个字符的大小写。

比如"Hello World"变形后就变成了"wORLD hELLO"。

思路:

将单词位置的反转,那肯定前后都是逆序,不如我们先将整个字符串反转,这样是不是单词的位置也就随之反转了。但是单词里面的成分也反转了啊,既然如此我们再将单词里面的部分反转过来就行。

具体做法:

  • step 1:遍历字符串,遇到小写字母,转换成大写,遇到大写字母,转换成小写,遇到空格正常不变。
  • step 2:第一次反转整个字符串,这样基本的单词逆序就有了,但是每个单词的字符也是逆的。
  • step 3:再次遍历字符串,以每个空间为界,将每个单词反转回正常。
  1. class Solution {
  2. public:
  3. string trans(string s, int n) {
  4. if(n==0)
  5. return s;
  6. string res;
  7. for(int i = 0; i < n; i++){
  8. //大小写转换
  9. if (s[i] <= 'Z' && s[i] >= 'A')
  10. res += s[i] - 'A' + 'a';
  11. else if(s[i] >= 'a' && s[i] <= 'z')
  12. res += s[i] - 'a' + 'A';
  13. else
  14. //空格直接复制
  15. res+=s[i];
  16. }
  17. //翻转整个字符串
  18. reverse(res.begin(), res.end());
  19. for (int i = 0; i < n; i++){
  20. int j = i;
  21. //以空格为界,二次翻转
  22. while(j < n && res[j] != ' ')
  23. j++;
  24. reverse(res.begin() + i, res.begin() + j);
  25. i = j;
  26. }
  27. return res;
  28. }
  29. };

 六,二叉树的前序遍历(根左右)

具体做法:

  • step 1:准备数组用来记录遍历到的节点值,C++可以直接用vector。
  • step 2:从根节点开始进入递归,遇到空节点就返回,否则将该节点值加入数组。
  • step 3:依次进入左右子树进行递归。
  1. class Solution {
  2. public:
  3. void preorder(vector<int>&res,TreeNode* root){
  4. //遇到空节点则返回
  5. if(root==NULL)
  6. return;
  7. //先遍历根节点
  8. res.push_back(root->val);
  9. //再去左子树
  10. preorder(res,root->left);
  11. //最后再去右子树
  12. preorder(res,root->right);
  13. }
  14. vector<int>preorderTraversal(TreeNode *root)
  15. {
  16. vector<int>res;
  17. //递归前序遍历
  18. preorder(res,root);
  19. return res;
  20. }
  21. };

  七,二叉树的中序遍历(左根右)

具体做法:

  • step 1:准备数组用来记录遍历到的节点值,C++可以直接用vector。
  • step 2:从根节点开始进入递归,遇到空节点就返回,否则优先进入左子树进行递归访问。
  • step 3:左子树访问完毕再回到根节点访问。
  • step 4:最后进入根节点的右子树进行递归。
  1. class Solution {
  2. public:
  3. void inorder(vector<int>&res,TreeNode* root){
  4. //遇到空节点则返回
  5. if(root==NULL)
  6. return;
  7. //先遍历左子树
  8. inorder(res,root->left);
  9. //再遍历根节点
  10. res.push_back(root->val);
  11. //最后遍历右子树
  12. inorder(res,root->right);
  13. }
  14. vector<int> inorderTraversal(TreeNode* root) {
  15. vector<int>res;
  16. //递归中序遍历
  17. inorder(res,root);
  18. return res;
  19. }
  20. };

八. 二叉树的后序遍历(左右根)

  1. class Solution {
  2. public:
  3. void postorder(vector<int>&res,TreeNode*root){
  4. //遇到空节点则返回
  5. if(root==NULL)
  6. return;
  7. //先遍历左子树
  8. postorder(res,root->left);
  9. //再遍历右子树
  10. postorder(res,root->right);
  11. //最后遍历根节点
  12. res.push_back(root->val);
  13. }
  14. vector<int> postorderTraversal(TreeNode* root) {
  15. vector<int>res;
  16. //递归后续遍历
  17. postorder(res,root);
  18. return res;
  19. }
  20. };

 九,二叉树的最大深度

具体做法:

  • step 1:对于每个节点,若是不为空才能累计一次深度,若是为空,返回深度为0.
  • step 2:递归分别计算左子树与右子树的深度。
  • step 3:当前深度为两个子树深度较大值再加1。
  1. class Solution {
  2. public:
  3. int maxDepth(TreeNode* root) {
  4. //空节点没有深度
  5. if(root==NULL)
  6. return 0;
  7. //返回子树深度加1
  8. return max(maxDepth(root->left),maxDepth(root->right))+1;
  9. }
  10. };

十,二分查找

  1. class Solution {
  2. public:
  3. int search(vector<int>& nums, int target) {
  4. int l=0;
  5. int r=nums.size()-1;
  6. //从数组首尾开始,直到二者相遇
  7. while(l<=r){
  8. //每次检查中点的值
  9. int m=(l+r)/2;
  10. if(nums[m]==target)
  11. return m;
  12. if(nums[m]>target)
  13. r=m-1;
  14. else
  15. l=m+1;
  16. }
  17. return -1;
  18. }
  19. };

 十一,最长公共前缀

 给你一个大小为 n 的字符串数组 strs ,其中包含n个字符串 , 编写一个函数来查找字符串数组中的最长公共前缀,返回这个公共前缀。

进阶:空间复杂度 O(1),时间复杂度 O(n∗len)

方法:子串纵向查找

纵向遍历非常的直观,如下图所示,将每个字符串分别依次遍历每一列的元素,比较相同列上字符是否相同,若相同则比较下一个子串,若不同则最长公共前缀为上个遍历过的公共前缀。

  1. class Solution {
  2. public:
  3. //题目给出长度为n的子串,找出子串中最长的公共前缀。
  4. string longestCommonPrefix(vector<string>& strs) {
  5. if(!strs.size())//特判若子串为空则返回空值
  6. return "";
  7. for(int i=0;i<strs[0].size();i++)//枚举第一个子串的每个字符
  8. {
  9. for(int j=1;j<strs.size();j++)//枚举后面所有子串
  10. {
  11. if(strs[0][i]!=strs[j][i]||i==strs[j].size())
  12. {
  13. return strs[0].substr(0,i);
  14. }
  15. }
  16. }
  17. return strs[0];
  18. }
  19. };

十二,大数加法 

以字符串的形式读入两个数字,编写一个函数计算它们的和,以字符串形式返回。

数据范围s.length,t.length≤100000,字符串仅由'0'~‘9’构成

思路:

字符串中就是从两个字符串的末尾开始相加。

step 1:若是其中一个字符串为空,直接返回另一个,不用加了。

step 2:交换两个字符串的位置,我们是s为较长的字符串,t为较短的字符串,结果也记录在较长的字符串中。

step 3:从后往前遍历字符串s,每次取出字符转数字,加上进位制,将下标转换为字符串t中从后往前相应的下标,如果下标为非负数则还需要加上字符串t中相应字符转化的数字。

step 4:整型除法取进位,取模算法去掉十位,将计算后的结果放入较长数组对应位置。

step 5:如果遍历结束,进位值还有,则需要直接在字符串s前增加一个字符‘1’。

  1. class Solution {
  2. public:
  3. string solve(string s, string t) {
  4. if(s.empty())
  5. return t;
  6. if(t.empty())
  7. return s;
  8. //让s为较长的,t为较短的
  9. if(s.length()<t.length())
  10. swap(s,t);
  11. //进位标志
  12. int carry=0;
  13. //从后往前遍历较长的字符串
  14. for(int i=s.length()-1;i>=0;i--)
  15. {
  16. //转数字加上进位
  17. int temp=s[i]-'0'+carry;
  18. //转较短的字符串相应的从后往前的下标
  19. int j=i-s.length()+t.length();//此处大数相加需要记住
  20. if(j>=0)
  21. temp+=t[j]-'0';
  22. carry=temp/10;
  23. temp=temp%10;
  24. s[i]=temp+'0';
  25. }
  26. if(carry==1)
  27. s='1'+s;
  28. return s;
  29. }
  30. };

十三,判断一个链表是否为回文结构

给定一个链表,请判断该链表是否为回文结构。

回文是指该字符串正序逆序完全一致。

数据范围: 链表节点数 0≤n≤105,链表中每个节点的值满足 ∣val∣≤107

具体做法:

  • step 1:遍历一次链表,将元素取出放入辅助数组中。
  • step 2:准备另一个辅助数组,录入第一个数组的全部元素,再将其反转。
  • step 3:依次遍历原数组与反转后的数组,若是元素都相等则是回文结构,只要遇到一个不同的就不是回文结构。
  1. class Solution {
  2. public:
  3. bool isPail(ListNode* head) {
  4. vector<int>nums;
  5. //将链表元素取出一次放入数组
  6. while(head!=NULL) //需要记
  7. {
  8. nums.push_back(head->val);
  9. head=head->next;
  10. }
  11. vector<int>temp=nums; //对于向量赋值不需要一个一个数,直接就可以啦
  12. //准备一个数组继承翻转之后的数组
  13. reverse(temp.begin(),temp.end());
  14. for(int i=0;i<nums.size();i++)
  15. {
  16. //正向遍历和反向遍历是否相同
  17. if(nums[i]!=temp[i])
  18. return false;
  19. }
  20. return true;
  21. }
  22. };

 十四,兑换零钱

给定数组arr,arr中所有的值都为正整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个aim,代表要找的钱数,求组成aim的最少货币数。

如果无解,请返回-1.

数据范围:数组大小满足 0≤n≤10000 , 数组中每个数字都满足 0<val≤10000,0≤aim≤5000

具体做法:

  • step 1:可以用dp[i]表示要凑出i元钱需要最小的货币数。
  • step 2:一开始都设置为最大值aim+1,因此货币最小1元,即货币数不会超过aim.
  • step 3:初始化dp[0]=0。
  • step 4:后续遍历1元到aim元,枚举每种面值的货币都可能组成的情况,取每次的最小值即可,转移方程为dp[i]=min(dp[i],dp[i−arr[j]]+1).
  • step 5:最后比较[aim]的值是否超过aim,如果超过说明无解,否则返回即可。
  1. class Solution {
  2. public:
  3. int minMoney(vector<int>& arr, int aim) {
  4. //小于1的都返回0
  5. if(aim<1)
  6. return 0;
  7. //dp[i]表示凑齐i元最小的货币数
  8. vector<int>dp(aim+1,aim+1);
  9. dp[0]=0;
  10. //遍历1~aim元
  11. for(int i=1;i<=aim;i++)
  12. {
  13. //每种面值货币都要枚举
  14. for(int j=0;j<arr.size();j++)
  15. {
  16. //如果面值不超过要凑的钱才可以用
  17. if(arr[j]<=i)
  18. {
  19. //维护最小值
  20. dp[i]=min(dp[i],dp[i-arr[j]]+1);
  21. }
  22. }
  23. }
  24. //如果最终答案大于aim代表无解
  25. return dp[aim]>aim?-1:dp[aim];
  26. }
  27. };


总结

珍惜当下的经历,生活就是一段段经历、修心,感受不同的经历,不要等到某个时候再去做什么事,人生没有过渡,你不能等到生活不再艰难了,才决定开始快乐。

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号