数据结构:编程题

数据结构和数据库的区别 

  • 他们两个本质在于存储管理数据
  • 数据结构--在内存中存储管理数据
  • 数据库--在磁盘中存储管理数据

为什么现在越来越不关心内存,而关心运行速度(时间复杂度)

  • 摩尔定律表示:集成电路上可以容纳的晶体管数目在大约每经过18个月便会增加一倍
  • 因为存在摩尔定律,所以随着时间的推移,内存会越来越大

 1.消失的数字

 思路一:映射

  • malloc一个全是-1的新数组 
  • 遍历这些数字,这个数是多少,就写到新数组对应的位置
  • 遍历一遍新数组,那个位置是-1,它的下标就是缺失的数字

思路二:异或(推荐)

  •  异或时,一个数字异或它自己结果为0,一个数字异或0结果是它自己
  • 其他数出现两次,只有缺失的那个数字出现一次

思路三:排序+二分查找(不推荐)

  • 虽然二分查找的时间复杂度是O(logN),但是二分查找使用的前提是在有序的情况下
  • 对于排序,即使用了快排,时间复杂度也是O(N*logN)

思路四:公式法

  •  求和计算,两者一减得到的就是那个缺失的数字

2.轮转数组

思路一:直接旋转 

  • 每次旋转一个,旋转k次
  • 时间复杂度为O(N*k),空间复杂度O(1)

思路二:以空间换时间

  •  开辟一段新的空间,先把数组后k个数放入新空间,再把n-k个数放入新空间,最后拷贝回去

  • 时间复杂度为O(N),空间复杂度O(N)

思路三:三步翻转法(推荐) 

  •  前n-k个逆置,后k个逆置,整体逆置
  • 时间复杂度O(N),空间复杂度O(1)

3.移除元素

 思路一:覆盖删除

  •  先找到val,并将val之后的数从后向前覆盖
  • 时间复杂度O(N^2)

思路二: 空间换时间

  •  保留不是val的值,将这些值拷贝到一个新数组
  • 时间复杂度O(N),空间复杂度O(N)

思路三:双指针

  •  src所指向的值不等于val时,将src所指向的值赋值给des所指向的值,然后src++,des++
  • src所指向的值等于val时,只让src++

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

  •  去重算法,使用的是前后指针:

5.合并两个有序数组

 

  •  从后向前进行比较,然后归并 

6.移除链表元素 

思路一: 直接移除

  •  情况有点多,需要考虑全面

思路二:头插

  •  注意最后需要释放头结点,把头节点置为空

7.反转链表

 思路一:翻指针 

 思路一:头插

  •  注意最后需要释放头结点,把头节点置为空 

8.链表的中间节点

  •  这里运用了快慢指针

9.链表中倒数第k个结点

  •  这里还是用的快慢指针,只不过这里是快慢指针的一种变形,
  • fast是从头节点的k个节点开始,
  • 注意考虑要全面

10.合并两个有序链表

  •  这里运用了哨兵位,带头节点

11.链表分割

 

  •  一个链表存比x大的,另一个链表存比x小的,

 12链表的回文结构

  •  找中间节点,逆置,比较

13.相交链表

  •  这里需要先让长的那个链表先走(两个链表长度只差)
  • 时间复杂度为O(N),设计的十分巧妙

14.判断是否为环形链表

 

证明slow一次走一步,fast一次走两步,一定能追上

  • 设环的长度为C
  • slow刚进环的时候,fast就开始追,假设从fast开始到slow之间的距离是N(0<=N<C)
  • 因为fast比slow多走一步,那么他们之间的距离就会不断的减一
  • 从fast开始到slow之间的距离:N N-1 N-2 N-3 N-4 .....0
  • 从fast开始到slow之间的距离变成0,意味着fast追上slow,相遇了

证明slow一次走一步,fast一次走三步,不一定能追上,甚至会死循环

  • 设环的长度为C
  • slow刚进环的时候,fast就开始追,假设从fast开始到slow之间的距离是N(0<=N<C)
  • 因为fast比slow多走一步,那么他们之间的距离就会不断的减二
  • 当N为偶数时,从fast开始到slow之间的距离:N N-2 N-4 N-6 .....0
    从fast开始到slow之间的距离变成0,意味着fast追上slow,相遇了
  • 当N为奇数时,从fast开始到slow之间的距离:N N-2 N-4 N-6 .....-1
    从fast开始到slow之间的距离变成-1,意味着fast追上且超过了slow,
    此时从fast开始到slow之间的距离变成了C-1
  •  当C-1为偶数时,从fast开始到slow之间的距离:C-1 C-3 C-5 C-7....0
    从fast开始到slow之间的距离变成0,意味着fast追上slow,相遇了
  •  当C-1为奇数时,从fast开始到slow之间的距离:C-1 C-3 C-5 C-7....-1
    从fast开始到slow之间的距离变成-1,意味着fast追上且超过了slow,
    此时从fast开始到slow之间的距离变成了C-1,如果到了这一步就意味着已经确定了C-1是奇数
    之后从fast开始到slow之间的距离将用永远不会为0,意味着死循环

 15. 求环形链表的入口点

