目录
一、链表
Ⅰ、使用指针类型表示链表
1、移除链表元素
题目链接:
①、使用原链表进行操作
思路看下图注释
while(head != NULL && head ->val == val){
struct ListNode* tem = head;
head = head->next;
delete tem;
}
//分两部分,先去除头部可能有val的部分,然后去除后面的部分
//通常去除后面的部分通常会用一个新的指针去遍历去除。
struct ListNode* cur = head;
while(cur != NULL && cur ->next != NULL)
{
if(cur ->next ->val == val){
cur ->next = cur->next->next;
}
else cur = cur->next;
}
return head;
②、建立虚拟头节点操作
该方法将前面的思路直接合并。
struct ListNode* nhead = (struct ListNode*) malloc(sizeof(struct ListNode));
nhead ->next = head;
struct ListNode* tem = nhead;
while(tem != NULL && tem ->next!=NULL)
{
if(tem ->next->val == val){
tem ->next = tem ->next ->next;
}
else tem = tem ->next;
}
head = nhead->next;
return head;
//该方法较上面的好处是可以将移除头部元素和尾部元素的情况合并,一次遍历成功。
2、设计链表(熟悉链表的基本操作)
题目链接:
该题目较为全面的实现了基本插入、删除、查找、清空操作,用来熟悉链表非常好用。
//定义结构体
typedef struct MyLinkedList{
int val;
struct MyLinkedList* next;
} MyLinkedList;
//建立头节点
MyLinkedList* myLinkedListCreate() {
MyLinkedList* head = (MyLinkedList*)malloc(sizeof(MyLinkedList));
head->next = NULL;
return head;
}
//获取链表中元素
int myLinkedListGet(MyLinkedList* obj, int index) {
MyLinkedList *cur = obj->next;
for (int i = 0; cur != NULL; i++){
if (i == index){
return cur->val;
}
else{
cur = cur->next;
}
}
return -1;
}
//头插法
void myLinkedListAddAtHead(MyLinkedList* head, int val) {
MyLinkedList* addhead = (MyLinkedList*)malloc(sizeof(MyLinkedList));
addhead->val = val;addhead->next = head->next;
head->next = addhead;
}
//尾部插法
void myLinkedListAddAtTail(MyLinkedList* head, int val) {
MyLinkedList* tem = head;
while(tem->next!=NULL){
tem = tem->next;
}
MyLinkedList* cur = (MyLinkedList*)malloc(sizeof(MyLinkedList));
cur->val = val;cur->next = NULL;
tem->next = cur;
}
//中间插入法
void myLinkedListAddAtIndex(MyLinkedList* obj, int index, int val) {
if (index == 0){
myLinkedListAddAtHead(obj, val);
return;
}
MyLinkedList *cur = obj->next;
for (int i = 1 ;cur != NULL; i++){
if (i == index){
MyLinkedList* newnode = (MyLinkedList *)malloc(sizeof (MyLinkedList));
newnode->val = val;
newnode->next = cur->next;
cur->next = newnode;
return;
}
else{
cur = cur->next;
}
}
}
//删除链表元素
void myLinkedListDeleteAtIndex(MyLinkedList* head, int index) {
if(index==0) {
MyLinkedList* tem = head->next;
if(tem!=NULL){
head->next = tem->next;
free(tem);
}
}
MyLinkedList* cur = head->next;
for(int i = 1;cur!=NULL;i++){
if(i==index){
MyLinkedList* temp = cur->next;
if(temp!=NULL){
cur->next = temp->next;
free(temp);
}
return;
}
else{
cur = cur->next;
}
}
}
//清空元素
void myLinkedListFree(MyLinkedList* head) {
while(head){
MyLinkedList* p2 = head;
head = head->next;
free(p2);
}
}
/**
* Your MyLinkedList struct will be instantiated and called as such:
* MyLinkedList* obj = myLinkedListCreate();
* int param_1 = myLinkedListGet(obj, index);
* myLinkedListAddAtHead(obj, val);
* myLinkedListAddAtTail(obj, val);
* myLinkedListAddAtIndex(obj, index, val);
* myLinkedListDeleteAtIndex(obj, index);
* myLinkedListFree(obj);
*/
3、双指针操作链表类型题目
①、反转链表
题目链接:反转链表
下面表格表示一次的变化情况:
NULL(pre) -> | node1 (cur) -> | node2 -> | NULL |
NULl(pre) | <- node1(cur) | node2(temp) -> | NULL |
NULL | <- node1(pre) | node2(temp) -> | NULL |
NULL | <- node1(pre | node2(cur) -> | NULL |
struct ListNode* cur = head; struct ListNode* pre = NULL;
while(cur != NULL){
struct ListNode* temp = cur ->next;
cur ->next = pre;
pre = cur;
cur = temp;
}
return pre;
②、两两交换链表中的元素
题目链接:
此处逻辑需要画图(按照下面代码一画图便理解了)来理清关系,此处while中的代码为重复的业务逻辑。
nhead(cur) -> 虚拟头节点 | node1(pre) -> 节点1 | node2 -> 节点2 | NULL |
if(head == NULL || head ->next == NULL) return head;
struct ListNode* nhead = (struct ListNode*) malloc(sizeof(struct ListNode));
nhead ->next = head;
struct ListNode* cur = nhead; struct ListNode* pre = cur ->next;
while(cur != NULL && pre != NULL && pre ->next != NULL)
{
cur ->next = cur ->next ->next;
pre ->next = cur ->next ->next;
cur ->next ->next = pre;
cur = pre;
pre = pre ->next;
}
return nhead ->next;
③、删除倒数第n个节点
题目链接:
struct ListNode* nhead = (struct ListNode*)malloc(sizeof(struct ListNode));
nhead->next = head;
if(nhead->next == NULL) return NULL;
struct ListNode* slow = nhead;
struct ListNode* fast = nhead->next;
int i = 1;
for(i = 1;i<=n;i++){
if(fast!=NULL) {
fast = fast->next;
}
}
while(fast!=NULL){
fast = fast->next;
slow = slow->next;
}
struct ListNode* tem = slow->next;
slow->next = tem->next;
free(tem);
return (nhead->next);
Ⅱ、使用数组类型表示链表
1、单链表
题目链接:单链表(数组实现)
该代码的关键在于ne[]数组的理解,以及idx作用的理解。
①、ne[]数组可以理解为链表节点的指针域。而idx可以理解为代替指针的一种东西,每个idx的数值不一样,例如ne[k] = k+1的意义就是,k这个下标的数组指向 下标为 k + 1的数组。
②、我们每次将idx++。就是为了保证idx对每个节点的唯一性。
#include<iostream>
using namespace std;
int head,idx;
const int N = 1e5+10;
int e[N],ne[N];
void add_to_head(int x)
{
e[idx] = x;
ne[idx] = head;
head = idx;
idx++;
}
void remove(int k)
{
ne[k] = ne[ne[k]];
}
void add(int k,int x)
{
e[idx] = x;
ne[idx] = ne[k];
ne[k] = idx;
idx++;
}
int main()
{
head = -1;idx = 0;
int n;cin>>n;
for(int i = 0;i<n;i++)
{
char s;cin>>s;
if(s == 'H')
{
int x;cin>>x;
add_to_head(x);
}
if(s == 'I')
{
int k,x;cin>>k>>x;
add(k-1,x);
}
if(s == 'D')
{
int k;cin>>k;
if(!k) head = ne[head];
remove(k-1);
}
}
for(int i = head;i!=-1;i = ne[i]) cout<<e[i]<<" ";
cout<<endl;
return 0;
}
2、双链表
本题目则是使用l[]和r[]数组来表示双链表的左右两端。
从而实现双链表功能。idx依然起代替指针的作用。
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int m;
int e[N], l[N], r[N];
int idx;
//! 初始化
void init()
{
l[1] = 0, r[0] = 1;
idx = 2;
}
void add(int k, int x)
{
e[idx] = x;
l[idx] = k;
r[idx] = r[k];
l[r[k]] = idx;
r[k] = idx;
idx++;
}
void remove(int k)
{
l[r[k]] = l[k];
r[l[k]] = r[k];
}
int main()
{
cin >> m;
init();
while(m--)
{
string op;
cin >> op;
int k, x;
if(op=="R")
{
cin >> x;
add(l[1], x);
}
else if(op=="L")
{
cin >> x;
add(0, x);
}
else if(op=="D")
{
cin >> k;
remove(k + 1);
}
else if(op=="IL")
{
cin >> k >> x;
add(l[k + 1], x);
}
else
{
cin >> k >> x;
add(k + 1, x);
}
}
for(int i = r[0]; i != 1; i = r[i]) cout << e[i] << ' ';
return 0;
}
二、栈与队列
Ⅰ、普通栈
1.使用数组实现栈
题目链接:
栈的结构非常简单,使用数组很简单就能实现。
#include<iostream>
using namespace std;
const int N = 1e5 + 10;
int stack[N];
int top = -1, n;
int main()
{
cin >> n;
while(n--)
{
string s;
cin >> s;
//栈顶所在索引往后移动一格,然后放入x。
if(s == "push")
{
int a; cin>>a;
stack[++top] = a;
}
//往前移动一格
if(s == "pop")
{
top--;
}
//返回栈顶元素
if(s == "query")
{
cout<<stack[top]<<endl;
}
//大于等于 0 栈非空,小于 0 栈空
if(s == "empty")
{
if(top >= 0) cout<<"NO"<<endl;
else cout<<"YES"<<endl;
}
}
return 0;
}
(重要)2、表达式求值(逆波兰表达式)
题目链接:
unordered_map<char,int>
h{{'+',1},{'-',1},{'*',2},{'/',2}};
此处运用map将h中运算符的优先级进行定义,数字大的优先级大。
op[]用来存放非数字的数据,num[]用来存放数字。
#include<iostream>
using namespace std;
#include<string>
#include<stack>
#include<unordered_map>
stack<int> num;
stack<char> op;
unordered_map<char,int> h{{'+',1},{'-',1},{'*',2},{'/',2}};
void eval()
{
int a = num.top();num.pop();
int b= num.top();num.pop();
char r = op.top();op.pop();
int x = 0;
if(r=='+') x = b+a;
else if(r=='-') x = b-a;
else if(r == '*') x = b*a;
else if(r=='/') x = b/a;
num.push(x);
}
int main()
{
string s;cin>>s;
for (int i = 0; i < s.size(); i++)
{
if (isdigit(s[i]))//数字入栈
{
int x = 0, j = i;//计算数字
while (j < s.size() && isdigit(s[j]))
{
x = x * 10 + s[j] - '0';
j++;
}
num.push(x);//数字入栈
i = j - 1;
}
//左括号无优先级,直接入栈
else if (s[i] == '(')//左括号入栈
{
op.push(s[i]);
}
//括号特殊,遇到左括号直接入栈,遇到右括号计算括号里面的
else if (s[i] == ')')//右括号
{
while(op.top() != '(')//一直计算到左括号
eval();
op.pop();//左括号出栈
}
else
{
while (op.size() && h[op.top()] >= h[s[i]])//待入栈运算符优先级低,则先计算
eval();
op.push(s[i]);//操作符入栈
}
}
while (op.size()) eval();//剩余的进行计算
cout << num.top() << endl;//输出结果
return 0;
}
Ⅱ、队列
1、使用数组实现队列
题目链接:
#include<iostream>
using namespace std;
const int N = 100010;
int q[N];
int hh = 0;//队头
int tt = -1;//队尾
int m;
string s;
void push(int x){
q[++tt] = x;
}
void pop(){
hh++;
}
void empty(){
if(tt >= hh) cout << "NO" << endl;
else cout << "YES" << endl;
}
void query (){
cout << q[hh] << endl;
}
int main(){
cin >> m;
while(m--){
cin >> s;
//入队
if(s == "push"){
int x;
cin >> x;
push(x);
}
//出队
if(s == "pop"){
pop();
}
//问空
if(s == "empty"){
empty();
}
//问队头
if(s == "query"){
query();
}
}
}
Ⅲ、单调栈
题目链接:
#include<iostream>
using namespace std;
const int N = 1e5+10;
int tt,skt[N];
int main()
{
int n; cin>>n;
for(int i = 0; i<n; i++)
{
int x; cin>>x;
while(tt != 0 && skt[tt] >= x) tt--;
if(tt != 0) cout<<skt[tt]<<" ";
else cout<<"-1 ";
skt[++tt] = x;
}
return 0;
}
Ⅳ、单调队列(滑动窗口)
题目链接:
#include <iostream>
#include <deque>
using namespace std;
const int N = 1000010;
int a[N];
int main()
{
int n, k;
cin >> n >> k;
for (int i = 1; i <= n; i ++ ) cin >> a[i];//读入数据
deque<int> q;
for(int i = 1; i <= n; i++)
{
while(q.size() != 0 && q.back() > a[i]){
q.pop_back();
}
q.push_back(a[i]);
if(i - k >= 1 && q.front() == a[i - k]){
q.pop_front();
}
if(i >= k){
cout<<q.front()<<" ";
}
}
q.clear();
cout << endl;
//打印最大值
for(int i = 1; i <= n; i++)
{
while(q.size() != 0 && q.back() < a[i]){
q.pop_back();
}
q.push_back(a[i]);
if(i-k >= 1 && q.front() == a[i - k]){
q.pop_front();
}
if(i >= k){
cout<<q.front()<<" ";
}
}
}
三、字符串
Ⅰ、反转字符串系列
①、简单反转
1.反转Ⅰ
题目链接:
void reverseString(vector<char>& s) {
int i, j;
i = 0; j = s.size() - 1;
while(i < j)
{
swap(s[i ++], s[j --]);
}
}
2、反转Ⅱ
题目链接:
for(int i = 0;i<s.size();i+=(k*2)){
if(i+k<=s.size()){
reverse(s.begin()+i,s.begin()+i+k);
}
else{
reverse(s.begin()+i,s.end());
}
}
return s;
②、复杂反转
1、反转单词
题目链接:
void reverse(string& s, int start, int end){ //翻转,区间写法:左闭右闭 []
for (int i = start, j = end; i < j; i++, j--) {
swap(s[i], s[j]);
}
}
void removeExtraSpaces(string& s) {//去除所有空格并在相邻单词之间添加空格, 快慢指针。
int slow = 0;
for (int i = 0; i < s.size(); ++i) { //
if (s[i] != ' ') { //遇到非空格就处理,即删除所有空格。
if (slow != 0) s[slow++] = ' '; //手动控制空格,给单词之间添加空格。slow != 0说明不是第一个单词,需要在单词前添加空格。
while (i < s.size() && s[i] != ' ') { //补上该单词,遇到空格说明单词结束。
s[slow++] = s[i++];
}
}
}
s.resize(slow); //slow的大小即为去除多余空格后的大小。
}
string reverseWords(string s) {
removeExtraSpaces(s); //去除多余空格,保证单词之间之只有一个空格,且字符串首尾没空格。
reverse(s, 0, s.size() - 1);
int start = 0; //removeExtraSpaces后保证第一个单词的开始下标一定是0。
for (int i = 0; i <= s.size(); ++i) {
if (i == s.size() || s[i] == ' ') { //到达空格或者串尾,说明一个单词结束。进行翻转。
reverse(s, start, i - 1); //翻转,注意是左闭右闭 []的翻转。
start = i + 1; //更新下一个单词的开始下标start
}
}
return s;