leetcode-链表
文章目录
前言
leetcode中的链表专题中的部分题目,这里记录了自己的解题思路。
一、160-相交链表
思路1:哈希表法,将headA中的所有节点都放入哈希表中,然后遍历headB。如果map[q]=1,就代表在哈希表存在该节点,就返回该节点。否则就返回nullptr。
C++代码:
struct ListNode {
int val;
ListNode *next;
ListNode(int x) : val(x), next(NULL) {}
};
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
unordered_map<ListNode*,int>map;
ListNode*p = headA;
while (p) {
map[p]++;//将headA中的元素放入哈希表中
p = p->next;
}
ListNode*q = headB;
while (q) {//遍历headB
if (map[q] == 1) {
return q;
}
q = q->next;
}
return nullptr;
}
};
思路2:双指针法,遍历headA与headB指向的链表。当p节点为空时就指向headB,当q节点为空时就指向headA,这样遍历一次p与q一定会相等。相等有两种情况,要么没有交点,都为nullptr;要么在交点处相等。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode*p = headA;
ListNode*q = headB;
while (p != q) {
p = p == nullptr ? headB:p->next;
q = q == nullptr ? headA : q->next;
}
return p;
}
};
二、206-反转链表
思路1:遍历。通过遍历反转链表,就需要知道每个遍历节点的前一个节点和后一个节点。
C++代码如下:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode*pre = nullptr;//前向节点
ListNode*next = nullptr;//后向节点
ListNode*cur = head;//当前节点
while (cur) {
next = cur->next;//记录当前节点的后一个节点
cur->next = pre;//反向
pre = cur;//更新前向节点
cur = next;//更新当前节点
}
return pre;//最后的pre指向的就是反向链表
}
};
思路2:递归。递归需要弄清楚三个点:1、递归体(每一次递归需要具体执行的内容)2、递归结束条件3、递归参数。对于该题,递归体是执行到当前节点,当前节点就反向,结束条件是当前节点或者下一个节点为空,递归参数就是当前节点的下一个节点。
C++代码:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
//当前节点或者下一节点为空时,结束循环
if (head == nullptr || head->next == nullptr) {
return head;
}
//当前节点不需要关注后面节点的情况,所以循环参数是head->next,
//因为是反向,所以返回的是新的头节点
ListNode *newHead = reverseList(head->next);
head->next->next = head;//下一个节点的next指向当前接待你,这样就完成了当前节点的反向
head->next = nullptr;//当前节点的next要指向nullptr,避免出现环
return newHead;
}
};
三、21-合并两个有序链表
思路1:递归。利用哑指针dummy,返回dummy->next。
C++代码:
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode*dummy = new ListNode(-1);
ListNode*p = list1;
ListNode*q = list2;
ListNode*temp = dummy;
while (p&&q) {//循环结束条件是有一个链表已经被遍历完
if (p->val <= q->val) {
temp->next = p;
p = p->next;
}
else {
temp->next = q;
q = q->next;
}
temp = temp->next;
}
temp->next = p == nullptr ? q : p;//返回不为空的链表
return dummy->next;
}
};
思路2:递归。
C++代码:
//递归
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if (list1 == nullptr) {//递归结束条件
return list2;
}
if (list2 == nullptr) {//递归结束条件
return list1;
}
if (list1->val < list2->val) {//递归体
ListNode*list = list1;//选择值较小的作为当前节点
list->next = mergeTwoLists(list1->next, list2);//再比较下一个节点
return list;//返回当前节点
}
else {
ListNode*list = list2;
list->next = mergeTwoLists(list1, list2->next);
return list;
}
}
};
四、83-删除排序链表中的重复元素
思路:遍历
C++代码:
//遍历
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return head;
}
ListNode*p = head;
while (p&&p->next) {
//这里一定要将p->next!=nullptr放在前面
while (p->next!=nullptr&&p->val == p->next->val) {
p->next = p->next->next;
}
p = p->next;
}
return head;
}
};
五、19-删除链表的倒数第N个节点
思路1:双指针法。定义一个快指针,和一个慢指针(哑指针,预防删除第一个节点的情况)。先让快指针移动n-1步,让后让快指针和慢指针一起移动直到快指针移动到表尾,这样慢指针指向的就是要删除节点的前一个节点。
C++代码:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
ListNode*fast = head;
ListNode*slow = new ListNode(-1, head);//慢指针
ListNode*p = slow;
for (int i = 1; i < n; ++i) {//让快指针先移动n-1次
fast = fast->next;
}
while (fast->next) {//快指针移动到链表尾,慢指针就是倒数第n个节点
fast = fast->next;
p = p->next;
}
p->next = p->next->next;
ListNode*ans = slow->next;
delete slow;
return ans;
}
};
思路2:栈。先定义一个哑指针,下一个节点指向头节点(预防删除头节点的情况)。然后将哑节点放入栈中,在出栈n次,目前的栈顶元素就是要删除节点的前一个节点。
C++代码:
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
stack<ListNode*>stk;
ListNode*dummy = new ListNode(-1, head);
ListNode*p = dummy;
while (p) {//入栈
stk.push(p);
p = p->next;
}
for (int i = 0; i < n; ++i) {
stk.pop();
}
//注意栈中的每一个节点都保持与原链表的关系,主要依据是对应地址上的内容是否放生改变
ListNode*pre = stk.top();//获取要删除节点的前一个节点
pre->next = pre->next->next;
ListNode*ans = dummy->next;
delete dummy;
return ans;
}
};
六、24-交换链表中的相邻节点
思路1:递归。递归结束条件是当head或者head->next=nullptr;递归体是改变两个节点的先后顺序。递归参数是原本的第二个节点的下一个节点,也是newHead的下一个节点。
C++代码:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
//递归结束条件
if (head == nullptr || head->next == nullptr) {
return head;
}
//递归体
ListNode*newHead = head->next;
head->next = swapPairs(newHead->next);
newHead->next = head;
return newHead;
}
};
思路2:遍历
C++代码:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
ListNode*dummy = new ListNode(-1, head);//定义哑指针
ListNode*temp = dummy;
while (temp->next&&temp->next->next) {
//每一次处理两个节点
ListNode*p = temp->next;
ListNode*q = temp->next->next;
temp->next = q;//取出第一个节点(原本是第二个节点)
p->next = q->next;//(第一个节点的后面指向改为原本第二个节点后面的指向)
q->next = p;//(原本的第二个节点,现在的第一个几点的后面一个节点就是原本的第一个节点)
temp = p;//将temp移动到第二个节点
}
return dummy->next;
}
};
七、445-两数相加Ⅱ
思路1:反转链表加求出链表长度。
C++代码:
//三次反转链表
class Solution {
public:
ListNode*reverseList(ListNode*head) {
if (head == nullptr || head->next == nullptr) {
return head;
}
ListNode*newHead = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return newHead;
}
int getLength(ListNode*head) {
ListNode*l = head;
int len = 0;
while (l) {
len++;
l = l->next;
}
return len;
}
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
l1 = reverseList(l1);
l2 = reverseList(l2);
int len1 = getLength(l1);
int len2 = getLength(l2);
ListNode*list;
if (len1 > len2) {
list = l1;
}
else {
list = l2;
}
ListNode*temp = list;
while (l1&&l2) {
temp->val = l1->val + l2->val;
temp = temp->next;
l1 = l1->next;
l2 = l2->next;
}
ListNode*temp1 = list;
int flag = 0;
while (temp1) {
if (flag == 1) {
temp1->val++;
flag = 0;
}
if (temp1->val >= 10) {
flag = 1;
temp1->val %= 10;
}
temp1 = temp1->next;
}
if (flag == 1) {
temp = list;
while (temp->next) {
temp = temp->next;
}
temp->next = new ListNode(1, nullptr);
}
return reverseList(list);
}
};
八、234-回文链表
思路1:将链表中的数据放入数组中。通过数组可以很方便的访问对应下标中的数据,同时数组的大小可以直接获取。只是这种方法的空间开销比较大。
C++代码:
class Solution {
public:
bool isPalindrome(ListNode* head) {
vector<int>vec;
ListNode*p = head;
while (p) {
vec.push_back(p->val);
p = p->next;
}
for (int i = 0, j = vec.size() - 1; i < j; ++i, --j) {
if (vec[i] != vec[j]) {
return false;
}
}
return true;
}
};
思路2:快慢指针。快指针每次走两步,慢指针每次走一步。这样就很容易获取链表的前半部分和后半部分。然后反转后半部分,将反转的后半部分与前面的进行比较。
C++代码:
class Solution {
public:
ListNode*reverseList(ListNode*head) {
if (head == nullptr || head->next == nullptr) {
return head;
}
ListNode*newHead = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return newHead;
}
ListNode*getFirstHalf(ListNode*head) {
//定义快慢指针
ListNode*fast = head;//快指针
ListNode*slow = head;//慢指针
while (fast->next&&fast->next->next) {
fast = fast->next->next;
slow = slow->next;
}
return slow;
}
bool isPalindrome(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return head;
}
ListNode*firstHalf = getFirstHalf(head);
ListNode*secondeHalf = reverseList(firstHalf->next);
//比较
bool flag = true;
ListNode*p = head;
while (secondeHalf&&flag) {
if (p->val != secondeHalf->val) {
flag = false;
}
secondeHalf = secondeHalf->next;
p = p->next;
}
firstHalf->next = reverseList(firstHalf->next);
return flag;
}
};
九、725-分割链表
思路:求出链表的长度,平均间隔,余数。
C++代码:
class Solution {
public:
int getLength(ListNode*head) {
ListNode*p = head;
int len = 0;
while (p) {
len++;
p = p->next;
}
return len;
}
vector<ListNode*> splitListToParts(ListNode* head, int k) {
int len = getLength(head);//获取链表长度
int gap = len / k;//平局间隔
int remainder = len % k;//余数
ListNode*p = head;
vector<ListNode*>ret(k,nullptr);
for (int i = 0; i < k&&p!=nullptr; ++i) {
ret[i] = p;
int l = gap + (remainder > i ? 1 : 0);//余数是否用完
for (int j = 1; j < l; ++j) {
p = p->next;
}
ListNode*next = p->next;//记录下下一个节点
p->next = nullptr;
p = next;
}
return ret;
}
};
十、328-奇偶链表
思路:定义奇偶链表,然后将二者连接起来。
C++代码:
class Solution {
public:
ListNode* oddEvenList(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return head;
}
ListNode*p = head;//定义奇指针链表
ListNode*q = head->next;//定义偶指针链表
ListNode*l1 = p;
ListNode*l2 = q;
while (l2&&l2->next) {
l1->next = l2->next;//奇指针的下一个节点指向偶指针的下一个节点
l2->next = l2->next->next;//偶指针的下一个节点指向下下一个节点
l1 = l1->next;//移动奇节点
l2 = l2->next;//移动偶节点
}
l1->next = q;//奇节点链表的尾部指向偶节点的头部,即连接奇偶链表
return p;
}
};
总结
链表的特性是不能直接通过下标访问某个节点,所有遍历在链表中十分常见。与遍历十分相关的就是递归,发现一个问题可以用递归解决时,考虑递归的三要素。如果考虑链表倒序的问题,可以利用双指针,或者栈的思想。