搬家浪费了几天时间,从进行开始继续更新。
首先还是Leetcode:
leetcode:24 两两交换列表
首先虚拟节点是个好东西,可以避免许多头结点的边界问题,应当多用!
思考一下如果需要交换两个节点,比如交换23,则需要知道1,2,3,4四个节点的内存地址。所以非常自然地定义四个ListNode类型的指针变量。分别存储连续的四个地址。交换其实比较简单。2连接4,3链接2,1链接3,最后记得把指针变量2,3交换一下换为原来的顺序,方便下一次交换。
边界问题:需要思考能否移动,在我们这样的算法里面,每一次交换的始终是p2和p3节点,因此只要p3不为null则可以进行交换。下一次交换的条件就是,p3所要到的地方(p4->next)不为空。但是值得注意的是,使用p4->next的时候需要确保p4不是一个野指针,所以应当增加判断。
ListNode* swapPairs(ListNode* head) {
if(head == nullptr) return head;
if(head->next == nullptr) return head;
ListNode* v_node = new ListNode(-101);
ListNode* p1 = v_node;
ListNode* p2 = head;//左边指针
ListNode* p3 = head->next;//右边指针
ListNode* p4 = head->next->next;
while(p3 != nullptr)
{
p2->next = p4;
p1->next = p3;
p3->next = p2;
ListNode* tmp;
tmp = p2;
p2 = p3;
p3 = tmp;
if(p4 == nullptr || p4->next == nullptr)
{
break;
}else{
p1 = p1->next->next;
p2 = p2->next->next;
p3 = p3->next->next;
p4 = p4->next->next;
}
}
return v_node->next;
}
leetcode 25 k个一组反转链表
这道题目有点困难,主要需要考虑一下几点,首先需要考虑到剩余节点是否够k个节点能够成为一组进行反转,然后反转的过程也就不多说了。每一组之间如何连接,这里我设计了pair对来记录这个信息,然后再对其进行连接。代码如下:
ListNode* reverseKGroup(ListNode* head, int k) {
vector<pair<ListNode*,ListNode*>> link_together;
int nums = 0; //已经反转的节点
//注意最后一组如果节点数不足k则不进行反转
if(k == 1) return head;
if(head == nullptr || head->next == nullptr) return head;
//确保至少有两个元素
ListNode* p2 = head->next;
ListNode* p3 = head->next->next;
//ListNode* v = new ListNode(-101);
ListNode* p1 = head;
//需要一个侦察兵
ListNode* detect = head;
while(detect!=nullptr)
{
ListNode* tail = p1;
nums+=1;
detect = detect->next;
if(nums%k == 0)
{
while(--nums)
{
//每完成一次元素的反转nums--1;
p2->next = p1;
if(p1->next == p2) p1->next = nullptr;
p1 = p2;
p2 = p3;
if(p3!= nullptr ) p3 = p3->next;
}
ListNode* head = p1;
p1 = p2;
p2 = p3;
if(p3!=nullptr) p3 = p3->next;
link_together.push_back({head,tail});
}
}
for(int i = 0 ; i < link_together.size()-1;i++)
{
link_together[i].second->next = link_together[i+1].first;
}
link_together[link_together.size()-1].second->next = p1;
return link_together[0].first;
}
官方题解里面有一个很巧妙的递归解法,可以看到这个问题可可以被划分为递归问题,不仅仅是树的结构可以巧妙地运用递归,类似于这样也行,还需要积累。
class Solution {
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) {
return head;
}
ListNode newHead = head.next;
head.next = swapPairs(newHead.next);
newHead.next = head;
return newHead;
}
}
。
leetcode 138
我的方法过于粗劣了就不展示了,直接进行了模拟,费事费力,在这里默写一下官方的题解
unordered_map<Node*, Node*> cachedNode;
Node* copyRandomList(Node* head) {
if(head == NULL) return NULL;
//如果
if(!cachedNode.count(head))
{
Node* headNew = new Node(head->val);
cachedNode[head] = headNew;
headNew->next = copyRandomList(head->next);
headNew->random = copyRandomList(head->random);
}
return cachedNode[head];
}
对某种结构的子结构做某种相同的操作都可以运用这种方法。
今天开始学习极客时间06 MySQL实战45讲。
1. 一条SQL查询语句如何执行的:
这里主要讲讲MySQL服务端发生了什么:
首先是连接器:MySQL是一个Client-Sever架构的软件系统,连接器只要管理和客户端的连接状态和请求。首先是进行密码检查,完事后从库里面读取该用户的权限,值得注意的是一旦连接完成权限会持续到下一次连接,在此期间管理员改变了什么都没用。
长连接和短连接的问题:
这个问题,是由于长时间的长连接使得内存占用过大,可以主动断开连接,或者执行特定语句,初始化连接资源。
查询缓存:顾名思义通过sql语句去缓存中查询是否有相关的结果,有的话直接返回没有再去查询。但是MySQL作为一个业务型数据来讲更新频率很高,缓存使用率不是很高,因此在8.5后缓存已经被弃用。
分析器: 词法分析,挑出关键词,以及比较列名。
优化器: 优化查询路径
执行器:首先是查看当前用户对于该表是否具备权限,调用引擎接口进行查询。
2.一条更新语句如何执行?
首先当一个表有任何更新的时候这个缓存就会失效,这也就注定了MySQL的缓存效率不高。在数据库的更新操作中主要的内容是两个日志:
redo log日志:innoDB独有的日志系统
这个非常经典的图,redo log是先在内存中记录每一次的更新操作,注意他所记录的是物理逻辑上的更新,而不是SQL语句,当内存快满的时候就开始往磁盘写入,或者不忙的时候再写。这个能力叫做crash-safe.
bin log: sever层面的操作日志
日志有以下三点不同。
-
redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。
-
redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。
-
redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志
为了保证两个日志的记录一致性,redolog采用两阶段方式提交,先prepare之后写binlog,binlog写完之后再写redo变成commit状态。
一般用binlog来进行数据库恢复,这是由于Binlog追加写,能储存更长时间的信息。
3. 数据库的事务隔离
三个重要的并行问题:脏读,不可重复读,幻读
四个隔离级别:读未提交(没有任何锁),读已提交(写锁),可重复读(读写锁),串行化(大范围的锁,或者完全串行执行)。
MVCC:多版本并行控制
数据库采用了这样一个多版本控制的策略进行并行管理,具体来说,数据库每一行操作都有多个版本,每一个事务去访问这个版本的时候都会获得一个版本号,然后将自己的版本号和当前活跃的事务版本号进行对比有以下结果。
1.我可以访问当前所有活跃事务的操作,则是读未提交的级别。
2.我只可以访问dead事务的版本,这就是一个读已提交。
3.在2的基础上同时别人不能修改活跃版本的数据,可重复读
4.在3的基础上,加上next-key这样的表锁,串行化
如果当前事务失败了,则回滚回上一事务
一致性原理:大家看到的顺序一样,对于一个顺序,我的最后的操作序没变。
长事务引发的常见危害有:
-
数据库连接池被占满,应用无法获取连接资源;
-
容易引发数据库死锁;
-
数据库回滚时间长;
-
在主从架构中会导致主从延时变大。
长事务往往伴随着大颗粒度和对较多资源的占有,就会导致死锁。