剑指offer刷题记录(1)(2022.02.18)
写在前面:没有学过c++,参考解题评论边刷题边学。
数据结构
- 从尾到头打印链表
解法: 直接遍历
题目很简单,很朴素。我们直接从这个链表的头节点开始进行遍历。然后我们记录下这个数组的每个节点的信息。最后反转一下整个数组,返回即可。
代码如下
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) :
* val(x), next(NULL) {
* }
* };
*/
class Solution {
public:
vector<int> printListFromTailToHead(ListNode* head) {
vector<int> ans;
while(head){
ans.push_back(head->val);
head=head->next;
}
reverse(ans.begin(), ans.end());
return ans;
}
};
需要直接遍历长度为n的链表的所有的结点,时间复杂度为O(n)
需要存储长度为n的链表的所有的结点,空间复杂度为O(n)
- 反转链表
此题想考察的是:如何调整链表指针,来达到反转链表的目的。
初始化:3个指针
1)pre指针指向已经反转好的链表的最后一个节点,最开始没有反转,所以指向nullptr
2)cur指针指向待反转链表的第一个节点,最开始第一个节点待反转,所以指向head
3)nex指针指向待反转链表的第二个节点,目的是保存链表,因为cur改变指向后,后面的链表则失效了,所以需要保存
接下来,循环执行以下三个操作
1)nex = cur->next, 保存作用
2)cur->next = pre 未反转链表的第一个节点的下个指针指向已反转链表的最后一个节点
3)pre = cur, cur = nex; 指针后移,操作下一个未反转链表的第一个节点
循环条件,当然是cur != nullptr
循环结束后,cur当然为nullptr,所以返回pre,即为反转后的头结点
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* ReverseList(ListNode* pHead) {
ListNode* cur=pHead;
ListNode* next=nullptr;
ListNode* pre=nullptr;
while(cur){
next=cur->next;
cur->next=pre;
pre=cur;
cur=next;
}
return pre;
}
};
- 合并两个排序的链表
迭代法:
初始化:定义cur指向新链表的头结点
操作:
如果l1指向的结点值小于等于l2指向的结点值,则将l1指向的结点值链接到cur的next指针,然后l1指向下一个结点值
否则,让l2指向下一个结点值
循环步骤1,2,直到l1或者l2为nullptr
将l1或者l2剩下的部分链接到cur的后面
技巧
一般创建单链表,都会设一个虚拟头结点,也叫哨兵,因为这样每一个结点都有一个前驱结点。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* Merge(ListNode* pHead1, ListNode* pHead2) {
ListNode* vhead = new ListNode(-1);
ListNode* cur = vhead;
while(pHead1 && pHead2){
if(pHead1->val<=pHead2->val){
cur->next=pHead1;
pHead1=pHead1->next;
}
else{
cur->next=pHead2;
pHead2=pHead2->next;
}
cur=cur->next;
}
cur->next=pHead1?pHead1:pHead2;
return vhead->next;
}
};
- 两个链表的第一个公共结点
双指针法
这里先假设链表A头结点与结点8的长度 与 链表B头结点与结点8的长度相等,那么就可以用双指针。
初始化:指针ta指向链表A头结点,指针tb指向链表B头结点
如果ta == tb, 说明找到了第一个公共的头结点,直接返回即可。
否则,ta != tb,则++ta,++tb
所以现在的问题就变成,如何让本来长度不相等的变为相等的?
假设链表A长度为a, 链表B的长度为b,此时a != b
但是,a+b == b+a
因此,可以让a+b作为链表A的新长度,b+a作为链表B的新长度。
这样,长度就一致了,可以用上述的双指针解法了。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
ListNode* a=pHead1;
ListNode* b=pHead2;
while(a!=b){
a= a ? a->next:pHead2;
b= b ? b->next:pHead1;
}
return a;
}
};
- 链表中环的入口结点
题目抽象:给定一个单链表,如果有环,返回环的入口结点,否则,返回nullptr
方法一:哈希法
遍历单链表的每个结点
如果当前结点地址没有出现在set中,则存入set中
否则,出现在set中,则当前结点就是环的入口结点
整个单链表遍历完,若没出现在set中,则不存在环
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead) {
unordered_set<ListNode*> st;
while(pHead){
if(st.count(pHead)==0){
st.insert(pHead);
pHead=pHead->next;
}
else
return pHead;
}
return nullptr;
}
};
方法2:快慢指针
通过定义slow和fast指针,slow每走一步,fast走两步,若是有环,则一定会在环的某个结点处相遇(slow == fast),根据分析计算,可知从相遇处到入口结点的距离与头结点与入口结点的距离相同。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead) {
if(!pHead)
return nullptr;
ListNode* fast=pHead;
ListNode* slow=pHead;
while(fast && fast->next){
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
break;
}
if(!fast || !fast->next)
return nullptr;
fast=pHead;
while(fast!=slow){
fast=fast->next;
slow=slow->next;
}
return fast;
}
};