提示:这些是自己整理 可以借鉴 也可能存在错误 欢迎指正
链表
数据结构介绍
链表
链表存储结构 :以指针
指向来表示数据元素之间的逻辑关系。
(单)链表是由节点
和指针
构成的数据结构,每个节点存有一个值,和一个指向下一个节点的指针,因此很多链表问题可以用递归来处理。
简单
1. 模板链表
请你实现一个链表。
操作:
insert x y:将y加入链表,插入在第一个值为x的结点之前。若链表中不存在值为x的结点,则插入在链表末尾。保证x,y为int型整数。
delete x:删除链表中第一个值为xx的结点。若不存在值为xx的结点,则不删除。
输入描述:
第一行输入一个整数n ,表示操作次数。
接下来的n行,每行一个字符串,表示一个操作。保证操作是题目描述中的一种。
输出描述:
输出一行,将链表中所有结点的值按顺序输出。若链表为空,输出"NULL"(不含引号)。
例子:
输入:
5
insert 0 1
insert 0 3
insert 1 2
insert 3 4
delete 4
输出:2 1 3
#include <bits/stdc++.h>
using namespace std;
typedef struct List{
int data;
struct List *next;
}List, *LinkList;
LinkList Insert(int x, int y, LinkList p) {// 在inv前插入x;
LinkList Pre, t, Init;
Pre = p;
t = p->next;
//插入新结点、需要先new
Init = new List;
Init->data = y;
Init->next = NULL;
//先判断链表为不为空
if(t == NULL)
{
Pre->next = Init;
return p;
}
while(t)
{
if(t->data == x)
{
Init->next = t;
Pre->next = Init;
return p;
}else
{
t = t->next;
Pre = Pre->next;
}
}
if(t==NULL)
{
Pre->next = Init;
return p;
}
return p;
}
//删除结点
LinkList Deletenode(int x, LinkList p){
LinkList t, q;
t = p->next;
q = p;
while(t!=NULL)
{
if(t->data != x){
t = t->next;
q = q->next;
}else
{
q->next = t->next;
free(t);
return p;
}
}
return p;
}
int main()
{
int n;
cin>>n;//输入操作次数
LinkList head = new List;//新建链表
head->next = NULL;
while(n--){
string c;
cin>>c;
if(c == "insert"){
int x, y;
cin>>x>>y;
head = Insert(x, y, head);
}else{
int x;
cin>>x;
head = Deletenode(x, head);
}
}
//输出链表
LinkList t;
t = head->next;
if(t == NULL)
{
cout<<"NULL";
return 0;
}
while(t){
cout<<t->data<<" ";
t = t->next;
}
}
2. Reverse Linked List(反转链表)
翻转链表
输入输出样式:
输入:一个链表
输出:链表翻转后的结果
例如:
输入: 1->2->3->4->5->NULL
输出:5->4->3->2->1->NULL
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*
* C语言声明定义全局变量请加上static,防止重复定义
*/
/**
*
* @param pHead ListNode类
* @return ListNode类
*/
struct ListNode* ReverseList(struct ListNode* pHead ) {
// write code here
if(pHead == NULL)
return pHead;
struct ListNode* A = (struct ListNode*)malloc(sizeof(struct ListNode));
A->next = NULL;
struct ListNode* t, *p;
t = A;
p = pHead->next;
//头插法建链表
while(pHead != NULL)
{
pHead->next = t->next;
t->next = pHead;
pHead = p;
p = p->next;
}
return A->next;
}
3.合并两个排序链表
输入两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序的。
如输入{1,3,5},{2,4,6}时,合并后的链表为{1,2,3,4,5,6},所以对应的输出为{1,2,3,4,5,6}
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
/**
*
* @param pHead1 ListNode类
* @param pHead2 ListNode类
* @return ListNode类
*/
struct ListNode* Merge(struct ListNode* pHead1, struct ListNode* pHead2){
if(pHead1 == NULL)
return pHead2;
if(pHead2 == NULL)
return pHead1;
//构建新结点
struct ListNode* C = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* Ct;
Ct = C;
//头结点包含数据
while(pHead2 != NULL && pHead1 !=NULL)
{
if(pHead1->val <= pHead2->val){
Ct->next = pHead1;
pHead1 = pHead1->next;
Ct = Ct->next;
}else {
Ct->next = pHead2;
pHead2 = pHead2->next;
Ct = Ct->next;
}
}
Ct->next = NULL;
if(pHead1 != NULL)
{
Ct->next = pHead1;
}
if(pHead2 != NULL)
{
Ct->next = pHead2;
}
return C->next;
}
4. 删除链表的节点
输入:{2,5,1,9},5
返回值:{2,1,9}
说明:给定你链表中值为 5 的第二个节点,那么在调用了你的函数之后,该链表应变为 2 -> 1 -> 9
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*
* C语言声明定义全局变量请加上static,防止重复定义
*/
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param head ListNode类
* @param val int整型
* @return ListNode类
*/
struct ListNode* deleteNode(struct ListNode* head, int val ) {
// write code here
struct ListNode* t, *pre;
pre = head;
t = head;
if(head->val == val)
return head->next;
t = t->next;
while(t != NULL)
{
if(t->val == val)
{
pre->next = t->next;
return head;
}
t = t->next;
pre = pre->next;
}
return head;
}
5. 环形链表的约瑟夫问题
描述
编号为 1 到 n 的 n 个人围成一圈。从编号为 1 的人开始报数,报到 m 的人离开。
下一个人继续从 1 开始报数。
n-1 轮结束以后,只剩下一个人,问最后留下的这个人编号是多少?
class Solution {
public:
/**
*
* @param n int整型
* @param m int整型
* @return int整型
*/
int ysf(int n, int m) {
// write code here
vector<int> nums(n);
//初始
for(int i=0; i<n; i++)
nums[i] = i;
int s = 0;
while(nums.size() != 1){
int id_away = (s+m-1)%nums.size();
nums.erase(nums.begin() + id_away);//移除下标为id_away
//erase(position)函数---移除position处的一个字符
s = id_away;
}
return nums[0]+1;
}
};
6.判断链表中是否有环
描述
判断给定的链表中是否有环。如果有环则返回true,否则返回false。
解题思路
- 哈希
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
unordered_map<ListNode*, int> mp;//哈希记录结点出现的次数
bool flag = false;
ListNode* p = head;
while(p !=NULL){
if(mp.count(p)){
flag = true;
break;
}
else
mp[p]++;
p = p->next;
}
return flag;
}
};
7. 两个链表的第一个公共结点
描述
输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)
具体做法:
- step 1:单独的遍历两个链表,得到各自的长度。
- step 2:求得两链表的长度差nnn,其中较长的链表的指针从头先走nnn步。
- step 3:两链表指针同步向后遍历,遇到第一个相同的节点就是第一个公共节点。
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};*/
class Solution {
public:
//计算链表长度
int Listlenth(ListNode* head){
int res = 0;
ListNode* p = head;
while(p != NULL){
res++;
p = p->next;
}
return res;
}
ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
int n1 = Listlenth(pHead1);
int n2 = Listlenth(pHead2);
//链表1长于链表2
if(n1 >= n2){
for(int i=0; i<(n1-n2); i++){
pHead1 = pHead1->next;
}
}else{
for(int i=0; i<(n2-n1); i++)
pHead2 = pHead2->next;
}
while((pHead1!=NULL) && (pHead2!=NULL) && (pHead1 != pHead2)){
pHead1 = pHead1->next;
pHead2 = pHead2->next;
}
return pHead1;
}
};
中等
1. 链表内指定区间反转
将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n)O(n),空间复杂度 O(1)O(1)。
例如:
给出的链表为 1→2→3→4→5→NULL, m=2,n=4
返回 1→4→3→2→5→NULL.
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
class Solution {
public:
/**
*
* @param head ListNode类
* @param m int整型
* @param n int整型
* @return ListNode类
*/
ListNode* reverseBetween(ListNode* head, int m, int n) {
// write code here
ListNode* rehead = new ListNode(0);
rehead->next = head;
ListNode *p = rehead, *q = rehead;
for(int i=1; i<m; i++)
p = p->next;
ListNode *s = p->next;
for(int i=0; i<n-m; i++){
ListNode *t = s->next;
s->next = t->next;
t->next = p->next;
//t->next = s; 不能为s s一直在变换
p->next = t;
}
return rehead->next;
}
};
2.链表中的节点每k个一组翻转
将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表
如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样
你不能更改节点中的值,只能更改节点本身。
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
class Solution {
public:
/**
*
* @param head ListNode类
* @param k int整型
* @return ListNode类
*/
ListNode* reverseBetween(ListNode* head, int m, int n) {
// write code here
ListNode* rehead = new ListNode(0);
rehead->next = head;
ListNode *p = rehead, *q = rehead;
for(int i=1; i<m; i++)
p = p->next;
ListNode *s = p->next;
for(int i=0; i<n-m; i++){
ListNode *t = s->next;
s->next = t->next;
t->next = p->next;
//t->next = s; 不能为s s一直在变换
p->next = t;
}
return rehead->next;
}
ListNode* reverseKGroup(ListNode* head, int k){
if(head == NULL ||head->next==NULL || k==1)
return head;
int length = 0;
ListNode *t = head;
while(t){
length++;
t = t->next;
}
t = head;
for(int i=1; i<=length; i+=k){
if(i+k-1 > length)
break;//请注意!!!
ListNode* p = reverseBetween(t, i, k+i-1);//请注意!!! 直接t = reverseBetween(); 会发生数组越界
t = p;
}
return t;
}
};
3.链表中环的入口结点
描述
给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。
解题思路
- 哈希,记录结点出现次数
- 超过1 ,表示环的入口出现
- 同“判断链表中是否有环”
/*
struct ListNode {
int val;
struct ListNode *next;
ListNode(int x) :
val(x), next(NULL) {
}
};
*/
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead) {
unordered_map<ListNode*, int> mp;
ListNode* p = pHead;
ListNode* res;
while(p!=NULL){
if(mp[p] > 1){
res = p;
break;
}else
mp[p]++;
p = p->next;
}
return res;
}
};
4.删除链表的倒数第n个节点
描述
给定一个链表,删除链表的倒数第 n 个节点并返回链表的头指针
例如,
给出的链表为: 1→2→3→4→5, n=2.
删除了链表的倒数第 n 个节点之后,链表变为1→2→3→5.
解题思路
- 三个指针
- 指向当前结点now、指向当前结点的前一个结点pre、指向想隔n的结点p
- 直到p为空,删除now结点
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
class Solution {
public:
/**
*
* @param head ListNode类
* @param n int整型
* @return ListNode类
*/
ListNode* removeNthFromEnd(ListNode* head, int n) {
// write code here
ListNode* res = new ListNode(-1);
res->next = head;
ListNode* now = head;//当前结点
ListNode* pre = res;//当前结点的前一个结点
ListNode* p = head;//相隔n结点
while(n--){
p = p->next;
}
while(p!=NULL){
p = p->next;
now = now->next;
pre = pre->next;
}
pre->next = now->next;//删除结点
return res->next;
}
};
5.链表相加
描述
假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。
给定两个这种链表,请生成代表两个整数相加值的结果链表。
数据范围:0≤n,m≤1000000,链表任意值 0≤val≤9
要求:空间复杂度 O(n),时间复杂度 O(n)
解题思路:
- 先反转
- 应用大数加法的逻辑
- 注意:建立新的链表更适合
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
/**
*
* @param head1 ListNode类
* @param head2 ListNode类
* @return ListNode类
*/
//将链表先反转 然后再从头开始相加
ListNode* reverseList(ListNode* head){
if(head == NULL)
return head;
if(head->next == NULL){
return head;
}
ListNode* pre = new ListNode(0);
pre->next = head;
ListNode* p = head->next;//从头结点的下一个结点开始一个一个头插法
while(p != NULL){
head->next = p->next;
p->next = pre->next;
pre->next = p;
p = head->next;
}
return pre->next;
}
ListNode* addInList(ListNode* head1, ListNode* head2) {
// write code here
if(head1 == NULL)
return head2;
if(head2 == NULL)
return head1;
//反转链表
ListNode* p1 = reverseList(head1);
ListNode* p2 = reverseList(head2);
ListNode* res = new ListNode(0), *cur = res;
int flag = 0;
while(p1 || p2 || flag){
int x,y;
if(p1)
x = p1->val;
else
x = 0;
if(p2)
y = p2->val;
else
y = 0;
int sum = x+y+flag;
flag = sum/10;
sum = sum%10;
cur->next = new ListNode(sum);
cur = cur->next;
if(p1) p1=p1->next;
if(p2) p2=p2->next;
}
res = res->next;
res = reverseList(res);
return res;
}
};
较难
合并k个已排序的链表
解题思路:
在两个有序链表合并的基础上,依次合并----时间复杂度高
解决办法:折半法
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
//合并两个链表
struct ListNode* Merge(struct ListNode* pHead1, struct ListNode* pHead2){
if(pHead1 == NULL)
return pHead2;
if(pHead2 == NULL)
return pHead1;
//构建新结点
struct ListNode* C = (struct ListNode*)malloc(sizeof(struct ListNode));
struct ListNode* Ct;
Ct = C;
//头结点包含数据
while(pHead2 != NULL && pHead1 !=NULL)
{
if(pHead1->val <= pHead2->val){
Ct->next = pHead1;
pHead1 = pHead1->next;
Ct = Ct->next;}
else {
Ct->next = pHead2;
pHead2 = pHead2->next;
Ct = Ct->next;
}
}
Ct->next = NULL;
if(pHead1 != NULL){
Ct->next = pHead1;
}
if(pHead2 != NULL){
Ct->next = pHead2;
}
return C->next;
}
ListNode *mergeKLists(vector<ListNode *> &lists) {
// ListNode *res = nullptr;
// for(int i=0; i<lists.size(); i++){
// res = Merge(res, lists[i]);
// }
// return res;
//超出时间
return divideMerge(lists, 0, lists.size()-1);
}
//划分合并区间函数
ListNode *divideMerge(vector<ListNode *> &lists, int left, int right){
if(left > right)
return NULL;
else if(left == right)
return lists[left];
//从中间分成两段,再讲合并好的两端合并
int mid = (left+right)/2;
return Merge(divideMerge(lists, left, mid), divideMerge(lists, mid+1, right));
}
};
设计LRU缓存结构描述
描述
设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为 capacity ,操作次数是 n ,并有如下功能:
1. Solution(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存
2. get(key):如果关键字 key 存在于缓存中,则返回key对应的value值,否则返回 -1 。
3. set(key, value):将记录(key, value)插入该结构,如果关键字 key 已经存在,则变更其数据值 value,如果不存在,则向缓存中插入该组 key-value ,如果key-value的数量超过capacity,弹出最久未使用的key-value
提示
提示:
1.某个key的set或get操作一旦发生,则认为这个key的记录成了最常使用的,然后都会刷新缓存。
2.当缓存的大小超过capacity时,移除最不经常使用的记录。
3.返回的value都以字符串形式表达,如果是set,则会输出"null"来表示(不需要用户返回,系统会自动输出),方便观察
4.函数set和get必须以O(1)的方式运行
5.为了方便区分缓存里key与value,下面说明的缓存里key用""号包裹
//构造结点
struct Node{
int key;
int val;
Node* pre;
Node* next;
//初始化
Node(int k, int v): key(k), val(v), pre(NULL), next(NULL){};
};
class Solution {
public:
//全局变量,记录双向链表的头和尾、LRU剩余的大小
int size = 0;
int cap;
Node* head = NULL;
Node* tail = NULL;//双向链表
//哈希表
unordered_map<int, Node*> mp;
Solution(int capacity){
// write code here
//初始化
size = 0;
cap = capacity;
head = new Node(0,0);
tail = new Node(0,0);
head->next = tail;
tail->pre = head;
mp.clear();
}
int get(int key) {
// write code here
if(mp.count(key)){
movehead(mp[key]);
return mp[key]->val;
}
else
return -1;
}
void set(int key, int value){
// write code here
if(mp.count(key)){
movehead(mp[key]);
mp[key]->val = value;
}
else{
size++;
if(size > cap){
//缓存已满,更新尾,将尾删除
Node* ans = deletenode();
if(mp.count(ans->key)){
mp.erase(ans->key);
delete ans;
size--;
}
Node* t = new Node(key, value);
mp[key] = t;
addhead(t);
}
else{
//内存未满,直接在头部插入
Node* t = new Node(key, value);
mp[key] = t;
addhead(t);
}
}
}
//移到头结点
void movehead(Node* p){
removenode(p);
addhead(p);
}
//移除结点
void removenode(Node* p){
p->next->pre = p->pre;
p->pre->next = p->next;
}
//添加头结点
void addhead(Node* p){
p->pre = head;
p->next = head->next;
head->next->pre = p;
head->next = p;
}
Node* deletenode(){
Node* t = tail->pre;
removenode(t);
return t;
}
};
关于哈希表的用法
unordered_map是一个将key和value关联起来的容器,它可以高效的根据单个key值查找对应的value。
key值应该是唯一的,key和value的数据类型可以不相同。
定义:
unordered_map<int, Node*> haxi;
增加:
haxi.insert(p);
删除
haxi.erase(1);//根据key删除
查找
haxi(count(key))