一、仿写vector
class Array {
public:
Array(int size = 10):capacity(size), cur(0) {
p_arr = new int[size]();
}
~Array() {
// 若只是释放,那p_arr还是指向堆内存,成为野指针
// 没必要if(p_arr == nullptr),因为delete nullptr就是空操作,不报错
delete[] p_arr;
p_arr = nullptr;
}
void push_back(int val) {
if (cur == capacity) {
expand(capacity);
}
p_arr[cur++] = val;
}
void pop_back() {
if (cur == 0) {
return;
}
cur--;
}
// 按位置插入
void insert(int pos, int val) {
if (pos < 0 || pos > cur) {
throw "pos out of range";
}
if (cur == capacity) {
expand(capacity);
}
for (int i = cur - 1; i >= pos; i--) {
p_arr[i + 1] = p_arr[i];
}
p_arr[pos] = val;
cur++;
}
// 按位置删除
void erase(int pos) {
if (pos < 0 || pos >= cur) {
throw "pos out of range";
}
for (int i = pos + 1; i < cur; i++) {
p_arr[i-1] = p_arr[i];
}
cur--;
}
// 按值查找,返回索引
int find(int val) {
for (int i = 0; i < cur; i++) {
if (p_arr[i] == val) {
return i;
}
}
return -1;
}
void show() {
for (int i = 0; i < cur; i++) {
cout << p_arr[i] << " ";
}
cout << endl;
}
private:
void expand(int size) {
int* tmp_arr = new int[2*size]();
// 不涉及深拷贝可以使用memcpy
// memcpy(tmp_arr, p_arr, sizeof(int) * capacity);
for (int i = 0; i < cur; i++) {
tmp_arr[i] = p_arr[i];
}
delete[] p_arr;
p_arr = tmp_arr;
capacity = size;
}
int* p_arr;
int capacity;
int cur; // 数组内元素个数
};
二、数组中偶数放左边,奇数放右边
// 偶数放在左边,奇数放在右边
void adjust_array(int arr[], int size) {
int* left = arr;
int* right = arr + size - 1;
while (left < right) {
while (left < right && (*left & 0x1) == 0) {
left++;
}
while (left < right && (*right & 0x1) != 0) {
right--;
}
// 不写if程序结果也正确,如果不写if,left==right时也会进行交换
// 不写if,程序在交换时满足left<=right
if (left < right) {
int tmp = *left;
*left = *right;
*right = tmp;
left++;
right--;
}
}
}
三、移除指定元素
// 移除指定元素,返回长度
int removeElement(vector<int>& nums, int val) {
int left = 0;
int right = nums.size() - 1;
while (left <= right) {
if (nums[right] == val) {
right--;
continue;
}
if (nums[left] == val) {
nums[left] = nums[right];
right--;
}
left++;
// right找到非val的元素后,无论left是否为val,都要往后移动
// 不是val,往后找是val的位置;是val,被覆盖后,继续往后查找
}
return left;
}
/*
int removeElement(vector<int>& nums, int val) {
int left = 0;
int right = nums.size() - 1;
while(left <= right){
while(left <= right && nums[right] == val){
right--; // 找值不为val的位置
}
if(left > right){
break;
}
// 如果不是因为越界,那就是因为nums[right] != val
if(nums[left] == val){
// 在这里,left指向的一定是val,right指向的一定不是val
// 所以一定有left < right
nums[left] = nums[right];
right--;
}
// 所以执行left++前一定不会有left>right
left++; // nums[left]被覆盖或者是往后寻找值为val的位置
}
return left;
}
*/
四、关于普通单链表
1. p->next != nullptr
和 p != nullptr
查找尾结点写法,只有查找尾结点的时候才判断next
Node* p = head;
while(p->next != nullptr){
p = p->next;
}
遍历链表写法,遍历的时候判断当前结点即可
Node* p = head->next;
while(p != nullptr){
p = p->next;
}
2. 删除一个值为val的结点
bool remove_one_val(){
Node* pre = head;
Node* cur = head->next;
while(cur != nullptr){
if(cur->data == val){
pre->next = cur->next;
delete cur;
return true; // 删除
}else{
pre = cur;
cur = cur->next;
}
}
return false; // 没删除
}
3. 删除所有值为val的结点
bool remove_all_val(int val) {
bool is_del = false;
Node* pre = head;
Node* cur = head->next;
while (cur != nullptr) {
if (cur->data == val) {
pre->next = cur->next;
delete cur;
is_del = true;
// 这里pre不可能为空,只有pre->next可能为空
cur = pre->next;
}
else {
pre = cur;
cur = cur->next;
}
}
return is_del;
}
4. 销毁整个链表
void destroy(){
Node* del = head;
while(nullptr != del){
head = head->next;
delete del;
del = head;
}
}
5. 不带头结点,删除倒数第n个结点
ListNode* removeNthFromEnd(ListNode* head, int n) {
if (n < 1) {
return head;
}
ListNode* pre = head;
ListNode* cur = head;
for (int i = 1; i <= n; i++) {
cur = cur->next;
if (nullptr == cur) {
// 如果cur为nullptr,说明n太大
// 然而在leetcode中规定1 <= n <= <结点数目>
// 所以只有当 n==结点数目 的时候,cur为nullptr
// 删除倒数第<结点数目>个,表示删除第一个结点
return head->next;
}
}
while (nullptr != cur->next) {
// cur指向末尾结点时,pre->next即为待删除结点
cur = cur->next;
pre = pre->next;
}
pre->next = pre->next->next;
return head;
}
6. 带头结点删除倒数第n个结点
ListNode* removeNthFromEnd(int n) {
if (n < 1) {
return head;
}
Node* pre = head;
Node* del = head;
Node* cur = head;
for (int i = 1; i <= n; i++) {
cur = cur->next;
if (nullptr == cur) {
return head;
}
}
while (nullptr != cur) {
cur = cur->next;
pre = del;
del = del->next;
}
pre->next = del->next;
delete del;
return head;
}
7. 不带头结点,合并两个有序链表
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode* new_head = new ListNode();
ListNode* last = new_head;
while(l1 != nullptr && l2 != nullptr){
if(l1->val > l2->val){
last->next = l2;
l2 = l2->next;
last = last->next;
}else{
last->next = l1;
l1 = l1->next;
last = last->next;
}
}
last->next = (l1 == nullptr) ? l2 : l1;
return new_head->next;
}
8. 判环
bool hasCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != nullptr && fast->next != nullptr){
fast = fast->next->next;
slow = slow->next;
if(fast == slow){
return true;
}
}
return false;
}
9. 判环且返回入口地址
ListNode *detectCycle(ListNode *head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast != nullptr && fast->next != nullptr){
fast = fast->next->next;
slow = slow->next;
if(fast == slow){
fast = head;
while(fast != slow){
fast = fast->next;
slow = slow->next;
}
return fast;
}
}
return nullptr;
}
10. 判断链表是否相交
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if(headA == nullptr || headB == nullptr){
return nullptr;
}
ListNode* cur1 = headA;
ListNode* cur2 = headB;
while(cur1 != cur2){
cur1 = (cur1 == nullptr) ? headB : cur1->next;
cur2 = (cur2 == nullptr) ? headA : cur2->next;
}
// 就算没有交点,cur1和cur2同时变成nullptr
return cur1;
}
11. 旋转链表
ListNode* rotateRight(ListNode* head, int k) {
if(head == nullptr || k == 0){
return nullptr;
}
int len = 1;
ListNode* tail = head;
while(tail->next != nullptr){
len++;
tail = tail->next;
}
tail->next = head;
// 实际旋转的个数
k = k % len;
// 找到倒数第k+1个结点
ListNode* cur = head;
for(int i=1; i<=k; i++){
cur = cur->next;
}
ListNode* new_tail = head;
// 当cur指向尾结点的时候,new_tail指向需要断开的地方
while(cur != tail){
cur = cur->next;
new_tail = new_tail->next;
}
ListNode* head = new_tail->next;
new_tail->next = nullptr;
return head;
}
五、单向循环链表
约瑟夫环
// 无头节点,第k个人开始报数,报到m出列,打印出列顺序
void joseph(Node* head, int k, int m) {
Node* cur = head;
Node* pre = cur;
// 使pre的next一直是cur
while (pre->next != head) {
pre = pre->next;
}
// 从第k个开始报数
for (int i = 1; i < k; i++) {
pre = cur;
cur = cur->next;
}
while (true) {
// 报数
for (int i = 1; i < m; i++) {
pre = cur;
cur = cur->next;
}
pre->next = cur->next;
cout << cur->data << " ";
delete cur;
cur = pre->next;
if (cur == pre) {
cout << cur->data << " ";
delete cur;
cur = nullptr;
break;
}
}
}
六、栈
1. 顺序栈
class SeqStack {
public:
SeqStack(int size = 10) {
mstack = new int[size];
mtop = 0;
mcap = size;
}
~SeqStack() {
delete[] mstack;
mstack = nullptr;
}
SeqStack(const SeqStack& src) = delete;
SeqStack& operator=(const SeqStack& src) = delete;
public:
// push pop top empty
void push(int val) {
if (mtop == mcap) {
expand();
}
mstack[mtop++] = val;
}
void pop() {
if (empty()) {
throw "stack is empty!";
}
else {
mtop--;
}
}
int top() const {
if (empty()) {
throw "stack is empty!";
}
return mstack[mtop - 1];
}
int size() const{
return mtop;
}
bool empty() const{
return mtop == 0;
}
private:
void expand() {
int* tmp_stack = new int[2 * mcap];
memcpy(tmp_stack, mstack, sizeof(int) * mtop);
delete mstack;
mstack = tmp_stack;
mcap *= 2;
}
private:
int* mstack;
int mtop; // 栈内元素
int mcap; // 栈容量,开辟的堆内存大小
};
2. 链栈
class LinkStack {
public:
LinkStack() {
head = new Node();
head->data = 0; // 用头节点的数据域存放数据节点的数量
}
~LinkStack() {
while (head != nullptr) {
Node* del = head;
head = head->next;
delete del;
}
}
LinkStack(const LinkStack& src) = delete;
LinkStack& operator=(const LinkStack& src) = delete;
public:
// push pop top empty
void push(int val) {
// 采用头插法
Node* node = new Node(val);
node->next = head->next;
head->next = node;
head->data++;
}
void pop() {
if (empty()) {
throw "stack is empty!";
}
Node* del = head->next;
head->next = del->next;
delete del;
head->data--;
}
int top() const {
if (empty()) {
throw "stack is empty!";
}
return head->next->data;
}
int size() const {
return head->data;
}
bool empty() const {
return head->data == 0;
}
private:
struct Node {
Node(int d = 0)
:data(d)
,next(nullptr) {
}
int data;
Node* next;
};
Node* head;
};
3. 括号匹配
class Solution {
public:
bool is_match(char a, char b) {
if ((a == '(' && b == ')')) {
return true;
}
else if ((a == '[' && b == ']')) {
return true;
}
else if ((a == '{' && b == '}')) {
return true;
}
return false;
}
bool isValid(string s) {
stack<char> st;
for (char c : s) {
if (c == '(' || c == '[' || c == '{') {
st.push(c);
}
else {
// 右括号
if (!st.empty() && is_match(st.top(), c)) {
st.pop();
}
else {
return false;
}
}
}
return st.empty();
}
};
4. 逆波兰表达式
- 数字则入栈
- 运算符出栈两个数字,先出的是右操作数,后出的是左操作数
- 两个数字的计算结果入栈
class Solution {
public:
int evalRPN(vector<string>& tokens) {
stack<int> st;
for(string s : tokens){
if(s == "+" || s == "-" || s == "*" || s == "/"){
int right = st.top();
st.pop();
int left = st.top();
st.pop();
switch (s[0]) {
case '+':
st.push(left + right);
break;
case '-':
st.push(left - right);
break;
case '*':
st.push(left * right);
break;
case '/':
st.push(left / right);
break;
}
}else{
st.push(stoi(s)); // atoi(s.c_str())
}
}
return st.top(); // 最后的计算结果存放在栈中
}
};
5. 中缀转后缀表达式
- 栈空,符号直接入栈
- 符号为
(
,直接入栈 - 符号为
)
,一直出栈,直到出栈一个(
- 用当前符号和栈顶符号比较优先级
(1) 当前符号优先级 > 栈顶符号优先级:当前符号入栈
(2) 当前符号优先级 <= 栈顶符号优先级:栈内符号出栈,继续和当前符号比较,直到符号出完。
// cur优先级是否大于stack_c
bool is_high_priority(char cur, char stack_c) {
if (stack_c == '(') {
return true;
}
return (cur == '*' || cur == '/') && (stack_c == '+' || stack_c == '-');
}
// 四则运算和小括号的表达式
string MiddleToEndExpr(string str) {
stack<char> st;
string res;
for (int i = 0; i < str.size(); i++) {
if (st.empty() || str[i] == '(') {
st.push(str[i]);
}
else if (str[i] == ')') {
while (st.top() != '(') {
// 表达式正确的话,这里栈不可能为空
// 因为只要遇见了右括号,栈内一定有一个左括号
res += st.top();
st.pop();
}
st.pop(); // 出栈左括号
}
else {
// 数字字符
if (str[i] >= '0' && str[i] <= '9') {
res += str[i];
}
else {
// 运算符
if (is_high_priority(str[i], st.top())) {
// 当前字符优先级大于栈顶字符优先级
st.push(str[i]);
}
else {
res += st.top();
st.pop();
i--;
}
}
}
}
// 剩下字符全部出栈
while (!st.empty()) {
res += st.top();
st.pop();
}
return res;
}
七、队列
1. 顺序环形队列常用公式
// front指向第一个有效元素 rear指向最后一个有效元素的后继位置
front == rear // 判空
(rear + 1) % len == front // 判满
(rear - front + len) % len // 元素个数
2. 顺序环形队列
class Queue {
public:
Queue(int size = 10) {
_q = new int[size];
_cap = size;
_front = 0;
_rear = 0;
}
~Queue() {
delete _q;
_q = nullptr;
}
public:
bool empty() const{
return _front == _rear;
}
bool full() const {
return (_rear + 1) % _cap == _front;
}
int size() const{
return (_rear - _front + _cap) % _cap;
}
void push(int val) {
if (full()) {
expand();
}
_q[_rear] = val;
_rear = (_rear + 1) % _cap;
}
void pop() {
if (empty()) {
throw "queue is empty!";
}
_front = (_front + 1) % _cap;
}
int front() const{
if (empty()) {
throw "queue is empty!";
}
return _q[_front];
}
int back() const{
return _q[(_rear - 1 + _cap) % _cap];
}
private:
void expand() {
const int time = 2;
int* tmp_q = new int[time * _cap];
int dst = 0;
for (int src = _front; src != _rear; src = (src + 1) % _cap) {
tmp_q[dst] = _q[src];
dst++;
}
delete[] _q;
_q = tmp_q;
_cap *= time;
_front = 0;
_rear = dst;
}
private:
int* _q;
int _cap;
int _front;
int _rear;
};
3. 链式队列
class LinkQueue {
public:
LinkQueue() {
_head = new Node();
_head->_pre = _head;
_head->_next = _head;
_size = 0;
}
~LinkQueue() {
Node* del = _head->_next;
while (del != _head) {
_head->_next = del->_next;
del->_next->_pre = _head;
delete del;
del = _head->_next;
}
delete _head;
_head = nullptr;
}
public:
bool empty() const {
return _head->_next == _head;
}
int size() const {
return _size;
}
void push(int val) {
Node* node = new Node(val);
node->_pre = _head->_pre;
node->_next = _head;
_head->_pre->_next = node;
_head->_pre = node;
}
void pop() {
if (empty()) {
throw "queue is empty!";
}
Node* del = _head->_next;
_head->_next = del->_next;
del->_next->_pre = _head;
delete del;
}
int front() const {
if (empty()) {
throw "queue is empty!";
}
return _head->_next->_data;
}
int back() const {
if (empty()) {
throw "queue is empty!";
}
return _head->_pre->_data;
}
private:
struct Node {
Node(int data = 0)
:_data(data)
,_pre(nullptr)
,_next(nullptr)
{}
int _data;
Node* _pre;
Node* _next;
};
Node* _head;
int _size;
};
4. 两个栈实现一个队列
class MyQueue {
public:
MyQueue() {
}
void push(int x) {
in_st.push(x);
}
int pop() {
if(out_st.empty()){
while(!in_st.empty()){
out_st.push(in_st.top());
in_st.pop();
}
}
int val = out_st.top();
out_st.pop();
return val;
}
int peek() {
if(out_st.empty()){
while(!in_st.empty()){
out_st.push(in_st.top());
in_st.pop();
}
}
return out_st.top();
}
bool empty() {
return in_st.empty() && out_st.empty();
}
private:
stack<int> in_st;
stack<int> out_st;
};
5. 两个队列实现一个栈
每次入栈时都将元素入到空队列A,然后把另一个队列B的所有元素都导入当前这个队列A,这样当前队列A的元素出队列的顺序就和栈一样了,下次入栈再把元素入到空队列。
每次进行push,都是入到空队列,然后把不空队列的所有元素导入有新元素加入的队列
class MyStack {
public:
MyStack() {
empty_q = new queue<int>();
fine_q = new queue<int>();
}
// O(n)
void push(int x) {
empty_q->push(x);
while(!fine_q->empty()){
empty_q->push(fine_q->front());
fine_q->pop();
}
queue<int> *tmp = empty_q;
empty_q = fine_q;
fine_q = tmp;
}
int pop() {
int val = fine_q->front();
fine_q->pop();
return val;
}
int top() {
return fine_q->front();
}
bool empty() {
return fine_q->empty();
}
private:
queue<int> *empty_q; // 每次入队,都入这个空队列
queue<int> *fine_q; // 已经调整好栈顺序的队列
};
6. 用一个队列实现一个栈
每次入一个元素,需要把其他元素全部都出队并入到新元素的后面,每次push都是O(n)
class MyStack {
public:
MyStack() {
}
// O(n)
void push(int x) {
q.push(x);
int len = q.size() - 1;
for(int i = 0; i < len; i++){
q.push(q.front());
q.pop();
}
}
int pop() {
int val = q.front();
q.pop();
return val;
}
int top() {
return q.front();
}
bool empty() {
return q.empty();
}
private:
queue<int> q;
};