本笔记不以刷题数量为目的,而是希望通过整理各类题型,总结归纳学习中的解题思路,望各位大佬批评指正。
1. 反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
示例:
输入: 1->2->3->4->5->NULL
输出: 5->4->3->2->1->NULL
分析:
反转链表即改变每一个节点的指向。
链表一般通过指针操作,因此需要一个指针访问链表中的某一个节点,然后将.next指向上一个节点,因此上一个节点需要也需要一个指针,这样操作后发现改变当前节点指向后将无法访问到下一个节点,因此还需要一个指针提前保存当前节点的下一个节点。
变量名称:
当前节点:cur
上一个节点:pre
下一个节点:next
初值 cur=head,pre=nullptr,next=nullptr,
使用while循环,先判断条件再执行后面语句,当当前节点cur为空指针时,表明反转操作执行完毕。
注意:尽量不要直接使用提供的head指针,这容易导致分析问题时指代混乱。
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur=head, * pre=nullptr, *next=nullptr;
while(cur!=nullptr)
{
next=cur->next;//先保存当前节点的下一个节点,因为当前节点指向上个节点后无法访问到下个节点
cur->next=pre;//将当前节点指向上一个节点
pre=cur;//移动上一个节点的指针到当前节点
cur=next;//移动当前节点的指针到下一个节点
}
return pre;
}
};
2. 求两个链表的交点
已知链表A的头节点指针headA,链表B的头结点指针headB,两个链表相交,求两链表交点对应的节点。
分析:
节点相交说明链表A上某个节点与B上某节点的地址相同
最简单的思路:取出任意B上的节点,循环比较B上节点与A的节点是否相同。但这样计算量很大。
发现砍掉较长链表多出的部分后,两链表到交点的距离相同,这样通过两个指针便可以轻松访问对比两个节点的地址是否一致。这样做需要先计算链表长度len,再将较长链表的head指针向后移动两链表长度的差值。
变量名称:
链表A的长度:lenA
链表B的长度:lenB
计算链表长度的函数getListLength
将长链表向后移动的函数moveLongList
//计算链表长度。方法:只要头指针不为空,链表长度加1,随后头指针后移一位
int getListLength(ListNode* head)
{
int len = 0;
while (head!=nullptr)
{
len += 1;
head = head->next;
}
return len
}
//将较长链表的头指针后移两链表长度的差值,使两个链表的头指针在同一起跑线上。
ListNode* moveLongList(ListNode* head lenA, lenB)
{
int delta = abs(lenA - lenB);
while (head!=nullptr&&delta!=0)
{
head = head->next;
delta -= 1;
}
return head;
}
class Solution {
public:
ListNode* getIntersectionNode(ListNode* headA, ListNode* headB) {
int lenA = getListLength(headA);
int lenB = getListLength(headB);
if (lenA>lenB)
{
headA = moveLongList(headA, lenA, lenB);
}
if (lenA < lenB)
{
headB = moveLongList(headB, lenA, lenB);
}
while (headA!=nullptr&&headB!=nullptr)
{
if (headA==headB)
{
return headA;
}
headA = headA->next;
headB = headB->next;
}
return NULL;
}
};
3. 链表求环
已知链表中可能存在环,若有环返回环起始节点,否则返回NULL。
分析:
有环的判断条件是链表中有两个节点指向了同一个节点
用set容器保存访问过的节点地址,与新访问的地址进行比较
set集合中每个元素只出现一次,默认按键值升序排列
find()在集合中查找,找到则返回当前迭代器,没找到则返回尾部迭代器,迭代器相当于一个指针
class Solution
{
public:
ListNode* detectCycle(ListNode* head)
{
std::set<ListNode*>node_set;//用set容器保存已经访问过的节点地址
while (head!=nullptr)
{
if (node_set.find(head) != node_set.end())//在set容器中查找新节点,如果找到了,直接返回该节点的指针。
{
return head;
}
node_set.insert(head);
head = head->next;
}
return NULL;
}
};
4. 链表划分
已知链表头指针head和数值x,将所有小于x的节点放在大于或等于x的节点前,且保持这些节点的原来相对位置。
分析:
因为不能改变节点原来的相对位置,考虑遍历链表时先断开值大于x和小于x节点的连接,将小于x的链表和大于x的链表分开拼接,用指针记录小于x节点和大于等于x链表的头尾地址,最后合并。
如果使用指针记录两组数据的第一个节点,那么首先需要判断指针所指的值大于x还是小于x,分别找到第一个大于x和小于x的节点后才能固定指针的指向。因此更好的方法是直接创建两个节点,做好了两个容器等着添加数据。
变量名称
小于x的链表的头节点less_head
小于x的链表的尾指针less_ptr
大于等于x的链表的头节点more_head
大于等于x的链表的尾指针more_ptr
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
ListNode less_head(0);
ListNode more_head(0);
ListNode* less_ptr = &less_head;
ListNode* more_ptr = &more_head;
while (head != nullptr)
{
if (head->val < x)//判断新节点的值是否小于x,若小于x则将链接新节点,并将小数组的尾指针指向新节点
{
less_ptr->next = head;
less_ptr = less_ptr->next;
}
else
{
more_ptr->next = head;
more_ptr = more_ptr->next;
}
head = head->next;
}
less_ptr->next = more_head.next;
more_ptr->next = NULL;
return less_head.next;
};
5. 复杂的链表的深度拷贝
已知一个复杂的链表,节点中有一个指向本链表任意某个节点的随机指针(也可以为空),求这个链表的深度拷贝。
分析:
按照next的顺序遍历链表。拷贝当前节点,确定新节点的指向,此时需要一个指针保存上一个节点,同是还需要一个指针一直保存新链表头节点的地址,于是可以选择直接创建头节点。连接完成后发现无法将原random的指向与当前random对应,于是调整思路。
使用map,通过键值对保存新旧节点,键是旧的链表节点,值是新的链表节点,这样random的指向就可以通过旧链表节点 找到对应的新链表节点,完成深拷贝。
变量名称
旧链表当前节点cur
新链表当前节点map[cur]
class Solution {
public:
Node* copyRandomList(Node* head) {
if(head == nullptr) return nullptr;
Node* cur = head;//1. 用cur指向旧链表的第一个节点
unordered_map<Node*, Node*> map;//2. 创建map
// 3. 复制各节点,并建立 “原节点 -> 新节点” 的 Map 映射
while(cur != nullptr) {
map[cur] = new Node(cur->val);
cur = cur->next;
}
cur = head;
// 4. 构建新链表的 next 和 random 指向
while(cur != nullptr) {
map[cur]->next = map[cur->next];//新链表节点的next指向旧链表节点next对应的新链表的节点
map[cur]->random = map[cur->random];
cur = cur->next;
}
// 5. 返回新链表的头节点
return map[head];
}
};
6. 排序链表的合并
已知两个已排序的链表头节点指针l1和l2,将这两个链表合并,合并后仍为有序的,返回合并后的头节点。
分析:
思路1:由于要返回头节点,头节点是固定不动的,直接创建一个节点可以省去在两链表中挑选一个较小值节点做头节点的麻烦。
相当于两个链表从头依次比较大小,较小的节点会被指向,然后指针后移一位。
由于是从小到大排列,比较完l1节点和l2节点的值后,较小值所在的链表指针后移一位。
因此需要有两个指针cur1和cur2.
思路2:比较两个节点值,将较小节点作为头节点,保存较小节点的下一个节点,改变较小节点的指向,将较小节点的下一个节点作为当前节点再进行下一轮比较
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode node = new ListNode(0);
LietNode* cur1 = list1, * cur2 = list2, * cur=&node;
while (cur1!=nullptr&&cur2!=nullptr)
{
if (cur1->val < cur2->val)//比较两链表节点的值,较小节点被指向
{
cur->next = cur1;
cur1=cur1->next
}
else
{
cur->next = cur2;
cur2 = cur2->next;
}
cur = cur->next;//合并后的链表指针后移一位。
}
if (cur1 != nullptr)
cur->next = cur1;
if (cur2 != nullptr)
cur->next = cur2;
return node.next;
}
};
7. 排序链表的合并
已知k个已排序的链表头节点指针,将这k个链表合并,合并后仍是有序的,返回合并后的头节点。
分析:将kn个节点放到vector中,再将其排序,最后节点顺序相连
bool cmp(const ListNode* a, const ListNode* b)
{
return a->val < b->val;
}
class Solution
{
public:
ListNode* mergeKLists(std::vector<ListNode*>& lists)
{
std::vector<ListNode*>node_vec;
for (int i = 0; i < lists.size(); i++)
{
ListNode* head = lists[i];
while (head != nullptr)
{
node_vec.push_back(head);
head = head->next;
}
}
if (node_vec.size() == 0)
{
return NULL;
}
std::sort(node_vec.begin(), node_vec.end(), comp);
for (int i = 1; i < node_vec.size(); i++)
{
node_vec[i - 1]->next = node_vec[i];
}
node_vec[node_vec.size - 1]->next = NULL;
return node_vec[0];
}
};