- leetcode-206-从尾到头打印链表
- leetcode-19-删除链表的倒数第N个节点
- leetcode83-删除链表中重复元素
- leetcode-82-删除链表中重复元素II
- leetcode-24-两两交换链表中的节点
- leetcode-25-k个一组翻转链表
- leetcode-92-反转链表范围II
- leetcode-61-旋转链表
- leetcode-234-回文链表
- leetcode-21-和并两个排序的链表
- leetcode-83-合并K个排序链表
- leetcode-141-环形链表[offer23]
- leetcode-142-环的入口节点[offer23]
- leetcode-160-两个链表的第一个公共节点[offer-52]
- leetcode-86-分隔链表
自己总结的链表题目
leetcode-206-从尾到头打印链表
输入一个链表,按链表值从尾到头的顺序返回一个ArrayList
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
/**
* 方法一:使用头插法创建节点
* 超级Bug,leetcode中 C++用C的malloc方式写不能通过!
* 然后又改成了下面使用new()的方式
*/
ListNode* reverseListII(ListNode* head) {
ListNode *L = (ListNode*)malloc(sizeof(ListNode));
L->next = NULL;
ListNode *s;
while (head) {
s = (ListNode*)malloc(sizeof(ListNode));
s->val = head->val;
s->next = L->next;
L->next = s;
head = head->next;
}
return L->next;
}
/**
* 方法一:使用头插法创建节点
* 改成使用new()的方式创建链表节点就可以通过了
*/
ListNode* reverseListII_CPP(ListNode* head) {
ListNode *L = new ListNode(-1);
ListNode *s;
while (head) {
s = new ListNode(head->val);
s->next = L->next;
L->next = s;
head = head->next;
}
return L->next;
}
/**
* 方法二: 使用栈存储
* C++编译器,使用malloc()方式创建节点无法通过
*/
ListNode* reverseList(ListNode* head) {
stack<int> stk;
while (head) {
stk.push(head->val);
head = head->next;
}
ListNode *L = (ListNode*)malloc(sizeof(ListNode));
L->next = NULL;
ListNode *r, *s;
r = L;
while (!stk.empty()) {
s = (ListNode*)malloc(sizeof(ListNode));
s->val = stk.top();
stk.pop();
s->next = NULL;
r->next = s;
r = s;
}
r->next = NULL;
return L->next;
}
leetcode-19-删除链表的倒数第N个节点
/**
* 方法一:使用快慢指针,让fast先走(n+1)步,slow再从表头开始走,这样fast走到末尾时,slow刚好走到要删除节点的先驱节点
* 然后slow->next = slow->next->next就可以删除了
*/
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode *fast = head, *slow = head;
for (int i = 0; i < n; i++) {
fast = fast->next;
}
// 若删除的是头结点,则fast先走n步之后就到NULL了,此时,直接返回头结点的next节点即可
if (fast == NULL) {
return head->next;
}
while (fast->next) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
return head;
}
leetcode83-删除链表中重复元素
给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
示例 1:
输入: 1->1->2
输出: 1->2
示例 2:输入: 1->1->2->3->3
输出: 1->2->3
/**
* 方法一:使用一个指针,简洁明了
*/
ListNode* deleteDuplicates(ListNode* head) {
ListNode *cur = head;
while (cur && cur->next) {
if (cur->val == cur->next->val) {
cur->next = cur->next->next;
} else {
cur = cur->next;
}
}
return head;
}
/**
* 方法二:递归方法
* 首先我们判断是否至少有两个结点,若不是的话,直接返回head。否则对head->next调用递归函数,
* 并赋值给head->next。这里可能比较晕,我们先看后面一句,返回的时候,head结点先跟其身后的
* 结点进行比较,如果值相同,那么返回后面的一个结点,当前的head结点就被跳过了,而如果不同的
* 话,还是返回head结点。我们发现了,进行实质上的删除操作是在最后一句进行了,再来看第二句,
* 我们对head后面的结点调用递归函数,那么就应该suppose返回来的链表就已经没有重复项了,此时
* 接到head结点后面,再第三句的时候再来检查一下head是否又duplicate了,实际上递归一直走到了
* 末尾结点,再不断的回溯回来,进行删除重复结点,
*/
ListNode* deleteDuplicatesII(ListNode* head) {
if (!head || !head->next) return head;
head->next = deleteDuplicates(head->next);
return (head->val == head->next->val) ? head->next : head;
}
leetcode-82-删除链表中重复元素II
deleteDuplicatesII-删除链表中重复元素II
给定一个排序链表,删除所有含有重复数字的节点,只保留原始链表中 没有重复出现 的数字。
示例 1:
输入: 1->2->3->3->4->4->5
输出: 1->2->5
示例 2:输入: 1->1->1->2->3
输出: 2->3
/**
* 方法二:增加一个头节点(代码更精简!)
*/
ListNode* deleteDuplicatesII(ListNode* head) {
if (head == NULL || head->next == NULL)
return head;
ListNode *head_pre = new ListNode(-1);
head_pre->next = head;
ListNode *pre = head_pre, *cur = head, *last = NULL;
while (cur && cur->next) {
last = cur->next;
if (cur->val == last->val) {
while (last && cur->val == last->val) {
last = last->next;
}
pre->next = last;
cur = last;
} else {
pre = cur;
cur = last;
}
}
return head_pre->next;
}
leetcode-24-两两交换链表中的节点
给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。示例:
给定 1->2->3->4, 你应该返回 2->1->4->3.
/**
* 方法一:自己画图理解一下,比较容易弄糊涂,一定要画图
* (经常使用,必须要会!)(注意这里是两两交换,还不是最一般的形式)
* (下面的翻转部分才是最一般的形式)
*/
ListNode* swapPairs(ListNode* head) {
ListNode *header = new ListNode(-1), *pre = header;
header->next = head;
while (pre->next && pre->next->next) {
ListNode *t = pre->next->next;
pre->next->next = t->next;
t->next = pre->next;
pre->next = t;
pre = t->next;
}
return header->next;
}
/**
* 方法二:递归写法,回溯思想(递归的很不熟悉)
*/
ListNode* swapPairs(ListNode* head) {
if (!head || !head->next) return head;
ListNode *t = head->next;
head->next = swapPairs(head->next->next);
t->next = head;
return t;
}
leetcode-25-k个一组翻转链表
给出一个链表,每 k 个节点一组进行翻转,并返回翻转后的链表。k 是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍,那么将最后剩余节点保持原有顺序。
示例 :
给定这个链表:1->2->3->4->5
当 k = 2 时,应当返回: 2->1->4->3->5
当 k = 3 时,应当返回: 3->2->1->4->5说明 :
你的算法只能使用常数的额外空间。
你不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
/**
* 方法一:
* 这道题让我们以每k个为一组来翻转链表,实际上是把原链表分成若干小段,
* 然后分别对其进行翻转,那么肯定总共需要两个函数,一个是用来分段的,
* 一个是用来翻转的,我们就以题目中给的例子来看,对于给定链表1->2->3->4->5,
* 一般在处理链表问题时,我们大多时候都会在开头再加一个dummy node,因为翻转
* 链表时头结点可能会变化,为了记录当前最新的头结点的位置而引入的dummy node,
* 那么我们加入dummy node后的链表变为-1->1->2->3->4->5,如果k为3的话,
* 我们的目标是将1,2,3翻转一下,那么我们需要一些指针,pre和next分别指向要翻
* 转的链表的前后的位置,然后翻转后pre的位置更新到如下新的位置:
-1->1->2->3->4->5
| | |
pre cur next
-1->3->2->1->4->5
| | |
cur pre next
* 以此类推,只要cur走过k个节点,那么next就是cur->next,就可以调用翻转函数来
* 进行局部翻转了,注意翻转之后新的cur和pre的位置都不同了,那么翻转之后,cur应
* 该更新为pre->next,而如果不需要翻转的话,cur更新为cur->next
*/
ListNode* reverseKGroup(ListNode* head, int k) {
if (!head || k == 1) return head;
ListNode *header = new ListNode(-1), *pre = header, *cur = head;
header->next = head;
for (int i = 1; cur; ++i) {
if (i % k == 0) {
pre = reverseOneGrop(pre, cur->next);
cur = pre->next;
} else {
cur = cur->next;
}
}
return header->next;
}
/**
* 将改组链表翻转,并返回翻转后的改组头结点3,即翻转前的改组尾节点3
*/
ListNode *reverseOneGrop(ListNode *pre, ListNode *next) {
ListNode *last = pre->next, *cur = last->next;
while (cur != next) { // 先画图理解记忆,不行就找规律背下来,简单
last->next = cur->next;
cur->next = pre->next;
pre->next = cur;
cur = last->next;
}
return last;
}
leetcode-92-反转链表范围II
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ m ≤ n ≤ 链表长度。示例:
输入: 1->2->3->4->5->NULL, m = 2, n = 4
输出: 1->4->3->2->5->NULL
/**
* 方法一:常用方法,构建一个头节点
* 然后,下面的方法很经典,必须要会!!!
* 首先遍历到m位置的前一个位置,做为我们的pre,
* cur = pre->next; 建立一个临时节点t,指向cur->next 即t = cur->next;
* (注意,使用临时变量保存某个节点就是为了首先断开该节点和前面节点之间的联系!!!)
* 然后不断交换pre之后的节点,这里要画图理解。
* 比如原始链表如下,m = 2, n = 4,即pre指向 1,cur = 2,t = 3
* 1 -> 2 -> 3 -> 4 -> 5 -> NULL
* | | |
* pre cur t
* 第一次交换后就是:
* 1 -> 3 -> 2 -> 4 -> 5 -> NULL
* | | |
* pre t cur
* 然后继续交换,t = cur->next;
* 1 -> 3 -> 2 -> 4 -> 5 -> NULL
* | | |
* pre cur t
* 第二次交换后的结果为:
* 1 -> 4 -> 3 -> 2 -> 5 -> NULL
* | | |
* pre t cur
* 完成交换
*/
ListNode* reverseBetween(ListNode* head, int m, int n) {
ListNode *header = new ListNode(-1), *pre = header;
header->next = head;
for (int i = 0; i < m - 1; ++i) pre = pre->next;
ListNode *cur = pre->next;
for (int i = m; i < n; ++i) {
ListNode *t = cur->next;
cur->next = t->next;
t->next = pre->next;
pre->next = t;
}
return header->next;
}
leetcode-61-旋转链表
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
示例 1:
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL示例 2:
输入: 0->1->2->NULL, k = 4
输出: 2->0->1->NULL
解释:
向右旋转 1 步: 2->0->1->NULL
向右旋转 2 步: 1->2->0->NULL
向右旋转 3 步: 0->1->2->NULL
向右旋转 4 步: 2->0->1->NULL
/**
* 方法一:使用快慢指针,快指针先走k步,然后两个指针一起走,当快指针到末尾时,
* 慢指针的下一个位置是新的顺序的头结点,这样就可以旋转链表了。但再这之前需要处理
* 一下k,可能k的值远远大于链表的长度,我们需要首先遍历一下链表,得到链表的长度len,
* 然后,用 k %= len,这样 k 肯定小于链表长度了,就可以按照上面的方法了。
*/
ListNode* rotateRight(ListNode* head, int k) {
if (!head) return NULL;
int n = 0;
ListNode *cur = head;
while (cur) {
n++;
cur = cur->next;
}
k %= n;
ListNode *fast = head, *slow = head;
for (int i = 0; i < k; ++i) {
if (fast) fast = fast->next;
}
if (!fast) return head;
while (fast->next) {
fast = fast->next;
slow = slow->next;
}
fast->next = head;
fast = slow->next;
slow->next = NULL;
return fast;
}
/**
* 方法二:和上面方法类似,不用快慢指针,只用一个指针,原理是先遍历整个链表获得链表
* 长度n,然后此时把链表头和尾链接起来,在往后走n - k % n个节点就到达新链表的头结点
* 前一个点,这时断开链表即可。
*/
ListNode* rotateRight(ListNode* head, int k) {
if (!head) return NULL;
int n = 1;
ListNode *cur = head;
while (cur->next) {
++n;
cur = cur->next;
}
cur->next = head;
int stride = n - k % n;
for (int i = 0; i < stride; ++i) {
cur = cur->next;
}
ListNode *new_head = cur->next;
cur->next = NULL;
return new_head;
}
leetcode-234-回文链表
请判断一个链表是否为回文链表。
示例 1:
输入: 1->2
输出: false示例 2:
输入: 1->2->2->1
输出: true
/**
* 方法一:使用快慢指针以及栈
* 使用快慢指针找链表的中点。使用fast和slow两个指针,每次快指针走两步,
* 慢指针走一步,等快指针走完时,慢指针的位置就是中点。我们还需要用栈,每次慢指针
* 走一步,都把值存入栈中,等到达中点时,链表的前半段都存入栈中了,由于栈的后进先
* 出的性质,就可以和后半段链表按照回文对应的顺序比较了。
*/
bool isPalindrome(ListNode* head) {
if (!head || !head->next) return true;
stack<int> stk;
ListNode *fast = head, *slow = head;
stk.push(head->val);
while (fast->next && fast->next->next) {
fast = fast->next->next;
slow = slow->next;
stk.push(slow->val);
}
if (fast->next == NULL) { // 即此时链表长为偶数
stk.pop();
}
slow = slow->next;
while (!stk.empty()) {
if (stk.top() == slow->val) {
slow = slow->next;
stk.pop();
} else {
return false;
}
}
return true;
}
/**
* 方法二:使用快慢指针,不使用栈
* 使用快慢指针找到中点后,将后半部分链表翻转,然后对应比较即可
*/
bool isPalindromeII(ListNode* head) {
if (!head || !head->next) return true;
ListNode *fast = head, *slow = head;
while (fast->next && fast->next->next) {
fast = fast->next->next;
slow = slow->next;
}
ListNode *cur = slow->next, *pre = head;
while (cur->next) {
ListNode *t = cur->next;
cur->next = t->next;
t->next = slow->next;
slow->next = t;
}
slow = slow->next;
while (slow) {
if (slow->val == pre->val) {
slow = slow->next;
pre = pre->next;
} else {
return false;
}
}
return true;
}
leetcode-21-和并两个排序的链表
输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
/**
* 方法一:迭代法
*/
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *C, *s, *r;
C = new ListNode(-1);
r = C;
while (l1 && l2) {
if (l1->val <= l2->val) {
s = new ListNode(l1->val);
r->next = s;
r = s;
l1 = l1->next;
} else {
s = new ListNode(l2->val);
r->next = s;
r = s;
l2 = l2->next;
}
}
while (l1) {
s = new ListNode(l1->val);
r->next = s;
r = s;
l1 = l1->next;
}
while (l2) {
s = new ListNode(l2->val);
r->next = s;
r = s;
l2 = l2->next;
}
return C->next;
}
/**
* 方法二:递归方法
*/
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == NULL) return l2; // 注意这里的返回
if (l2 == NULL) return l1;
ListNode *C = NULL;
if (l1->val <= l2->val) {
C = l1;
C->next = mergeTwoLists(l1->next, l2);
} else {
C = l2;
C->next = mergeTwoLists(l1, l2->next);
}
return C;
}
leetcode-83-合并K个排序链表
/**
* 方法二:采用分治思想
* 简单来说就是不停的对半划分,比如k个链表先划分为合并两个k/2个链表的任务,
* 再不停的往下划分,直到划分成只有一个或两个链表的任务,开始合并。举个例子
* 来说比如合并6个链表,那么按照分治法,我们首先分别合并0和3,1和4,2和5。
* 这样下一次只需合并3个链表,我们再合并1和3,最后和2合并就可以了。代码中的
* k是通过 (n+1)/2 计算的,这里为啥要加1呢,这是为了当n为奇数的时候,k能始
* 终从后半段开始,比如当n=5时,那么此时k=3,则0和3合并,1和4合并,最中间
* 的2空出来。当n是偶数的时候,加1也不会有影响,比如当n=4时,此时k=2,那么
* 0和2合并,1和3合并,完美解决问题。
*/
ListNode* mergeKLists(vector<ListNode*>& lists) {
if (lists.empty()) return NULL;
int n = lists.size();
while (n > 1) {
int k = (n + 1) / 2;
for (int i = 0; i < n / 2; i++) {
lists[i] = mergeTwoLists(lists[i], lists[i + k]);
}
n = k;
}
return lists[0];
}
ListNode *mergeTwoLists(ListNode* A, ListNode *B) {
ListNode *C = new ListNode(-1);
ListNode *p = C; // 尾指针,指向链表的最后一个节点
while (A && B) {
if (A->val < B->val) {
p->next = A;
p = A;
A = A->next;
} else {
p->next = B;
p = B;
B = B->next;
}
}
if (A) p->next = A;
if (B) p->next = B;
return C->next;
}
/**
* 方法二:使用最小堆(不熟,要多看几遍)
* 我们首先把k个链表的首元素都加入最小堆中,它们会自动排好序。然后我们
* 每次取出最小的那个元素加入我们最终结果的链表中,然后把取出元素的下一
* 个元素再加入堆中,下次仍从堆中取出最小的元素做相同的操作,以此类推,
* 直到堆中没有元素了,此时k个链表也合并为了一个链表,返回首节点即可
*/
ListNode* mergeKLists(vector<ListNode*>& lists) {
auto cmp = [](ListNode*& a, ListNode*& b) {
return a->val > b->val;
};
priority_queue<ListNode*, vector<ListNode*>, decltype(cmp) > q(cmp);
for (auto node : lists) {
if (node) q.push(node);
}
ListNode *dummy = new ListNode(-1), *cur = dummy;
while (!q.empty()) {
auto t = q.top();
q.pop();
cur->next = t;
cur = cur->next;
if (cur->next)
q.push(cur->next);
}
return dummy->next;
}
leetcode-141-环形链表[offer23]
给定一个链表,判断链表是否有环
/**
* 方法一:使用快慢指针
*/
bool hasCycle(ListNode *head) {
if (!head || head->next == NULL) return false;
ListNode *fast = head, *slow = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow)
return true;
}
return false;
}
leetcode-142-环的入口节点[offer23]
给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
/**
* 方法一:使用快慢指针
*/
ListNode *detectCycle(ListNode *head) {
ListNode *fast = head, *slow = head;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
if (slow == fast) break;
}
if (!fast || !fast->next) return NULL; // 遍历到了NULL,说明不存在环
// 当fast 和 slow相遇时,此时fast已近比slow多走了 x = n即环的长度,
// 此时slow再从head开始走,当fast和slow相遇时,即是环的入口,即倒数第n个节点
slow = head;
while (fast != slow) {
fast = fast->next;
slow = slow->next;
}
return slow;
}
leetcode-160-两个链表的第一个公共节点[offer-52]
/**
* 方法一:先计算连个链表的长度,然后计算两个链表差多少lenDis,让长的链表先走lenDif,然后短的
* 链表再开始出发,然后当他们第一次相遇的时候,就是他们的第一个公共节点了。
*/
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int lenA = 0, lenB = 0, lenDis;
ListNode *p, *pLong, *pShort;
p = headA;
while (p) {
lenA++;
p = p->next;
}
p = headB;
while (p) {
lenB++;
p = p->next;
}
lenDis = lenA - lenB;
pLong = headA;
pShort = headB;
if (lenDis < 0) {
pLong = headB;
pShort = headA;
lenDis = -lenDis;
}
for (int i = 0; i < lenDis; i++) {
pLong = pLong->next;
}
while (pShort != pLong) {
pLong = pLong->next;
pShort = pShort->next;
if (!pLong || !pShort) // 有一个遍历到了NULL末尾,则说明他们没有相交节点
return NULL;
}
return pShort;
}
leetcode-86-分隔链表
给定一个链表和一个特定值 x,对链表进行分隔,使得所有小于 x 的节点都在大于或等于 x 的节点之前。
你应当保留两个分区中每个节点的初始相对位置。
示例:
输入: head = 1->4->3->2->5->2, x = 3
输出: 1->2->2->4->3->5
/**
* 方法一:首先找到第一个大于或等于给定值的节点,用题目中给的例子来说就是先找到4,
* 然后再找小于3的值,每找到一个就将其取出置于4之前即可.
* (其中的交换链表节点是很常用的方法)
*/
ListNode* partition(ListNode* head, int x) {
ListNode *header = new ListNode(-1);
header->next = head;
ListNode *pre = header, *cur = head;
while (pre->next && pre->next->val < x) {
pre = pre->next;
}
cur = pre;
while (cur->next) {
if (cur->next->val < x) {
ListNode *t = cur->next;
cur->next = t->next;
t->next = pre->next;
pre->next = t;
pre = pre->next;
} else {
cur = cur->next;
}
}
return header->next;
}
/**
* 方法二:将所有小鱼给定值的节点取出,组成一个新的链表,此时原链表中
* 剩余的节点的值都大于或等于给定值,只要将只要将原链表直接接在新链表后即可
*/
ListNode* partition(ListNode* head, int x) {
if (head == NULL) return head;
ListNode *header_A = new ListNode(-1);
ListNode *header_B = new ListNode(-1);
header_A->next = head;
ListNode *cur = header_A, *p = header_B;
while (cur->next) {
if (cur->next->val < x) {
p->next = cur->next;
p = p->next;
cur->next = cur->next->next;
p->next = NULL;
} else {
cur = cur->next;
}
}
p->next = header_A->next;
return header_B->next;
}