1. 找两个链表的交点(NO.160)
题目描述:编写一个程序,找到两个单链表相交的起始节点。要求时间复杂度为 O(N),空间复杂度为 O(1)。如果不存在交点则返回 null。
解题思路:
设 A 的长度为 a + c,B 的长度为 b + c,其中 c 为尾部公共部分长度,可知 a + c + b = b + c + a。
当访问 A 链表的指针访问到链表尾部时,令它从链表 B 的头部开始访问链表 B;同样地,当访问 B 链表的指针访问到链表尾部时,令它从链表 A 的头部开始访问链表 A。这样就能控制访问 A 和 B 两个链表的指针能同时访问到交点。
如果不存在交点,那么 a + b = b + a,以下实现代码中 l1 和 l2 会同时为 null,从而退出循环。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *l1 = headA, *l2 = headB;
while (l1 != l2){
l1 = (l1 == nullptr) ? headB : l1->next;
l2 = (l2 == nullptr) ? headA : l2->next;
}
return l1;
}
};
暴力解法:遍历两个链表,找交点。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
if (headA == nullptr || headB == nullptr)
return nullptr;
ListNode *idx = headA;
while (idx != nullptr){
ListNode *now = headB;
while (now != nullptr){
if (now == idx)
return now;
now = now->next;
}
idx = idx->next;
}
return nullptr;
}
};
如果只是判断是否存在交点,有两种解法:
(1) 把第一个链表的结尾连接到第二个链表的开头,看第二个链表是否存在环;
(2) 直接比较两个链表的最后一个节点是否相同。
2. 链表反转(NO.206)
题目描述:反转一个单链表。
解法一:使用三个指针。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if (head == nullptr || head->next == nullptr)
return head;
ListNode *pre = nullptr, *pNode = head, *pNext = head->next;
while(pNext != nullptr){
pNode->next = pre;
pre = pNode;
pNode= pNext;
pNext = pNext->next;
}
pNode->next = pre;
return pNode;
}
};
解法二:使用头插法创建一个新链表。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode *newHead = nullptr;
while (head != nullptr){
ListNode *tmp = head;
head = head->next;
tmp->next = newHead;
newHead = tmp;
}
return newHead;
}
};
3. 合并两个有序链表(NO.21)
题目描述:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
解题思路:使用尾插法,建立一个新链表。过程与归并两个有序数组一样。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
ListNode *res = (ListNode*)malloc(sizeof(ListNode));
res->next = nullptr;
ListNode *p = res;
while (l1 != nullptr && l2 != nullptr){
ListNode *tmp;
if (l1->val < l2->val){
tmp = l1;
l1 = l1->next;
}
else{
tmp = l2;
l2 = l2->next;
}
p->next = tmp;
p = tmp;
}
if (l1 != nullptr){
p->next = l1;
}
if (l2 != nullptr){
p->next = l2;
}
return res->next;
}
};
解法二:使用递归方法。
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == nullptr)
return l2;
if (l2 == nullptr)
return l1;
ListNode *newHead = nullptr;
if (l1->val < l2->val){
newHead = l1;
newHead->next = mergeTwoLists(l1->next,l2);
}
else{
newHead = l2;
newHead->next = mergeTwoLists(l1,l2->next);
}
return newHead;
}
};
4. 删除链表中的重复元素(NO.83)
题目描述:给定一个排序链表,删除所有重复的元素,使得每个元素只出现一次。
解法一:寻找与当前节点值相同的后续节点,释放空间,并将当前节点的next指针指向下一个与它的值不相同的节点。注意如果最后几个节点值相同,则tmp和pNode会是空指针,所以要加入相应的判断条件。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
ListNode *pNode = head;
while(pNode != nullptr && pNode->next != nullptr){
ListNode *tmp = pNode->next;
while (tmp != nullptr && tmp->val == pNode->val){
ListNode *del = tmp;
tmp = tmp->next;
delete del;//释放节点空间
}
pNode->next = tmp;
pNode = tmp;
}
return head;
}
};
解法二:使用递归。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (head == nullptr || head->next == nullptr)
return head;
head->next = deleteDuplicates(head->next);
return (head->val == head->next->val) ? head->next : head;
}
};
5. 删除链表的倒数第N个节点
题目描述:给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。
我的解法:先遍历一次链表,获得链表的长度length。再遍历找到倒数第n个节点的前一个节点,即正数第length-n个节点,然后将倒数第n个节点删除。注意n==length的话,要删除的是头结点。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
int length = 0;
ListNode *tmp = head;
while (tmp != nullptr){
length++;
tmp = tmp->next;
}
int k = length - n;//要删除节点的前一个节点
if (k == 0){//要删除的是头结点
ListNode *del = head;
head = head->next;
delete del;
}
else{
int i = 1;
ListNode *pNode = head;
while (i < k){
pNode = pNode->next;
i++;
}
ListNode *del = pNode->next;
pNode->next = del->next;
delete del;
}
return head;
}
};
进阶:通过一趟扫描实现删除过程。使用两个指针l1和l2,首先让l1移动到第n+1个节点处,让l2指向头结点,这样,当l1移动到尾结点时,l2正好移动到倒数第n+1个结点处。这样理解:删除倒数第n个结点,需要找其前驱结点,即倒数第n+1个节点,其标号为N-n,从头结点移动到该节点需要N-n-1次,而从标号为n+1的结点移动到尾结点也是N-n-1次,所以首先让l1移动到第n+1个节点处,然后l1和l2同时向后移动。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode *l1 = head;
while (n > 0){
n--;
l1 = l1->next;
}
if (l1 == nullptr){
ListNode *del = head;
head = head->next;
delete del;
}
else{
ListNode *l2 = head;
while (l1->next != nullptr){
l1 = l1->next;
l2 = l2->next;
}
ListNode *del = l2->next;
l2->next = del->next;
delete del;
}
return head;
}
};
6. 两两交换链表中的节点(NO.24)
题目描述:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。注意不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
解题思路:使用三个指针preNode,pNode,nexNode,交换pNode和nexNode所指结点,然后三个指针都后移两位进行下一次的交换。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode *newNode = (ListNode*)malloc(sizeof(ListNode));
newNode->next = head;
ListNode *preNode = newNode, *pNode = head;
while (pNode != nullptr && pNode->next != nullptr){
ListNode *nexNode = pNode->next;
preNode->next = nexNode;
pNode->next = nexNode->next;
nexNode->next = pNode;
preNode = pNode;//后移,注意pNode和nexNode换了位置,所以更新preNode=pNode
pNode = pNode->next;
}
return newNode->next;
}
};
7. 两个链表数字求和(NO.445)
题目描述:给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。可以假设除了数字 0 之外,这两个数字都不会以零开头。
解题思路:将两个链表中的数据放在两个栈中,将出栈的两个元素相加,并判断是否需要进位。
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode *head1 = l1;
stack<int> stack1;
while (head1 != nullptr){
stack1.push(head1->val);
head1 = head1->next;
}
ListNode *head2 = l2;
stack<int> stack2;
while (head2 != nullptr){
stack2.push(head2->val);
head2 = head2->next;
}
ListNode *res = nullptr;
int flag = 0;
while (!stack1.empty() || !stack2.empty() || flag != 0){
int a = 0, b = 0;
if (!stack1.empty()){
a = stack1.top();
stack1.pop();
}
if (!stack2.empty()){
b = stack2.top();
stack2.pop();
}
int num = a + b + flag;
if (num >= 10){
flag = 1;
num -= 10;
}
else
flag = 0;
ListNode *s = new ListNode;
s->val = num;
s->next = res;
res = s;
}
return res;
}
};
8. 分割链表(NO.725)
题目描述:给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。返回一个符合上述规则的链表的列表。
输入: root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3
输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]]
解题思路:首先遍历链表得到链表的长度len,根据长度和分割的份数确定每份的长度。通过len/k获得平均长度,len%k得到较长子表的份数。接下来就是根据每个子表的长度分割链表,要将每一个子表最后一个结点的next指针置为nullptr来断开原链表中的连接。
class Solution {
public:
vector<ListNode*> splitListToParts(ListNode* root, int k) {
int len = 0;
ListNode *begin = root;
while (begin != nullptr){
len++;
begin = begin->next;
}
int subLen = len / k;
int n1 = len % k;
begin = root;
ListNode* pre = nullptr;
vector<ListNode*> result(k,nullptr);
for (int i = 0; begin != nullptr && i < k; i++){
result[i] = begin;
int curLen = i < n1 ? subLen + 1 : subLen;
for (int j = 0; j < curLen; j++){
pre = begin;
begin = begin->next;
}
pre->next = nullptr;
}
return result;
}
};
9. 链表元素按节点编号的奇偶性聚集(NO.328)
题目描述:给定一个单链表,把所有的奇数节点和偶数节点分别排在一起。请注意,这里的奇数节点和偶数节点指的是节点编号的奇偶性,而不是节点的值的奇偶性。算法的空间复杂度应为 O(1),时间复杂度应为 O(nodes),nodes 为节点总数。
输入: 2->1->3->5->6->4->7->NULL
输出: 2->3->6->7->1->5->4->NULL
解题思路:结点1作为奇数链的头,结点2作为偶数链的头,使用odd和even两个指针,从第3个点开始遍历,依次轮流附在奇、偶链的后面。遍历完后,奇数链的尾连向偶链的头,偶链的尾为空, 返回奇数链的头。注意循环结束的判断条件。
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if (head == nullptr)
return head;
ListNode *Odd = head;
ListNode *EvenHead = head->next, *Even = EvenHead;
while (Even != nullptr && Even->next != nullptr){
Odd->next = Even->next;
Odd = Even->next;
Even->next = Odd->next;
Even = Odd->next;
}
Odd->next = EvenHead;
return head;
}
};
10. 回文链表(NO.234)
题目描述:请判断一个链表是否为回文链表。
解题思路:使用快慢两个指针,快指针每次移动两个,满指针每次移动一个,当快指针到链表末尾时,满指针正好到达链表中间。将前一部分满指针遍历的节点值添加到一个栈中,并与链表后半部分元素比较。注意这里考虑链表节点总数是奇数还是偶数,若为奇数,中间节点值不用进栈,若为偶数,中间节点值进栈。
class Solution {
public:
bool isPalindrome(ListNode* head) {
if (head == nullptr)
return true;
ListNode* fast = head;
ListNode* slow = head;
stack<int> st;
while (fast->next != nullptr && fast->next->next != nullptr){
st.push(slow->val);
slow = slow->next;
fast = fast->next->next;
}
if (fast->next != nullptr)//偶数个节点,将中间元素加入
st.push(slow->val);
slow = slow->next;//后移一位,指向链表后半部分的第一个节点
while (slow != nullptr){
if (slow->val != st.top())
return false;
slow = slow->next;
st.pop();
}
return true;
}
};