学习来源:
代码随香炉:https://www.programmercarl.com/
labuladong算法:https://labuladong.github.io/algo/
链表
两数相加(链表数字相加)
两个链表倒序的,
解法一,报错,数据长度有限制, int long 都不能完全通过
求出来两个链表的数字,相加后,然后再合成链表返回
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
long long num1 = 0,num2 = 0;
long long mul1 = 1;
while(l1!=nullptr){
num1 = num1 + mul1 * l1->val;
mul1 *= 10;
l1 = l1->next;
}
mul1 = 1;
while(l2!=nullptr){
num2 = num2 + mul1 * l2->val;
mul1 *= 10;
l2 = l2->next;
}
long long res = num1 + num2;
if(res ==0 )
return new ListNode(0);
cout<<res;
ListNode* cur = new ListNode(0);
ListNode* dummy = new ListNode(-1);
dummy->next = cur;
while(res){
long long val = res%10;
ListNode* temp = new ListNode(val);
cur->next = temp;
cur = cur->next;
res = res/10;
}
return dummy->next->next;
}
};
解法二: 直接在一个链表里面相加,考虑进位
这里的head tail两个指针,head只参与第一次节点创建,相当于dummy指针
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *head = nullptr, *tail = nullptr;
int carry = 0;
while (l1 || l2) {
// 判断l1是否为空。
int n1 = l1 ? l1->val: 0;
// 判断l1是否为空。
int n2 = l2 ? l2->val: 0;
// 计算sum: carry为进位,初始为0
int sum = n1 + n2 + carry;
// 判断头节点是否非空:
if (!head) {
// 如果头节点空的话,首次添加节点
head = tail = new ListNode(sum % 10);
} else {
// 如果头节点非空的话,
tail->next = new ListNode(sum % 10);
tail = tail->next;
}
carry = sum / 10;
if (l1) {
l1 = l1->next;
}
if (l2) {
l2 = l2->next;
}
}
if (carry > 0) {
tail->next = new ListNode(carry);
}
return head;
}
};
单链表 双链表 循环链表(解决约瑟夫环的问题)
- 定义
// 单链表
struct ListNode {
int val; // 节点上存储的元素
ListNode *next; // 指向下一个节点的指针
ListNode(int x) : val(x), next(NULL) {} // 节点的构造函数
};
ListNode* head = new ListNode(5);
数组在定义的时候,长度就是固定的,如果想改动数组的长度,就需要重新定义一个新的数组。
链表的长度可以是不固定的,并且可以动态增删, 适合数据量不固定,频繁增删,较少查询的场景。
双指针7道题目
合并两个有序链表 !
虚拟节点,while里面两两比较大小接入即可
合并k个有序链表 !(dummy 优先队列)
用到了虚拟头节点 和 优先队列(注意优先队列)
priority_queue<Type, Container, Functional>;
//Type是数据类型,Container为保存数据的容器,Functional为元素比较方式
//以int为例定义大顶堆的两种形式,默认是大顶堆,元素从大到小排序
priority_queue<int, vector<int>, less<int>> q1;
priority_queue<int> q2;
//以int为例定义小顶堆,元素从小到大排序
priority_queue<int, vector<int>, greater<int>> q3;
优先队列 和 sort有区别
优先队列的大小比较是相反的,less默认是大顶堆
优先队列pq中的元素个数最多是k,所以一次poll或者add方法的时间复杂度是O(logk);所有的链表节点都会被加入和弹出pq,所以算法整体的时间复杂度是O(Nlogk),其中k是链表的条数,N是这些链表的节点总数。
删除链表的第k个节点
双指针方法 快慢指针
fast首先走n + 1步 ,为什么是n+1呢,因为只有这样同时移动的时候slow才能指向删除节点的上一个节点(方便做删除操作),如图:
fast和slow同时移动,直到fast指向末尾,
环形链表(是否有环)
判断链表是否有环
环形链表II (检测环的入口)
链表的中间节点
快慢指针
注意边界条件
相交链表
还有哈希set 判断第一个交点 不过空间复杂度高
两个链表搞成循环 遍历完1遍历2 两个指针 相交的地方就是交点
两个链表搞成循环 遍历完1遍历2 两个指针 相交的地方就是交点
有哈希解法 和 双链表解法
哈希
两个链表搞成循环 遍历完1遍历2 两个指针 相交的地方就是交点
翻转链表
迭代法和递归法
双指针法(因为单向,需要保存前一个)
步骤: 三步走
- 保存一下 cur的下一个节点,因为接下来要改变cur->next
- 翻转操作
- 更新pre 和 cur指针
k个一组反转链表
k个一组反转链表
如果不是k的整数倍,剩余的节点保持有序
1、先反转以 head 开头的 k 个元素。
2、将第 k + 1 个元素作为 head 递归调用 reverseKGroup 函数。
3、将上述两个过程的结果连接起来。
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if( head == nullptr ) return nullptr;
ListNode* a = head;
ListNode* b = head;
for(int i = 0; i < k; i++){
if(b == nullptr) return head;
b = b->next;
}
ListNode* newNode = reverse(a,b);
a->next = reverseKGroup(b,k);
return newNode;
}
ListNode* reverse(ListNode* a,ListNode* b){
ListNode* temp;
ListNode* pre = nullptr;
ListNode* cur = a;
while(cur != b){
temp = cur->next; // 保存cur的下一个节点
cur->next = pre; //翻转
// 移动
pre = cur;
cur = temp;
}
return pre;
}
};
判断回文链表
判断回文串 : 使用双指针从两端向中心遍历即可
寻找回文串: 需要根据奇数和偶数 从中心向两端扩展
三种方法:
- 创建一个新的链表,逆序然后对比
- 复制到数组中使用双指针法
- 利用后序遍历的思想
移除链表 虚拟头节点
设计链表
class MyLinkedList {
public:
// 定义链表节点结构体
struct LinkedNode {
int val;
LinkedNode* next;
LinkedNode(int val):val(val), next(nullptr){}
};
// 初始化链表
MyLinkedList() {
_dummyHead = new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点,而不是真正的链表头结点
_size = 0;
}
// 获取到第index个节点数值,如果index是非法数值直接返回-1, 注意index是从0开始的,第0个节点就是头结点
int get(int index) {
if (index > (_size - 1) || index < 0) {
return -1;
}
LinkedNode* cur = _dummyHead->next;
while(index--){ // 如果--index 就会陷入死循环
cur = cur->next;
}
return cur->val;
}
// 在链表最前面插入一个节点,插入完成后,新插入的节点为链表的新的头结点
void addAtHead(int val) {
LinkedNode* newNode = new LinkedNode(val);
newNode->next = _dummyHead->next;
_dummyHead->next = newNode;
_size++;
}
// 在链表最后面添加一个节点
void addAtTail(int val) {
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(cur->next != nullptr){
cur = cur->next;
}
cur->next = newNode;
_size++;
}
// 在第index个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
// 如果index 等于链表的长度,则说明是新插入的节点为链表的尾结点
// 如果index大于链表的长度,则返回空
void addAtIndex(int index, int val) {
if (index > _size) {
return;
}
LinkedNode* newNode = new LinkedNode(val);
LinkedNode* cur = _dummyHead;
while(index--) {
cur = cur->next;
}
newNode->next = cur->next;
cur->next = newNode;
_size++;
}
// 删除第index个节点,如果index 大于等于链表的长度,直接return,注意index是从0开始的
void deleteAtIndex(int index) {
if (index >= _size || index < 0) {
return;
}
LinkedNode* cur = _dummyHead;
while(index--) {
cur = cur ->next;
}
LinkedNode* tmp = cur->next;
cur->next = cur->next->next;
delete tmp;
_size--;
}
// 打印链表
void printLinkedList() {
LinkedNode* cur = _dummyHead;
while (cur->next != nullptr) {
cout << cur->next->val << " ";
cur = cur->next;
}
cout << endl;
}
private:
int _size;
LinkedNode* _dummyHead;
};
两两交换链表中的节点
方法1:正常模拟
此时一定要画图,不画图,操作多个指针很容易乱,而且要操作的先后顺序