剑指 Offer 09.用两个栈实现队列
class CQueue {
public:
CQueue() {
}
void appendTail(int value) {
stk1.push(value);
}
int deleteHead() {
if(stk2.empty())
{
if(stk1.empty()) return -1;
while(!stk1.empty())
{
int value1=stk1.top();
stk1.pop();
stk2.push(value1);
}
}
int value2=stk2.top();
stk2.pop();
return value2;
}
private:
stack<int> stk1;
stack<int> stk2;
};
说明:
deleteHead说明:stk2是用来反向存储stk1中的数据,因此可以保证stk2的top()元素就是Head元素。
所以算法的流程就是:
1.如果stk2为空,则将stk1中的元素pop到stk2中;
2.如果此时stk2仍未空,对应于stk1为空的情况,则返回-1;
3.如果stk2不为空,意味着之前就将stk1中的所有数据push到了stk2中,因此stk2的top()元素就是Head元素。
为了方便理解,可以将deleteHead写成:
删除元素对应方法 deleteHead
如果 stk2 为空,则将 stk1 里的所有元素弹出插入到 stk2 里
如果 stk2 仍为空,则返回 -1,否则从 stk2 弹出一个元素并返回
int deleteHead() {
/* 只有当输出栈为空的情况下才将输入栈的数据全部导入到输出栈 */
if(stk2.empty()) {
while(!stk1.empty()) {
stk2.push(stk1.top());
stk1.pop();
}
}
/* 如果栈为空输出-1 */
if (stk2.empty()) {
return -1;
}
else {
/* 输出栈获取输出的元素值 */
int result = stk2.top();
/* 获取值后将此元素弹出 */
stk2.pop();
return result;
}
}
参考:
剑指 Offer 30. 包含min函数的栈
剑指 Offer 30. 包含min函数的栈
class MinStack {
public:
/** initialize your data structure here. */
MinStack() {
min_stack.push(INT_MAX);//此处需要push进来最大的INT_MAX
}
void push(int x) {
stk.push(x);
min_stack.push(::min(min_stack.top(),x));
}
void pop() {
stk.pop();
min_stack.pop();
}
int top() {
return stk.top();
}
int min() {
return min_stack.top();
}
private:
stack<int> stk;
stack<int> min_stack;
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(x);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->min();
*/
说明:
定义两个stack,分别为stk和min_stack。
stk用来存储所有的元素。
min_stack只用来存储最小的元素,将新的元素x和min_stack的top()元素进行比较,如果x更小,才会push进min_stack。因此需要定义INT_MAX进行比较,如下:
MinStack()
{
min_stack.push(INT_MAX);//此处需要push进来最大的INT_MAX
}
缺少本行会报错:
Line 175: Char 16: runtime error: reference binding to misaligned address 0xbebebebebebec0ba for type 'int', which requires 4 byte alignment (stl_deque.h)
0xbebebebebebec0ba: note: pointer points here
<memory cannot be printed>
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/../lib/gcc/x86_64-linux-gnu/9/../../../../include/c++/9/bits/stl_deque.h:180:16
剑指 Offer 35. 复杂链表的复制
剑指 Offer 35. 复杂链表的复制
方法1:哈希表
class Solution {
public:
Node* copyRandomList(Node* head) {
if(head==nullptr) return head;
unordered_map<Node*,Node*> umap;
Node* cur=head;
//第一次遍历,初始化哈希表
//复制各节点,并建立 “原节点 -> 新节点” 的 unordered_map 映射
while(cur!=nullptr)
{
umap[cur]=new Node(cur->val);
cur=cur->next;
}
//第二次遍历,定义random和next。
cur=head;
while(cur!=nullptr)
{
umap[cur]->next=umap[cur->next];
umap[cur]->random=umap[cur->random];
cur=cur->next;
}
return umap[head];
}
};
利用哈希表的查询特点,考虑构建 原链表节点 和 新链表对应节点 的键值对映射关系,再遍历构建新链表各节点的 next 和 random 引用指向即可。
算法流程:
- 若头节点 head 为空节点,直接返回 null ;
- 初始化: 哈希表 umap, 节点 cur 指向头节点;
- 复制链表:
- 建立新节点,并向 umap添加键值对 (原 cur 节点, 新 cur 节点) ;
- cur 遍历至原链表下一节点;
- 构建新链表的引用指向:
- 构建新节点的 next 和 random 引用指向;
- cur 遍历至原链表下一节点;
- 返回值: 新链表的头节点 umap[cur] ;
复杂度分析:
时间复杂度 O(N) : 两轮遍历链表,使用 O(N)时间。
空间复杂度 O(N) : 哈希表 umap使用线性大小的额外空间。
方法2:官方解答(哈希表+回溯)
class Solution {
public:
unordered_map<Node*, Node*> umap;
Node* copyRandomList(Node* head) {
if (head == nullptr) {
return nullptr;
}
if (!umap.count(head)) {
Node* headNew = new Node(head->val);
umap[head] = headNew;
headNew->next = copyRandomList(head->next);
headNew->random = copyRandomList(head->random);
}
return umap[head];
}
};
本题要求我们对一个特殊的链表进行深拷贝。如果是普通链表,我们可以直接按照遍历的顺序创建链表节点。而本题中因为随机指针的存在,当我们拷贝节点时,「当前节点的随机指针指向的节点」可能还没创建,因此我们需要变换思路。一个可行方案是,我们利用回溯的方式,让每个节点的拷贝操作相互独立。对于当前节点,我们首先要进行拷贝,然后我们进行「当前节点的后继节点」和「当前节点的随机指针指向的节点」拷贝,拷贝完成后将创建的新节点的指针返回,即可完成当前节点的两指针的赋值。
具体地,我们用哈希表记录每一个节点对应新节点的创建情况。遍历该链表的过程中,我们检查「当前节点的后继节点」和「当前节点的随机指针指向的节点」的创建情况。如果这两个节点中的任何一个节点的新节点没有被创建,我们都立刻递归地进行创建。 当我们拷贝完成,回溯到当前层时,我们即可完成当前节点的指针赋值。注意一个节点可能被多个其他节点指向,因此我们可能递归地多次尝试拷贝某个节点,为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针并返回即可。
在实际代码中,我们需要特别判断给定节点为空节点的情况。
复杂度分析
时间复杂度:O(n),其中 n 是链表的长度。对于每个节点,我们至多访问其「后继节点」和「随机指针指向的节点」各一次,均摊每个点至多被访问两次。
空间复杂度:O(n),其中 n 是链表的长度。为哈希表的空间开销。
解法3:暴力解法
// my own answer
class Solution {
public:
//tool函数,找到cur的random节点在head为头结点的链表中的位置。
//newHead移动一样的count即可。返回移动之后的newHead。
Node* tool(Node* head,Node* newHead,Node* cur)
{
if(cur->random==nullptr) return nullptr;
Node* goal=cur->random;
int count=0;
while(head!=goal)
{
++count;
head=head->next;
}
while(count--) newHead=newHead->next;
return newHead;
}
Node* copyRandomList(Node* head) {
if(head==nullptr) return head;
Node* newHead=new Node(head->val);
Node* tmp=newHead;//保存newHead
Node* cur=head;//保存head
//遍历链表,使得newHead链表的next正确。复制next节点。
while(head!=NULL&&head->next!=NULL)
{
head=head->next;//此处head会进行改变
Node* tmp1=new Node(head->val);
tmp->next=tmp1;
tmp=tmp1;
}
//在遍历一遍复制random节点。
tmp=newHead;
head=cur;
while(cur)
{
tmp->random=tool(head,newHead,cur);//找到原来的链表中cur的random
cur=cur->next;
tmp=tmp->next;
}
return newHead;
}
};