《数据结构与算法之美》06~10笔记

文章目录

关于我的仓库

  • 这篇文章是我为面试准备的学习总结中的一篇
  • 我将准备面试中找到的所有学习资料,写的Demo,写的博客都放在了这个仓库里iOS-Engineer-Interview
  • 欢迎star??
  • 其中的博客在简书,优快云都有发布
  • 博客中提到的相关的代码Demo可以在仓库里相应的文件夹里找到

前言

  • 该系列为学习《数据结构与算法之美》的系列学习笔记
  • 总结规律为一周一更,内容包括其中的重要知识带你,以及课后题的解答
  • 算法的学习学与刷题并进,希望能真正养成解算法题的思维
  • LeetCode刷题仓库:LeetCode-All-In
  • 多说无益,你应该开始打代码了

06讲链表(上):如何实现LRU缓存淘汰算法

  • 常见缓存淘汰策略:先进先出策略FIFO(First In,First Out)、最少使用策略LFU(Least Frequently Used)、最近最少使用策略LRU(Least Recently Used)。
  • 删除指定节点:由于删除节点需要使用前一个节点的next指针,所以对于单链表,在执行这样的删除,插入【前一个插入】的时候,都需要遍历链表
  • 而双向链表对此就很有优势,包括对于有序链表查找,由于可以判定往后还是往前【此处存疑,怎么从中间开始?】
  • 对于执行较慢的程序,可以通过消耗更多的内存(空间换时间)来进行优化;而消耗过多内存的程序,可以 通过消耗更多的时间(时间换空间)来降低内存的消耗。 BFB09C85-132D-417E-A24F-0D8F707D2F59
  • 在实现上使用的是连续的内存空间,可以借助CPU的缓存机制,预读数组中的数据,所以访问效率更高。而 链表在内存中并不是连续存储,所以对CPU缓存不友好,没办法有效预读。CPU在从内存读取数据的时候,会先把读取到的数据加载到CPU的缓存中。而CPU每次从内存读取数据并不是只读取那个特定 要访问的地址,而是读取一个数据块并保存到CPU缓存中,然后下次访问内存数据的时候就会先从CPU缓存开始查找,如果找到就不需要再从内存中取。这样就实现了比内存访问速度更快的机制,也就是CPU缓存存在的意义:为了弥补内存访问速度过慢与CPU执行速度快之间的差异而引入。对于数组来说,存储空间是连续的,所以在加载某个下标的时候可以把以后的几个下标元素也加载到CPU缓存这样执行速度会 快于存储空间不连续的链表存储。
  • 链表本身没有大小的限制,天然地支持动态扩容,我觉得这也是它与数组最大的区 别。如果我们用ArrayList存储了了1GB大小的数据,这个时候已经没有空闲空间了,当我们再插入数据 的时候,ArrayList会申请一个1.5GB大小的存储空间,并且把原来那1GB的数据拷⻉到新申请的空间上。听起来是不是就很耗时?

实现LRU缓存淘汰算法

  • 维护一个单向链表,越靠近尾节点越是远古且不常用
  • 插入数据时:
    • 缓存已满,直接删除尾节点,将新数据插入头节点
    • 缓存不满:
      • 找到的该数据,删除原来的,在头节点加入
      • 找不到,直接在头节点加入

课后题:如何判断一个字符串是否是回文字符串的问题,我想你应该听过,我们今天的思题目就是基于这个问题的改造版本。如果字符串是通过单链表来存储的,那该如何来判断是一个回文串呢?你有什么好的解决思路呢?相应的时间空间复杂度又是多少呢?【LeetCode 234 回文链表】

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if (head == NULL || head->next == NULL) {
      return true;
    }

    ListNode *prev = NULL;
    ListNode *slow = head;
    ListNode *fast = head;

    while (fast != NULL && fast->next != NULL) {
      //pre:前一个
      //slow:慢指针&&后一段链表的开头
      //fast:快指针,边界工具人
      //操作就是要把慢指针经过的逆置过来,所以让pre作为前一个,slow作为当前工具人,使用next去记录下一个,就是slow的下一位
      //变换时,先把前面的逆过来,slow再往下走
      fast = fast->next->next;
      ListNode *next = slow->next;
      slow->next = prev;
      prev = slow;
      slow = next;
    }

    if (fast != NULL) {
      //!=NULL的这个情况就是奇数的情况,此时,slow工具人站在中间,pre就位,因此要让slow往前一个
      slow = slow->next;
    }

    while (slow != NULL) {
      if (slow->val != prev->val) {
        return false;
      }
      slow = slow->next;
      prev = prev->next;
    }

    return true;
    }
};
  • 先通过快慢指针找到中点,此时链表分为两部分,把前半部分逆置,然后比较两列

07讲链表(下):如何轻松写出正确的链表代码

链表六技

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

  • 指针的作用就在于存储对象的内存地址
  • 将某个变量赋值给指针,实际上就是将这个变量的地址赋值给指针,或者反过来说,指针中存储了这个变量的内存地址,指向了这个变量,通过指针就能找到这个变量。
  • p->next=q:p结点中的next指针存储了q结点的内存地址
  • p->next=p->next->next:p结点的next指针存储了p 结点的下下一个结点的内存地址。

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

  • 这一点其实就是由于链表单向性的特征,所以我们需要,我们一旦改变某一个节点的next指向,就会丢失掉原来那个
  • 插入结点时,一定要注意操作的顺序

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

  • 针对链表的插入,删除操作,需要对插入第一个节点和删除最后一个节点的情况进行特殊处理
  • 一个使用哨兵的例子:
// 正常人写的代码
// 在数组a中,查找key,返回key所在的位置 // 其中,n表示数组a的⻓度
int find(char* a, int n, char key) {
   
// 边界条件处理,如果a为空,或者n<=0,说明数组中没有数据,就不用while循环比较了 
  if(a == null || n <= 0) {
   
		return -1;
  }
	int i = 0;
// 这里有两个比较操作:i<n和a[i]==key. 
  while (i < n) {
   
  	if (a[i] == key) {
   
    	return i;
		}
		++i;
	}
	return -1;
}
// 憨憨哨兵代码
// 在数组a中,查找key,返回key所在的位置
// 其中,n表示数组a的⻓度
// 我举2个例子,你可以拿例子走一下代码 //a={4,2,3,5,9,6} n=6key=7 //a={4,2,3,5,9,6} n=6key=6 
int find(char* a, int n, char key) {
   
	if(a == null || n <= 0) {
    
    return -1;
	}
// 这里因为要将a[n-1]的值替换成key,所以要特殊处理这个值 
  if (a[n-1] == key) {
   
		return n-1;
  }
// 把a[n-1]的值临时保存在变量tmp中,以便之后恢复。tmp=6。 
// 之所以这样做的目的是:希望find()代码不要改变a数组中的内容 char tmp = a[n-1];
// 把key的值放到a[n-1]中,此时a = {4, 2, 3, 5, 9, 7} 
  a[n-1] = key;
	int i = 0;
// while 循环比起代码一,少了i<n这个比较操作 
  while (a[i] != key) {
   
		++i;
  }
// 恢复a[n-1]原来的值,此时a= {4, 2, 3, 5, 9, 6} 
  a[n-1] = tmp;
	if (i == n-1) {
   
// 如果i == n-1说明,在0...n-2之间都没有key,所以返回-1 
    return -1;
	} else {
   
// 否则,返回i,就是等于
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值