技巧一:理解指针或引用的含义

技巧二:警惕指针丢失和内存泄漏

技巧三:利用哨兵简化实现难度

哨兵结点是不存储数据的。因为哨兵结点一直存在,所以插入第一个结点和插入其他结点,删除最后一个结点和删除其他结点,都可以统一为相同的代码实现逻辑了。

image.png

优化前:

  1. // 在数组a中,查找key,返回key所在的位置
  2. // 其中,n表示数组a的长度
  3. int find(char* a, int n, char key) {
  4. // 边界条件处理,如果a为空,或者n<=0,说明数组中没有数据,就不用while循环比较了
  5. if(a == null || n <= 0) {
  6. return -1;
  7. }
  8. int i = 0;
  9. // 这里有两个比较操作:i<n和a[i]==key.
  10. while (i < n) {
  11. if (a[i] == key) {
  12. return i;
  13. }
  14. ++i;
  15. }
  16. return -1;
  17. }

优化后:

  • 实际上不要这么写, 可读性差
  • 优化掉了 while (i < n)
  1. // 在数组a中,查找key,返回key所在的位置
  2. // 其中,n表示数组a的长度
  3. // 我举2个例子,你可以拿例子走一下代码
  4. // a = {4, 2, 3, 5, 9, 6} n=6 key = 7
  5. // a = {4, 2, 3, 5, 9, 6} n=6 key = 6
  6. int find(char* a, int n, char key) {
  7. if(a == null || n <= 0) {
  8. return -1;
  9. }
  10. // 这里因为要将a[n-1]的值替换成key,所以要特殊处理这个值
  11. if (a[n-1] == key) {
  12. return n-1;
  13. }
  14. // 把a[n-1]的值临时保存在变量tmp中,以便之后恢复。tmp=6。
  15. // 之所以这样做的目的是:希望find()代码不要改变a数组中的内容
  16. char tmp = a[n-1];
  17. // 把key的值放到a[n-1]中,此时a = {4, 2, 3, 5, 9, 7}
  18. a[n-1] = key;
  19. int i = 0;
  20. // while 循环比起代码一,少了i<n这个比较操作
  21. while (a[i] != key) {
  22. ++i;
  23. }
  24. // 恢复a[n-1]原来的值,此时a= {4, 2, 3, 5, 9, 6}
  25. a[n-1] = tmp;
  26. if (i == n-1) {
  27. // 如果i == n-1说明,在0...n-2之间都没有key,所以返回-1
  28. return -1;
  29. } else {
  30. // 否则,返回i,就是等于key值的元素的下标
  31. return i;
  32. }
  33. }

技巧四:重点留意边界条件处理

  • 如果链表为空时,代码是否能正常工作?
  • 如果链表只包含一个结点时,代码是否能正常工作?
  • 如果链表只包含两个结点时,代码是否能正常工作?
  • 代码逻辑在处理头结点和尾结点的时候,是否能正常工作?

技巧五:举例画图,辅助思考

image.png

技巧六:多写多练,没有捷径

精选了 5 个常见的链表操作

  • 单链表反转
  • 链表中环的检测
  • 两个有序的链表合并
  • 删除链表倒数第 n 个结点
  • 求链表的中间结点