一、单链表问题
定义ListNode节点结构体
struct ListNode {
int val;
ListNode *next; //定义节点的后继
ListNode(int x) : val(x),next(NULL);//声明一个节点
}
1、链表反转
1->2->3->4->5->null
^
root
逆序的思路:首先声明一个空指针node指向一个空节点,然后将原链表中的节点1指向node,接着声明一个临时根节点root用来保存链表的表头,循环操作,直到root为空指针
示例程序如下:
ListNode *reverse(ListNode *root)
{
ListNode *node = nullptr;
while(root){
ListNode *next = root->next; //保存表头的位置
//由于逆序,因此此时原表头的下一个节点为空节点 ,下一个node为原表头
root->next = node;
node = root; //保存此时的根节点,准备下一次连接
root = next; //重置新的表头
}
return node;
}
程序执行流程如下:
//step1: 保存表头的位置
ListNode *next = root->next;
2->3->4->5->null
^
next
//step2:连接新的链表
root->next = node;
1->null
^
root
//step3:更新待连接的节点位置
node = root;
1->null
^
node
//step4:更新表头位置
root = next;
2->3->4->5->null
^
root
//循环操作,直到root为null
2、链表去重
去除链表中重复的数字,分两种情况:1,链表是有序链表;2,链表是无序链表
2.1有序链表去重
有序链表:即链表中的元素按照一定顺序排列,链表中的相同元素是相邻的,比如升序,降序。
eg:
1->2->2->3->4->5->null | 1->2->2->2->3->4->null
^ | ^
root | root
算法思路:首先明确判断的条件:相同的元素是相邻的,即cur->val == cur->next->val
,找到这个元素后,先将该元素(cur->next
)的后继节点连接到一个临时节点保存起来,然后删除cur->next
,此时cur->next
为空,最后将临时节点连接到当前cur
的后继。此处不能直接将cur->next
的后继节点赋值给cur->next
,也不能首先删除cur->next
,一定要缓存cur->next
。
//code:
void removeDuplicates(ListNode *head) {
if (head == nullptr) return; //若是空指针,则直接跳出
for (ListNode *cur=head; cur->next; ) //拷贝head到cur,判断cur->next是否为空,为空就跳出循环;
if (cur->val == cur->next->val) { //比较条件
ListNode *next = cur->next->next; //临时缓存cur->next的后继节点
delete cur->next; //删除重复的节点,释放内存
cur->next = next; //将cur->next的后继节点连接到cur的后继
} else
{
cur = cur->next; //比较下一个节点
}
}
2.2无序链表去重
无序链表:即链表中的元素顺序是无规则的,相同元素是不相邻的,此时删除的是后面重复的元素。
eg:
1->2->3->2->4->5->null | 1->2->3->2->1->3->null
^ | ^
root | root
算法思路:首先明确判断的条件:寻找后续的节点中的元素与当前节点是否相同,寻找到相同的元素后删除节点,继续寻找剩下的元素中是否有相同的元素。
//code1:时间复杂度O(N^2),空间复杂度O(1)
void removeRep1(ListNode* head){
if(head == nullptr){
return;
}
ListNode* cur = head;
ListNode* pre = nullptr;
ListNode* next = nullptr;
//选中一个节点,逐个比较后面是否有重复的节点,有则删除
while(cur != nullptr){
pre=cur;
next=cur->next;
while(next != nullptr){
if(cur->val==next->val){ //后面next的节点等于当前cur节点,删除next节点
pre->next = next->next;
delete next; //删除重复的节点
next = pre; //将链表重新连接起来
}
else
{
//不重复
pre = next;
}
next = next->next;
}
cur = cur->next;//再以下一个节点作为参考点
}
}
另外一种思路,利用容器的find功能进行遍历
//code2:时间复杂度O(N),空间复杂度O(N)
void removeRep1(ListNode *head){
if(head==nullptr){
return;
}
set<int> val_set; //利用容器find的功能
ListNode *pre = head; //声明前一个节点
ListNode *cur = head->next; //声明当前比较的节点
val_set.insert(pre->val); //头结点加到容器列表中
while(cur != nullptr)
{
//判断容器中是否已存在cur->val,不存在,就将该元素添加到容器中
if(val_set.find(cur->val) == val_set.end())
{
val_set.insert(cur->val);
pre = cur; //更新上一个节点
}
else
{
pre->next = cur->next; //删除重复节点
delete cur; //删除重复的节点的内存
cur = pre; //将链表重新连接起来
}
cur = cur->next; //再以下一个节点作为参考点
}
}
3、链表合并
链表合并,即将两个链表合并为一个链表,不是简单的首尾合并,示例如下:
1->2->3
^
a_root
==> 1->4->2->5->3->6
4->5->6 ^
^ new_list
b_root
算法思路:在程序中使用dummy节点来缓存节点,其作用是保存新的链表,头节点的值可任意设置,最后返回dummy.next即可。
//code:
ListNode *shuffleMerge(ListNode *a, ListNode *b) {
ListNode dummy(0), *tail = &dummy;
while (a && b) {
tail->next = a; //初始时tail->next为空,以后的循环中令上一轮中的节点b指向下一个a
tail = a; //更新tail的地址,以便下一次的拼接
a = a->next; //处理a的下一个节点
tail->next = b; //上一轮中的节点a指向下一个b
tail = b; //更新tail的地址,以便下一次的拼接
b = b->next; //处理b的下一个节点
}
tail->next = (a ? a : b); //若a和b不是等长的,将剩余部分拼接到tail的末尾
return dummy.next; //返回拼接好的节点的头结点
}
4、链表顺序插入
给一个有序链表root,先有一个新节点需要插入该列表中。
4
^
newNode
==> 1->3->4->5->7->8
1->3->5->7->8
^
root
算法思路1:直接法,遍历整个链表,找到newNode合适的位置插入
//code:
void sortinsert(ListNode **root, ListNode *newNode) {
if (*root == nullptr || (*root)->val >= newNode->val) {
newNode->next = *root;
*root = newNode; //更新链表root
}
else
{
ListNode *cur = *root;
//如果节点cur不为空,而且下一个节点的值小于newNode的值,则移动cur的位置
while(cur->next != nullptr && cur->next->val < newNode->val)
{
cur = cur->next;
}
//否则,将新节点插入当前位置
newNode->next = cur->next;
cur->next = newNode;
}
}
算法思路2:和引用类似的思路,不过不再是cur的移动,而是指针的移动
//code:
void sortinsert(ListNode **root, ListNode *newNode) {
ListNode **cur = root;//拷贝更节点
//如果节点*cur不为空,而且下一个节点的值小于newNode的值,则移动*cur的位置
while(*cur != nullptr && (*cur)->val < newNode->val){
cur = &((*cur)->next);
}
//否则,将新节点插入当前位置
newNode->next = *cur;
*cur = newNode;
}
5、链表排序
根据上述的顺序插入,链表的排序问题关键在于如何插入元素。
算法思路:声明一个空链表ListNode *newroot = nullptr
,然后遍历整个链表,再用上一节提到的顺序插入函数,将每一个节点用sortinsert
到newroot
void insertSort(ListNode **head) {
ListNode *newHead = nullptr; //声明空节点
ListNode *curr = *head, *next; //声明一个cur和一个临时节点
//若当前节点不为空
while (curr) {
next = curr->next; //缓存后续的节点
sortinsert2(&newHead, curr); //处理当前的节点
curr = next; //更新当前节点,准备处理下一个节点
}
*head = newHead; //更新链表
}