左程云算法基础班整理(二)
本系列文章是整理左程云算法基础班的视频,将Java代码翻译成C++代码。并且记录一些核心思想
1.单链表
struct ListNode
{
int val;
ListNode* next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode* next) : val(x), next(next) {}
};
2.双链表
struct DoubleListNode
{
int val;
DoubleListNode* next;
DoubleListNode* prev;
DoubleListNode():val(0),next(nullptr),prev(nullptr){}
DoubleListNode(int x):val(x),next(nullptr),prev(nullptr){}
DoubleListNode(int x,DoubleListNode* next,DoubleListNode* prev):val(x),next(next),prev(prev){}
};
1.反转链表
翻转单链表
首先是翻转单链表,链表题全是coding题,因此一定要细心,谨慎。
反转链表,(1)先记录环境,之后再设置头部两个指针的指向。
(2)pre和head都向右移。
内部的结构一般是首尾相连的。next->head.next->pre->head
ListNode* reverseLinkedList(ListNode* head)
{
ListNode* next = nullptr; //记前面的环境
ListNode* pre = nullptr; //记下面的环境
while (head)
{
//完成翻转
next = head->next;
head->next = pre;
//为了循环,往后移
pre = head;
head = next;
}
return pre;
}
翻转双链表
DoubleListNode* reverseDoubleLinkedList(DoubleListNode* head)
{
DoubleListNode* next = nullptr;
DoubleListNode* pre = nullptr;
while (head)
{
//记一下环境
next = head->next;
//翻转的核心,head的下指针往前指,前指针往后指
head->next = pre;
head->prev = next;
//为了循环
pre = head;
head = next;
}
return pre;
}
2.删除链表中指定的值(剑指offer第18题)
讲解时间,第二课45分钟左右开始讲解。
返回值要是一个节点,因为在删除时,是有可能将头结点删除掉的。
#include <iostream>
using namespace std;
struct ListNode
{
int value;
ListNode* next;
ListNode():value(0),next(nullptr){}
ListNode(int x):value(x),next(nullptr){}
ListNode(int x, ListNode* next) :value(x), next(next) {};
};
ListNode* DeleteGivenValue(ListNode* head,int num)
{
//先看看头部要删多少,找到第一个不需要删的节点,当做头。
while (head!=nullptr)//这一个是来遍历
{
if (head->value != num)//当不需要删的时候,break出来,此时的head节点就是将来要返回的节点。
{
break;
}
head = head->next;
}
//head此时来到 第一个不需要删的位置。因此可以将其保存起来,另申请一个指针用来遍历。
ListNode* pre = head;//记录pre是为了要找到前一个节点的向下的指针
ListNode* cur = head;//申请个cur变量,此时是第一个不需要删的位置,因此head就可以不用动了,遍历的时候只需要动cur变量即可。
while (cur != nullptr)
{
if (cur->value == num)
{
pre->next = cur->next;
}
else {
pre = cur;//这里直接pre后移的话就产生错误。
}
cur = cur->next;
}
return head;
}
c++的节点要自己delete掉,要不然会发生内存泄露。
但是翻遍了各大题解,都没有进行delete掉该节点的题解。。。。。。
3.双向链表实现栈和队列
#include<iostream>
using namespace std;
template<class T>
struct DoubleLinkList
{
T val;
DoubleLinkList* prev;
DoubleLinkList* next;
DoubleLinkList():val(0),prev(nullptr),next(nullptr){}
DoubleLinkList(T x):val(x),prev(nullptr),next(nullptr){}
DoubleLinkList(T x,DoubleLinkList* next,DoubleLinkList* prev):val(x),next(next),prev(prev){}
};
template<class T>
class DoubleEndsQueue
{
public:
void addFromHead(T value)
{
DoubleLinkList* cur = new DoubleLinkList(value);//待加入的节点
if (head == nullptr)//之前为空
{
head = cur;
tail = cur;
}
else//
{
cur->next = head;
head->prev = cur;
head = cur;
}
}
void addFromBottom(int value)
{
DoubleLinkList* cur = new DoubleLinkList(value);
if (head == nullptr)
{
head = cur;
tail = cur;
}
else
{
cur->prev = tail;
tail->next = cur;
tail = cur;
}
}
T popFromHead()
{
if (head == nullptr)
{
return nullptr;
}
DoubleLinkList* cur = head;//申请一个指针,指向头结点
if (head == tail)//只有一个节点,那出去一个就变成没有节点了,直接都置空
{
head = nullptr;
tail = nullptr;
}
else
{
head = head->next;
head->prev = nullptr;
cur->next = nullptr;
}
return cur->val;
}
T popFromBottom()
{
if (head == nullptr)
{
return nullptr;
}
DoubleLinkList* cur = tail;
if (head == tail)
{
head = nullptr;
tail = nullptr;
}
else
{
tail = tail->prev;
tail->next = nullptr;
cur->prev = nullptr;
}
return cur->val;
}
bool isEmpty()
{
return head == nullptr;
}
private:
//所有数据的头和尾,两个节点指针
DoubleLinkList* head;
DoubleLinkList* tail;
};
template<class T>
class MyStack
{
public:
MyStack()
{
queue = new DoubleEndsQueue();
}
void push(int value)
{
queue->addFromHead(value);
}
T pop()
{
return queue->popFromHead();
}
bool isEmpty()
{
return queue->isEmpty();
}
private:
DoubleEndsQueue* queue;
};
template<class T>
class MyQueue
{
public:
MyQueue()
{
queue = new DoubleEndsQueue();
}
T poll()
{
return queue->popFromBottom();
}
void push()
{
return queue->addFromHead();
}
bool isEmpty()
{
return queue->isEmpty();
}
private:
DoubleEndsQueue* queue;
};
4.怎么用数组实现不超过固定大小的队列和栈
栈:正常使用
#include<iostream>
#include<assert.h>
#include<array>
using namespace std;
//用数组实现栈
class MyStack
{
public:
MyStack(int limit)
{
this->limit = limit;
arr = new int[limit];
index = 0;
}
~MyStack()
{
delete[] arr;
}
void push(int val)
{
if (!isFullStack())
{
arr[index++] = val;
}
else
{
cout << "the stack is full !" << endl;
assert(0);
}
}
void pop()
{
if (!isEmpty())
{
index--;
}
else
{
cout << "the stack is empty !" << endl;
assert(0);
}
}
int top()
{
if (!isEmpty())
{
return arr[index];
}
else
{
cout << "the stack is empty !" << endl;
assert(0);//如果栈为空那么必须终止程序,reuturn 返回的会是乱码,push和pop不需要这个操作是因为他们没有返回值,所以如果不满足执行的条件只需要在终端上显示出现异常就行。
}
}
bool isEmpty() const
{
return index == 0;
}
bool isFullStack() const
{
return index == 9;
}
private:
int limit;
int* arr;
int index;
};
int main()
{
MyStack stack(3);
cout << stack.isEmpty() << endl;
//cout << stack.top() << endl;
stack.push(5);
cout << stack.isEmpty() << endl;
int b = stack.top();
cout << b << endl;
cout << stack.isEmpty() << endl;
/*
MyQueue queue(3);
cout << queue.isEmpty() << endl;
queue.push(2);
cout << queue.isEmpty() << endl;
queue.push(1);
queue.push(9);
*/
system("pause");
return 0;
}
队列:环形数组
精髓点在于,不是根据pushi和polli的相对位置关系讨论能否进出队,而是用size值。多一个变量,省好多事。
class MyQueue
{
public:
MyQueue(int limit)
{
this->limit= limit;
arr = new int[this->limit];
size = 0;
pushi = 0;
polli = 0;
}
~MyQueue()
{
delete[] arr;
}
void push(int value)
{
if (size == this->limit)
{
cout << "栈满了,不能再加了!!!" << endl;
assert(0);
}
else
{
size++;
arr[pushi] = value;
pushi = this->nextIndex(pushi);
}
}
int pop()
{
if (size == 0)
{
cout << "栈为空,不能再拿了!!!" << endl;
assert(0);
}
size--;
int ans = arr[polli];
polli = this->nextIndex(polli);
}
bool isEmpty() const
{
return size == 0;
}
int nextIndex(int i)
{
return i < this->limit - 1 ? i + 1 : 0;
}
private:
int* arr;
int limit;
int pushi;//进来的时候放哪,坐标
int polli;//出去的时候放哪,坐标
int size;//因为是环形数组,所以不用考虑pushi和polli的位置,只要size不为0,就是里面有值,也就是没追上
};
int main()
{
/*
MyStack stack;
cout << stack.isEmpty() << endl;
//cout << stack.top() << endl;
stack.push(5);
cout << stack.isEmpty() << endl;
int b = stack.top();
cout << b << endl;
cout << stack.isEmpty() << endl;
*/
MyQueue queue(3);
cout << queue.isEmpty() << endl;
queue.push(2);
cout << queue.isEmpty() << endl;
queue.push(1);
queue.push(9);
system("pause");
return 0;
}
5.最小栈(leetcode155题)
实现一个特殊的栈,在基本功能的基础上,再实现返回栈中最小元素的功能
1.pop、push、getMin操作的时间复杂度都是O(1)
2.设计的栈类型可以使用现成的栈结构
这道题很简单,使用辅助栈即可。
class MinStack {
public:
MinStack() {
help.push(INT_MAX);
}
void push(int val) {
a.push(val);
int mini = min(val,help.top());
help.push(mini);
}
void pop() {
a.pop();
help.pop();
}
int top() {
return a.top();
}
int getMin() {
return help.top();
}
stack<int> a;
stack<int> help;
};
6.用栈实现队列
队列是先进先出的,栈是先进后出的。
两个栈倒一下,顺序就和队列是一样的了。
可以分成一个主栈,一个专门用来进行倒的辅助栈。
再倒的时候要注意一些条件:
1.辅助栈为空,才能往里倒,里面还有数据则不能倒
2.往辅助栈里倒就直接倒完,不能倒一半。
class MyQueue {
public:
stack<int> a;
stack<int> help;
MyQueue() {
}
void helpToA()//倒的函数
{
while(!help.empty())//倒就要倒干净
{
a.push(help.top());
help.pop();
}
}
void push(int x) {
help.push(x);
}
int pop() {
while(a.empty())//主栈空了才能往里倒
{
helpToA();
}
int r = a.top();
a.pop();
return r;
}
int peek()
{
while(a.empty())//主栈空了才能往里倒
{
helpToA();
}
int r = a.top();
return r;
}
bool empty() {
return a.empty()&&help.empty();
}
};
7.用队列实现栈
左神原话,这题很好想,和弱智一样。。。。。。但是我就没想起来。。。。
两个队列,都是从头进,从尾出。
两个队列,一个辅助队列,一个主队列,主队列存储所有数据,辅助队列只是帮助进队列所用,所以最终形态里辅助队列是没有值的,因此直接看主队列有没有值就可以判空了。
下一次help变data,data变help。
class MyStack {
public:
queue<int> a;
queue<int> help;
MyStack() {
}
void push(int x) {
help.push(x);//先压入辅助队列
while(!a.empty())//当主队列不空时
{
help.push(a.front());//往辅助队列中压入主队列的对头
a.pop();//主队列队头出去
}
swap(a,help);//主队列为空,则直接交换辅助队列和主队列
}
int pop() {
int r = a.front();
a.pop();
return r;
}
int top() {
int r = a.front();
return r;
}
bool empty() {
return a.empty();
}
};
8. 递归方法实现求数组中最大值(用递归实现二分,递归初体验)
#include<iostream>
using namespace std;
int BSsearchmax(int a[], int L, int R)
{
if (L == R)//递归要写递归终止条件
{
return a[L];
}
int mid = L + ((R - L) >> 1);
return max(BSsearchmax(a, L, mid), BSsearchmax(a, mid + 1, R));//递归要划分子分支调用递归函数
}
int main()
{
int a[9] = { 0,1,3232,41,2121,41,2121,4232,111 };
cout << BSsearchmax(a, 0, 8) << endl;;
return 0;
}
9.哈希表的基本使用(c++)
9.1 头文件
#include<unordered_map>
9.2 定义一个哈希表
假设键和值都是int型的
unordered_map<int,int> a;
9.3 建立
哈希表的建立有3种方法
a[1]=3;
hash.insert<make_pair(1,3)>;
hash.insert({{1,3},{2,4}});
9.4 迭代器
unordered_map<int,int>::iterator it;
9.5 利用迭代器访问键或者值
it->first;//访问键
it->second;//访问值
9.6查找
注意,find函数返回的是元素位置所在的迭代器,如果没找到,则返回的是a.end();
it=a.find(1);
9.7包含
包含返回的是int值,记录找到的元素的个数。注意contain函数是string用的,哈希表和vector没有contain
a.count(1);
9.8修改哈希表
a[1]=4;
9.9清除哈希表
清除哈希表要注意迭代器失效的问题
a.erase(1);