思路一:数学推导 

当这个链表是环形链表时

  • slow从开始到与fast相遇点meet走过的路程为S1=L+x
  • fast从开始到与slow相遇点meet走过的路程为S2=L+x+N*C
  • fast的速度是slow的两倍,由2*S1=S2得,L=N*C-x,化简为L=(N-1)*C+y
  • (N-1)*C意味着位置没有变,在原地转圈
  • 综上诉述L=y

注意:slow指针进环之前,fast有可能在环里面转了N圈,这跟L(入环前的长度有关),也与C(环的长度有关),所以才有S2=L+x+N*C中的N*C

结论:一个指针从meet点开始走,一个指针从链表开始走他们会在入口点相遇

 思路二:转换

  •  一个指针从slow和fast相遇点开始,另一个指针从head开始
  • 原问题转换成求两个链表的相交节点

16.复制带随机指针的链表 

  •  设计要点:node->random = cur->random->next;

17.有效括号 

  • 左括号入栈,如果是右括号,则将栈顶的元素进行判断
  • 需要特别注意:只有一个括号,栈是否为空都需要考虑

18.用队列实现栈

  •  利用队列的特性进行解决
  • 两个队列相互交换

19.用栈实现队列

  •  利用栈的特性进行解决
  • 两个栈进行交换

20.设计循环队列 

  •  数组支持下标访问,这里多开了一个空间,使判断队列是否满更加简单
  • 开空间的时候注意括号,

21.单值二叉树

  • 利用二叉树的前中后序
  • 注意节点为空的情况

22.相同的树

  •  利用二叉树的前中后序
  • 注意节点为空的情况

23.对称二叉树

  •  借用上面相同的树的代码
  • 注意节点为空的情况

24.另一颗数的子树

  •  利用二叉树的前中后序
  • 借用上面相同的树的代码
  • 注意节点为空的情况

25.二叉树的前序遍历

  •  形参是实参的临时拷贝,传地址

26.二叉树的中序遍历 

  • 与二叉数的前序遍历同理

27.二叉数的后序遍历 

  • 与二叉数的前序遍历同理

28.二叉树遍历

  •  先用前序构建树,

29.二叉树的最大深度

  • 通过分治思想,结合后序,递归解决

30.平衡二叉数

  • 两层递归解决 

31.二叉树的层序遍历

 c语言动态开辟数组(一维与二维)

  • 重点在于returnColumnSizes,他的每一个行都是需要开辟空间的

32.迷宫问题

二叉树进阶

1.根据二叉树创建字符串

  •  节点的左子树为空,右子树不为空,这种情况的()就不能省略
  • 节点的左子树为不为空,右子树为空,这种情况的()就能省略
  • 节点的左子树为为空,右子树为空,这种情况的()就能省略

2.二叉树的层序遍历 

  •  _levelSize控制每一层的个数

 3.二叉树的层序遍历2

  •  先正向遍历,再通过reserve进行逆置

4.二叉树的最近公共祖先 

  • DFS遍历,再用stack存路径,最后使用回溯 

5.二叉搜索树与双向链表 

  • 中序得到cur,通过prev = cur不断的使prev移动
  • 这里的prev只用一份,所以要用引用

6.从前序与中序遍历序列构造二插树 

  •  前序得到根
  • 中序得到根的左右子树,控制左右区间的范围,

7. 二叉树的前序遍历(非递归)

  •  开始访问一颗树(v.push_back(cur->val);st.push(cur);),
  • 先访问左路节点,再访问左路节点的右子树
  • st.top();一个节点中栈里面出来意味着:这个节点级它的左子树访问完了,还剩右子树
  •  while(cur || !st.empty()),访问最左节点时触发cur,第一个根节点的左子树访问完了,要访问右子树时触发!st.empty()

8.二叉树的中序遍历(非递归实现) 

  • 中序遍历和前序遍历类似,只不过访问v.push_back(cur->val)记录节点的时间不同 

9.二叉树的后序遍历(非递归实现) 

  • v.push_back(cur->val)要记录节点,必须要把它的左右子树访问完
  • 用一个prev指针记录上一次记录的节点值,
  • 一个节点右不为空的情况下
    • if(top->right == nullptr || top->right == prev)
    • 右子树没有访问,访问右子树
    • 右子树已经访问过了,那就访问且记录根节点

1.前K个高频单词 

  •  这里直接使用一个大堆的优先级队列
  • 优先级队列也是可以通过迭代器区间来进行构造
  • 补充:map可以去重,仿函数less需要自己写

 2.两个数组的交集

  •  需要先排序,然后用迭代器或指针或下标进行操作
  • unique(v.begin(),v.end())返回的是去掉重复元素后的最后一个迭代器

  • 排序之后用才能用unique进行去重,它需要配合erase使用

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值