Leetcode:
T1:利用 “归并排序” 对 链表 进行 排序:
关键:
(1)merge_sort函数 : 递归 函数--出口,直到只有1个 或者 0个 元素为止,直接返回这个节点,作用就是 链表 分成 2半,
(2)merge_sort函数中:因为是链表,所以需要 利用 fast ,slow快慢指针 找到中间位置,然后分别找到 left链表 和 right链表的 头节点(注意把 left链表的 尾节点 设置为 NULL)
(3)merge函数:不需要用递归 实现 ,直接用while循环即可, 直到其中有一个指针为NULL,最后接上剩下的 那个链表(等价于上课 讲到的 2个有序 链表的 合并)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
//其中 一些 注释 用于之前的 调试,因为 函数体里面的 临时指针很容易被 销毁掉
class Solution {
public:
//利用 归并排序加以实现:
ListNode* sortList(ListNode* head) {
head=merge_sort(head);
return head;
}
ListNode* merge_sort(ListNode* head)
{
//static int time=1;
//cout<<"这是第"<
T2: 重排链表 (有点像 2个 链表交错)
给定一个单链表 L
的头节点 head
,单链表 L
表示为:
L0 → L1 → … → Ln-1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln-1 → L2 → Ln-2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
解:
1.关键:
(1)总的来说 : 就是3个 操作: 快慢指针找到中间点 -- 翻转后半链表 -- “交错合并”2个链表
(2) 注意 ,交错合并的 时候 指针的指向 次序 需要 “小心翼翼”
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
void reorderList(ListNode* head) {
//妙啊,这种 “交错的结构”--找中间点 , 然后对后半部分翻转 ,最后合并2个list
//特殊,只有一个节点,直接返回
if(head==NULL || head->next == NULL)
{
return ;
}
//1.找到中间点:(利用快慢指针 -- 找到“向下取整”的中间点)
ListNode* fast=head->next;
ListNode* slow=head;
while(fast!=NULL && fast->next!=NULL)
{
fast=fast->next->next;
slow=slow->next;
}
//此时slow就是那个“向下取整的”中间点
//无所谓 ,反正 多出的哪一个 最后接上就好了
//2.将slow->next 一直到 NULL前的 node进行翻转
ListNode* start=slow->next;
ListNode* tmp=start->next; //tmp永远维持 start的原序的 下一个节点
start->next=NULL; //翻转后的 “尾部” 置NULL
//别忘记了,需要把 head的 最后一个节点的next置NULL
slow->next=NULL;
while(tmp!=NULL && start!=NULL)
{
//tmp需要 指向现在的 start然后tmp给start,把u给tmp
ListNode* u=tmp->next;
tmp->next=start;
start=tmp;
tmp=u;
//成功翻转
}
//----测试: --好了, 上面的 步骤都没问题
//show(head);
//show(start);
//tmp==NULL,start指向新的起点
//3.好了2个链表的起点分别是head 和 start
//开始 “交错合并”2个链表
ListNode* pa=head;
ListNode* pb=start;
ListNode* pc=new ListNode(0);//头节点
ListNode* new_head=pc;
while(pa!=NULL && pb!=NULL)
{
pc->next=pa;
pa=pa->next;
//--次序
pc=pc->next;
pc->next=pb;
pb=pb->next;
pc=pc->next;
}
if(pa!=NULL)
{
pc->next = pa;
}
if(pb!=NULL)
{
pc->next=pb;
}
head=new_head->next;
}
//测试函数
void show(ListNode* node)
{
while(node!=NULL)
{
cout<val<<" ";
node=node->next;
}
cout<
T3:(链表的必备熟练操作之一)翻转链表
给定单链表的头节点 head
,请反转链表,并返回反转后的链表的头节点。
解:
1.关键:
由于 翻转的 指针变化 ,需要逻辑清晰的 变化tmp , head ,临时u这3个指针,次序很重要
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
//这是一个基本的 操作,应该作为像“冒泡排序”一样熟练
//tmp指向 head的 下一个节点,u作为临时节点
//特殊情况
if(head == NULL || head->next == NULL)
{
return head;
}
//--
ListNode* tmp=head->next;
head->next=NULL; //翻转后的 为节点的 next指针 置NULL
while(tmp!=NULL && head!=NULL)
{
//将tmp->next 给到u ,然后及那个tmp->next指向head,tmp给head,u给tmp
ListNode* u=tmp->next;
tmp->next=head;
head=tmp;
tmp=u;
}
return head;
}
};
T4: 判断一个链表是否为 “回文链表”
给定一个链表的 头节点 head
,请判断其是否为回文链表。
如果一个链表是回文,那么链表节点序列从前往后看和从后往前看是相同的。
解:
1.关键:
(1) 就是 3个 基本 操作的 组合: 快慢指针找到中间点 -- 翻转链表 -- 2个链表对应位置的比较
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
//3个操作: 找中间点 - 翻转后半部分 - 2个链表的val比较
//--特殊情况:
if(head == NULL || head->next == NULL)
{
return true;
}
//1.利用 快慢指针 找到中间点
ListNode* fast=head->next;
ListNode* slow=head;
while(fast!=NULL && fast->next!=NULL)
{
fast=fast->next->next;
slow=slow->next;
}
ListNode* start=slow->next;
slow->next= NULL;
//2.翻转后半 链表
ListNode *tmp = start->next;
start->next=NULL; //翻转 后的 为节点的 next指针 置NULL
while(tmp!=NULL && start!=NULL)
{
ListNode* u=tmp->next;
tmp->next=start;
start=tmp;
tmp=u;
}
//3. 2个链表之间的 比较 ,头节点分别 是 head 和 start
while(head !=NULL && start!=NULL)
{
if(head->val != start->val)
{
return false;
}
head = head->next;
start=start->next;
}
return true;
}
};
T5:合并 多个 有序链表
给定一个链表数组,每个链表都已经按升序排列。
请将所有链表合并到一个升序链表中,返回合并后的链表。
解:
1.关键:
(1)基础:2个有序链表的 排序
(2)思维: 利用 多元gcd的求解思维, 多元 == 依次二元
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector& lists) {
//这个题目 就是 多个 链表的 merge
//借鉴 求解多元gcd的 思路,多个 == 依次 2个,所以只要不断调用 2个链表的 合并
//特殊情况
if(lists.size() == 0)
{
return NULL;
}
ListNode* ans = lists[0];
int size= lists.size();
for(int i=1;ival < right->val)
{
tmp->next=left;
left=left->next;
tmp=tmp->next;
}
else
{
tmp->next=right;
right=right->next;
tmp=tmp->next;
}
}
//--那个还有剩余
if(left!=NULL)
{
tmp->next=left;
}
if(right!=NULL)
{
tmp->next=right;
}
return ans->next;
}
};
T6:复杂链表的 复制
请实现 copyRandomList
函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个 next
指针指向下一个节点,还有一个 random
指针指向链表中的任意节点或者 null
。
解:
1.关键:
(1)这是一道 关于 unordered_map 的问题
(2)首先 遍历 一次 , 存储下 对应的 next指针 和 在map中存储 对应的Node的对应关系
(3)最后遍历一次, 存储对应的 random指针
2.代码:
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node* random;
Node(int _val) {
val = _val;
next = NULL;
random = NULL;
}
};
*/
class Solution {
public:
Node* copyRandomList(Node* head) {
//特殊
if(head == NULL)
{
return NULL;
}
//设置一个 递增变量指针
Node* tmp=head;
//设置一个map用于存储对应的 Node的关系
unordered_map Map;
//利用一个map进行存储 两者对应的节点
Node* new_head=new Node(head->val);
Map[head]=new_head;
tmp=tmp->next;
Node* tmp2=new_head;
while(tmp!=NULL)
{
//每次开辟一个新的 NOde空间,
Node* new_node=new Node(tmp->val);
Map[tmp]=new_node;
tmp2->next=new_node;
tmp2=tmp2->next;
tmp=tmp->next;
//Map中存储对应的 Node的 关系:
}
//2.遍历了之后,需要 再次遍历 ,并且处理 random指针
tmp=head;
tmp2=new_head;
while(tmp!=NULL)
{
tmp2->random = Map[tmp->random];
tmp=tmp->next;
tmp2=tmp2->next;
}
return new_head;
}
};
T7: 向 有序 环形链表中 insert一个节点
给定循环单调非递减列表中的一个点,写一个函数向这个列表中插入一个新元素 insertVal
,使这个列表仍然是循环升序的。
给定的可以是这个列表中任意一个顶点的指针,并不一定是这个列表中最小元素的指针。
如果有多个满足条件的插入位置,可以选择任意一个位置插入新的值,插入后整个列表仍然保持有序。
如果列表为空(给定的节点是 null
),需要创建一个循环有序列表并返回这个节点。否则。请返回原先给定的节点。
解:
1.关键:
(1) 模拟遍历查找 + insert节点操作(熟练)
(2)需要考虑 一些 特殊操作 和 取等、、、那些情况
2.代码:
/*
// Definition for a Node.
class Node {
public:
int val;
Node* next;
Node() {}
Node(int _val) {
val = _val;
next = NULL;
}
Node(int _val, Node* _next) {
val = _val;
next = _next;
}
};
*/
class Solution {
public:
Node* insert(Node* head, int insertVal) {
//思路: 直接模拟 遍历查找 + 插入一个节点的 操作即可
//特殊情况:
if(head == NULL)
{
Node* new_head =new Node(insertVal);
new_head->next = new_head;
return new_head;
}
if(head->next == head)
{
Node* new_head =new Node(insertVal);
new_head->next = head;
head->next = new_head;
return head;
}
//(1)copy 保留开始 的head节点
Node* tmp = head;
Node* new_node=new Node(insertVal);
//将这个new_node通过遍历 找到合适的 位置, 然后 执行insert操作
//(2)遍历
//符合条件的位置: tmp->val < next->val tmp->val < insertVal && tmp->next->val
//或者: tmp->val > next->val && tmp->val < insertVal
int i=1;
while(1)
{
if(tmp == head && i!=1)
{
//说明 所有的val都相同
new_node->next = tmp->next;
tmp->next = new_node;
return head;
}
i++;
cout<<"来过吗 "<val <val < tmp->next->val && tmp->next->val >= insertVal && tmp->val <= insertVal)
{
new_node->next = tmp->next;
tmp->next = new_node;
return head;
}
else if(tmp->val > tmp->next->val && (tmp->val <= insertVal || tmp->next->val >= insertVal))
{
new_node->next = tmp->next;
tmp->next = new_node;
return head;
}
//case3:
tmp=tmp->next;
}
//:只剩 一种case 需要考虑, 就是所有的 val都相等怎么办?
//很简单 , 那时候tmp一定会回到head,直接insert到这个位置即可
}
};
T8:删除 链表 节点(基本操作)
给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。
返回删除后的链表的头节点。
解:
1.关键:
(1)利用 “双指针 ”找到需要删除的 节点的前驱节点
(2)然后 next 指next->next即可
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* deleteNode(ListNode* head, int val) {
//找到这个 节点的 前驱节点的指针即可
ListNode* tmp=head->next;
ListNode*tmp2=head;
//特殊情况:
if(head->val == val)
{
head = head->next;
return head;
}
//--一般情况:
while(tmp->val !=val)
{
tmp2=tmp;
tmp = tmp->next;
}
tmp2->next = tmp2->next->next;
return head;
}
};
T9:展平 多级 双向 链表
多级双向链表中,除了指向下一个节点和前一个节点指针之外,它还有一个子链表指针,可能指向单独的双向链表。这些子列表也可能会有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。
给定位于列表第一级的头节点,请扁平化列表,即将这样的多级双向链表展平成普通的双向链表,使所有结点出现在单级双链表中。
解:
1.关键:
(1)这个 “结构图 ”一看 马上 反应过来 就是 dfs(深搜) , 然后 直接用递归实现
(2)重点是 , 处理好 每次dfs的 出口时 head->next == NULL 以及 最后一个 节点的 child是否还有,
!! 每次返回的 都是 “当前这一层的 最后一个 节点的指针” , 除非是 next为 NULL,而child不为NULL,这时候就需要 返回 下一层的 最后一个节点 ,作为 “这一层的”最后一个节点
2.代码:
/*
// Definition for a Node.
class Node {
public:
int val;
Node* prev;
Node* next;
Node* child;
};
*/
class Solution {
public:
Node* flatten(Node* head) {
//很明显的 一个 dfs深度优先搜索的 结构
//特殊:
if(head == NULL)
{
return NULL;
}
dfs(head);
//--遍历一次, 置空所有的 CHILD指针域
Node* tmp=head;
while(tmp!=NULL)
{
tmp->child = NULL;
tmp= tmp->next;
}
return head;
}
Node* dfs(Node* head)
{
//这个递归 函数的 出口就是 next =NULL ,返回 每一层的 最后一个NOde的指针
Node* pa = new Node;
pa = head; //和head指向同一个 对象
//1.出口
if(head->next == NULL)
{
if(head->child != NULL)
{
head->next = head->child;
head->child->prev = head;
head=dfs(head->child); //先深搜一次(下一层的连接)然后再 进行连接
return head;
}
else{
return head;
}
}
//2.递归:
//--递归 搜索
while(pa->next !=NULL)
{
if(pa->child != NULL)
{
//需要 再深入 一层递归 , 并且返回 递归结果的头节点指针
//(1)存下 这一层的 next 的指针
Node* next_=pa->next;
//(2)让pa的next 和 pre 和 child进行 互相交织
pa->next = pa->child;
pa->child->prev = pa;
//(3)返回递归 得到的 下一层的最后一个 node的 指针
//然后和 next_进行交织
Node* tmp=dfs(pa->child); //返回 下一层的 最后一个节点的指针
tmp->next = next_;
next_->prev = tmp;
//--更新pa 到next_
pa = next_;
}
else
{
pa= pa->next; //直接更新 pa
}
//pa指向 下一个位置
}
//有一种可能, pa->next == NULlL ,倒是child还有
if(pa->child != NULL)
{
pa->next = pa->child;
pa->child->prev = pa;
pa=dfs(pa->child); //先深搜一次(下一层的连接)然后再 进行连接
}
return pa; //此时pa就是这一层的 最后一个节点
}
//最后一步 , 就是 将所有的child 域全部置空
};
T10:判断链表是否 相交
给你两个单链表的头节点 headA
和 headB
,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null
。
图示两个链表在节点 c1
开始相交:
解:
1.关键:
(1)利用 set 容器 存储 第一条链表中的 所有 节点
(2)第二次 遍历 一次 第二条 链表 判断是否 count 重复 出现的 节点,如果有 就直接返回了
2.代码:
/**
* 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) {
//利用set容器存储 第一条链表的 所有节点,然后遍历一次第二条链表即可
unordered_set Set;
//(1)遍历第一条 链表
ListNode* node1=headA;
while(node1!=NULL)
{
Set.insert(node1);
node1=node1->next;
}
//(2)遍历第二条 链表 + 判断是否在set中出现过
node1 = headB;
while(node1!=NULL)
{
if(Set.count(node1))
{
return node1;
}
node1=node1->next;
}
return NULL;
}
};
T11:又是 对一个链表进行 排序(熟练掌握 归并排序)
给你链表的头结点 head
,请将其按 升序 排列并返回 排序后的链表 。
解:
1.关键:
(1)归并排序 = 递归函数merge_sort + 2个有序链表的“合并”的merge函数
(2)merge_sort函数:递归
<1>出口if(head == NULL || head->next==NULL) (0个 或 1个节点,就是有序的,直接返回)
<2>将传递进来的1个链表head 拆分为 大小几乎相同的 两个链表, 利用快慢指针找到“中间点”(记得将 left链表的 最后一个节点的 next指针置空)
<3>然后 left = merge_sort(head) ,right = merge_sort(start) ,分别递归, 返回的时候 都是 2个有序链表的 头节点
<4>最后 merge(left,right), 2个链表合并为一个有序的 链表 ,最终返回的 就是一个 有序的 链表
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* sortList(ListNode* head) {
//利用 归并 排序 ,对一条链表进行 排序
return merge_sort(head);
}
ListNode* merge_sort(ListNode* head)
{
//特殊
if(head==NULL || head->next == NULL)
{
return head;
}
//不断的 分治 , 先递归 进入到 最深的 只有一个元素的有序情况,然后调用merge
//(1)找到 当前 这一层的 中间点
//利用 快慢指针实现
ListNode* fast= head->next;
ListNode* slow= head;
while(fast!=NULL && fast->next!=NULL)
{
fast= fast->next->next;
slow = slow->next;
}
//此时slow已经指向 “向下取整的中间点”
//(2)分成2个链表进行递归
ListNode* start=slow->next;
slow->next= NULL;
//head 和 start
ListNode* left = merge_sort(head);
ListNode* right = merge_sort(start);
//--返回的 都是 已经排序完成的 头节点
return merge(left,right);
}
ListNode* merge(ListNode* pa,ListNode* pb)
{
//特殊
if(pa == NULL)
{
return pb;
}
if(pb == NULL)
{
return pa;
}
//(1)这个函数 ,传递进来2个 头节点的 指针,都是有序的 ,相当于是合并2个有序
//自己设计一个 头节点,然后 返回头节点 next指针即可
ListNode* pc=new ListNode(0);
ListNode* tmp=pc;
while(pa!=NULL && pb!=NULL)
{
//case1:
if(pa->val < pb->val)
{
pc->next = pa;
pa=pa->next;
pc=pc->next;
}
else
{
pc->next = pb;
pb= pb->next;
pc=pc->next;
}
}
//--收尾
if(pa!=NULL)
{
pc->next = pa;
}
if(pb!=NULL)
{
pc->next = pb;
}
return tmp->next;
}
};
T12:用 链表表示的 2个整数的 求和
给定两个用链表表示的整数,每个节点包含一个数位。
这些数位是反向存放的,也就是个位排在链表首部。
编写函数对这两个整数求和,并用链表形式返回结果。
解:
1.关键:
(1)思路一:先 求出总和 , 然后造一个链表(利用 除10 mod10的思路)
(2)思路二: 借助 全加器的 思想, 利用 串行进位的 while循环方法 , a,b,c得到本位和 然后同时进行链表的 构造 , 不断更新进位, 别忘了 最后一个进位c不为0的时候 ,需要创造一个 节点
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
//第二种方法: 相当于是 写一个 全加器, 然后 循环 串行相加
//特殊情况:
if(l1 == NULL)
{
return l2;
}
if(l2 == NULL)
{
return l1;
}
//--
ListNode* l3=new ListNode(0);
ListNode* head = l3;
int a=l1->val,b=l2->val,c=0,sum=0;
while(l1!=NULL && l2!=NULL)
{
a=l1->val;
b=l2->val;
sum=(a+b+c)%10; //本位和
//--造链表
ListNode* node= new ListNode(sum);
l3->next = node;
l3=l3->next;
c=(a+b+c)/10; //进位和
l1=l1->next;
l2=l2->next;
}
//收尾:
while(l1 != NULL)
{
sum=( c+ l1->val)%10;
c=(c+l1->val)/10;
//--造链表
ListNode* node= new ListNode(sum);
l3->next = node;
l3=l3->next;
c=(c+l1->val)/10;
l1=l1->next;
}
//--
while(l2!=NULL)
{
sum=(c+l2->val)%10;
c=(c+l2->val)/10;
ListNode* node= new ListNode(sum);
l3->next = node;
l3=l3->next;
c=(c+l2->val)/10;
l2=l2->next;
}
//如果 最终的 c的值不为0 ,还需要 造一个节点
if(c!= 0 )
{
ListNode* node= new ListNode(c);
l3->next = node;
l3=l3->next;
}
return head->next;
}
};
T13:分隔链表(以数值x为界限)
给你一个链表的头节点 head
和一个特定值 x
,请你对链表进行分隔,使得所有 小于 x
的节点都出现在 大于或等于 x
的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
解:
1.关键:
(1)利用 2个 标记位置不动的节点head1,head2 ,以及 不断移动的 2个节点p1,p2
(2)遍历一次 “原链表” ,然后 在“对应的 位置” 执行 “节点插入的 基本操作”
(3)最后执行一次 “删除节点的 基本操作”
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
//特殊情况:
if(head == NULL || head->next== NULL)
{
return head;
}
//采用 不断插入的 思想,2个 相对插入位置
ListNode* p1=new ListNode(0);
ListNode* p2=new ListNode(0);
ListNode* head1 = p1;
ListNode* head2 = p2; //head不动, p指针不断移动
//(1)初始链表
p1->next=p2;
//(2)while循环插入
//val < x则 插入到p1 和 head2之间,并且更新p1
//否则 ,连接接到p2之后,并且 更新p2
while(head!=NULL)
{
//保留head->next
ListNode* tmp = head->next;
if(head->val < x)
{
head->next = p1->next;
p1->next = head;
p1 = p1->next;
}
else
{
p2->next = head;
p2= p2->next;
}
head=tmp;
}
p2->next=NULL;
//(3)删除 标志节点:head2 需要删除
p1->next = p1->next->next;
return head1->next;
}
};
T14:分割链表 , 将一个 链表分割为 k个部分
给你一个头结点为 head
的单链表和一个整数 k
,请你设计一个算法将链表分隔为 k
个连续的部分。
每部分的长度应该尽可能的相等:任意两部分的长度差距不能超过 1 。这可能会导致有些部分为 null 。
这 k
个部分应该按照在链表中出现的顺序排列,并且排在前面的部分的长度应该大于或等于排在后面的长度。
返回一个由上述 k
部分组成的数组。
解:
1.关键:
(1)遍历一次 ,得到 链表的 总长度
(2)特殊情况 ,k > n时 需要 返回一个 长度为 k 的vector
(3)一般情况,大概是 一个 数论题目: cnt2 = n/k--表示后面的 子链表长度,cnt1长度的子链表 一共有 n%k =diff 个 ,然后 遍历一次 对链表进行 切割 即可
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
vector splitListToParts(ListNode* head, int k) {
//就是将 一个 链表 分割为k个 链表 ,重点在于 计算链表的长度,和数论有关
vector vec;
//(1)遍历一次 ,得到总长度n
ListNode* tmp=head;
int n=0;
while(tmp !=NULL)
{
n++;
tmp = tmp->next;
}
//特殊 : k > n
//需要 将 原链表全部割开
if(k > n)
{
vector ans(k); //大小为k
int i=0;
while(head !=NULL)
{
ListNode* u= head->next;
head->next = NULL;
ans[i++] = head;
head = u;
}
return ans;
}
//(2)计算 各个数值
int cnt2= n/k;
int diff=n%k;
//需要有 diff 个前面的 链表长度为cnt2+1
int cnt1= cnt2+1;
//(3)再次遍历:
int i=1;
for(i=1 ; i <= diff ;i++)
{
ListNode* new_head=head;
//直接原地 分割好了
for(int j=1;j<=cnt1-1;j++)
{
head = head->next;
}
ListNode* next_head = head->next;
head->next= NULL;
vec.push_back(new_head);
head = next_head;
//此时 head指向的是 长度为cnt1的 子链表的最后一个节点
}
//--继续
for(i=diff+1 ; i <= k ;i++)
{
cout<<"第二部分 来过几次 "<next;
}
ListNode* next_head = NULL;
if(head != NULL)
{
next_head = head->next;
}
else
{
continue;
}
head->next= NULL;
vec.push_back(new_head);
head = next_head;
//此时 head指向的是 长度为cnt1的 子链表的最后一个节点
}
return vec;
//还要 考虑 k > n的情况
}
};
T15:从尾到头 打印 链表 中的 元素
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
解:
1.关键:
(1)等价于 stack栈结构 中的 “后进先出”
(2)不需要 反转 链表 ,只要 通过遍历一次 得到 链表的 总长度 ,然后 反向 存储 数值即可
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
vector reversePrint(ListNode* head) {
//先遍历一次 获取长度n,(不需要反转),然后 从vec的后面开始存放数值
//其实利用一个stack更好, 后进先出
int n=0;
ListNode* tmp= head;
while(tmp!= NULL)
{
n++;
tmp=tmp->next;
}
vector ans(n);
while(head != NULL)
{
ans[--n] = head->val;
head = head->next;
}
return ans;
}
};
T16:链表 表示的 2个整数 相加(高位 在前 version)
给定两个 非空链表 l1
和 l2
来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
可以假设除了数字 0 之外,这两个数字都不会以零开头。
解:
1.关键:
(1)从高位 开始 求和 是做不到的 ,所以 ,先反转 ,然后 回到 从低位开始 串行进位全加器
(2)串行进位 全加器
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
//这一次 是 高位在 链表的 前面,低位在 链表的 后面
//特殊情况:
if(l1 == NULL)
{
return l2;
}
if(l2 == NULL)
{
return l1;
}
//思路一:可以考虑 反转链表之后 按照原来的 方法进行求解
//因为 从高位 到 低位的 串行进位 全加器是 做不到的 ,所以 还是 反转吧
l1=reverse_list(l1);
l2=reverse_list(l2);
//--按照反转之后的计算(串行进位 全加器)
int a=0,b=0,c=0,sum=0;
ListNode* head=new ListNode(0);
ListNode* l3=head;
while(l1!=NULL && l2!=NULL)
{
a=l1->val;
b=l2->val;
sum=a+b+c;
c=sum/10;
ListNode* node=new ListNode(sum%10);
l3->next = node;
l3=l3->next;
l1=l1->next;
l2=l2->next;
}
//收尾
while(l1 !=NULL)
{
sum = l1->val +c;
c=sum/10;
ListNode* node=new ListNode(sum%10);
l3->next = node;
l3=l3->next;
l1=l1->next;
}
while(l2 !=NULL)
{
sum = l2->val +c;
c=sum/10;
ListNode* node=new ListNode(sum%10);
l3->next = node;
l3=l3->next;
l2=l2->next;
}
//--
if(c!=0)
{
ListNode* node=new ListNode(c);
l3->next = node;
l3=l3->next;
}
head = reverse_list(head->next);
return head;
}
//反转链表函数
ListNode* reverse_list(ListNode* list)
{
//特殊 情况:
if(list == NULL || list->next == NULL)
{
return list;
}
//--一般情况:
ListNode* tmp=list->next;
list->next =NULL;
while(tmp!=NULL)
{
ListNode* u= tmp->next;
tmp->next = list;
list = tmp;
tmp = u;
}
return list;
//悟:按 “值返回一个指针” 等价于“返回这个 指针的指向(本质上一个地址而已)”
}
};
T17:判断 链表中 环的 入口节点
给定一个链表,返回链表开始入环的第一个节点。 从链表的头节点开始沿着 next
指针进入环的第一个节点为环的入口节点。如果链表无环,则返回 null
。
为了表示给定链表中的环,我们使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos
是 -1
,则在该链表中没有环。注意,pos
仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
解:
1.关键:
(1)这和 那个 判断“2个链表相交节点很像”, 都是 利用Set 存储第一次 “经过!”该节点,然后利用Set判断是否 有 第一次 重复经过
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
//直接 利用 set进行判断 发生重复的 第一个节点就是入环的 节点
unordered_set Set;
ListNode* tmp = head;
//(1)遍历一次
while(tmp!=NULL)
{
if(Set.count(tmp))
{
return tmp;
}
else
{
Set.insert(tmp);
}
tmp = tmp->next;
}
return NULL; //出来了,说明没有环
}
};
T18:链表随机节点
给你一个单链表,随机选择链表的一个节点,并返回相应的节点值。每个节点 被选中的概率一样 。
实现 Solution
类:
Solution(ListNode head)
使用整数数组初始化对象。int getRandom()
从链表中随机选择一个节点并返回该节点的值。链表中所有节点被选中的概率相等解:
1.关键:
(1)由于 链表结构 不利于 随机访问 , 所以 使用 一个 数组 进行存储
(2)利用rand()%size 进行随机 读取
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
//借助一个 数据数组进行存储
vector vec;
int size;
Solution(ListNode* head) {
//利用一个 链表中的数据 进行初始化 数据数组
//随机访问 直接返回 数组中的元素即可
while(head != NULL)
{
vec.push_back(head->val);
head = head->next;
}
size = vec.size();
}
int getRandom() {
//随机
//srand((unsigned)time(NULL)); //种下随机数种子
int index = rand()%size;
return vec[rand()%size];
}
};
/**
* Your Solution object will be instantiated and called as such:
* Solution* obj = new Solution(head);
* int param_1 = obj->getRandom();
*/
T19:返回链表中 倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6
个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6
。这个链表的倒数第 3
个节点是值为 4
的节点。
解:
1.关键:
(1)利用 快慢指针 让2个指针之间 的 距离 是k-1个节点
(2)当fast到达 最后一个节点时, slow就是 倒数第k个节点
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
//特殊:
if(head == NULL || head->next == NULL)
{
return head;
}
//利用快慢指针 返回 单数第k个节点
ListNode* fast=head;
ListNode* slow=head;
for(int i=1;i<=k-1;i++)
{
fast=fast->next;
}
while(fast->next!=NULL)
{
fast = fast->next;
slow = slow->next;
}
return slow;
}
};
T20:中序遍历 二叉搜索树 构建 双向链表
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的循环双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。
解:
1.关键:(这个思路 是我 经历了很多 失败后, 无奈的 选择,虽然比较笨,但还是比较通用)
(1) 利用递归 写一个中序遍历的 代码
(2)将 “访问该节点” 改为 “将该节点 加入到vec中”
(3)最后对 vec进行连接 即可
2.代码:
class Solution {
public:
vector vec;
Node* treeToDoublyList(Node* root) {
//特殊:
if(root == NULL)
{
return root;
}
if(root->left ==NULL && root->right == NULL)
{
root->left =root;
root->right =root;
return root;
}
//干脆用一个全局vec进行存储,tmd
travel(root);
int size=vec.size();
//(遍历进行连接
vec[0]->left = vec[size-1];
vec[0]->right =vec[1];
vec[size-1]->right = vec[0];
vec[size-1]->left =vec[size-2];
//
for(int i=1;i<=size-2;i++)
{
vec[i]->left = vec[i-1];
vec[i]->right = vec[i+1];
}
return vec[0];
}
//利用 递归 来写 中序遍历
void travel(Node* root) //需要 返回 遍历之后的最后一个节点
{
//(1)递归出口
if(root ==NULL)
{
return ;
}
//(2)递归
travel(root->left);
//此时的root就是 你想要的 次序
vec.push_back(root);
travel(root->right);
}
};
T21:二进制链表 转为 一个十进制整数
给你一个单链表的引用结点 head
。链表中每个结点的值不是 0 就是 1。已知此链表是一个整数数字的二进制表示形式。
请你返回该链表所表示数字的 十进制值 。
解:
1.关键:
(1)一般,都是按照 从低位 到 高位 的次序进行处理的
(2)如果链表先存储高位 ,可以 反转 ,或者 改用一个vector进行存储 ,最后反向取出即可
(3)权值 : power*=2 进行更新
2.代码:
class Solution {
public:
int getDecimalValue(ListNode* head) {
//(1)可以反转 、 也可以先用一个vec存储
vector vec;
while(head!=NULL)
{
vec.push_back(head->val);
head= head->next;
}
//(2)权值和
long long power=1;
long long sum=0;
int size = vec.size();
for(int i=size-1;i >=0 ;i--)
{
sum+= (vec[i] * power);
power *=2;
}
return sum;
}
};
T22: 一颗二叉树 的每一层 节点构成一个链表 返回它们组成的 vector
给定一棵二叉树,设计一个算法,创建含有某一深度上所有节点的链表(比如,若一棵树的深度为 D
,则会创建出 D
个链表)。返回一个包含所有深度的链表的数组
解:
1.关键:
(1)二叉树的 层序遍历:果断 bfs广搜,
(2)这里需要 2层 交替, 所以创新 使用2个队列queue 交替存储下一层 树节点的 同时, 将这一层的 所有节点 全部取出 链接为 一个 新的链表
(3)bfs函数中 : 主要负责 判断2个队列是否都为空的结束情况,并将每次 搜索的返回的链表入vec
(4)update函数中: 主要负责 从其中一个不为空的队列中取出所有节点 构建一条链表, 同时向2个方向搜索的节点 一次加入到 另一个队列中
2.代码:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
vector listOfDepth(TreeNode* tree) {
//采用 一种 不一样的 bfs广搜的 方式,每次都将 queue队列中的 元素全部取出
return bfs(tree);
}
vector bfs(TreeNode* root)
{
//考虑使用 2个队列交替 运作的 方式,每次都 只搜索一层
queue Que1; //不需要map因为 不会重复
queue Que2;
Que1.push(root); //第一层 再Que1中
vector ans;
while((!Que1.empty()) || (!Que2.empty()))
{
//只有当 2 个队列都为空的 时候 才需要退出
ans.push_back(update(Que1,Que2));
}
return ans;
}
ListNode* update(queue& Que1,queue& Que2) //2个搜索方向
{
//传进来的队列 一个为空, 另一个个不为空
//忘记 新的元素加入队列了
//case1:Que2为空
if(!Que1.empty())
{
//好了,这个 队列中的元素 一次作为一条新链表的 元素
//ListNode* head=new ListNode(Que1.front()->val);
TreeNode* head =Que1.front();
ListNode* head_=new ListNode(head->val);
//两个节点不是同一种
Que1.pop();
ListNode* tmp=new ListNode(0);
ListNode* real_head = head_;
while(head_!=NULL)
{
//--先元素新的 子节点入队
if(head->left!=NULL)
{
Que2.push(head->left);
}
if(head->right != NULL)
{
Que2.push(head->right);
}
tmp->next = head_;
tmp = tmp->next;
if(Que1.empty())
{
break;
}
head = Que1.front();
head_ = new ListNode(head->val);
Que1.pop();
}
return real_head;
}
else
{
TreeNode* head=Que2.front();
ListNode* head_=new ListNode(head->val);
Que2.pop();
ListNode* tmp=new ListNode(0);
ListNode* real_head = head_;
while(head_!=NULL)
{
if(head->left!=NULL)
{
Que1.push(head->left);
}
if(head->right != NULL)
{
Que1.push(head->right);
}
tmp->next = head_;
tmp = tmp->next;
if(Que2.empty())
{
break;
}
head = Que2.front();
head_ = new ListNode(head->val);
Que2.pop();
}
return real_head;
}
}
};
T23:反转 部分链表
给你单链表的头指针 head
和两个整数 left
和 right
,其中 left <= right
。请你反转从位置 left
到位置 right
的链表节点,返回 反转后的链表 。
解:
1.关键:
(0)先考虑好 那些 特殊情况,比如left == right,无需反转
(1)找到left节点 的前一个位置的节点作为head1,right的那个节点作为head2
(2)然后 利用 “基本操作”对 head1->next 到 head2之间的 节点进行反转,并且 和head1 和 head2->next进行连接
(3)最后需要分情况 返回 哪一个 才是反转之后的 头节点
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseBetween(ListNode* head, int left, int right) {
//反转第left 到 right之间的 节点
//特殊情况: 如果left==1
if(head == NULL || head->next==NULL || left==right)
{
return head;
}
//(1)先获取到 left的前一个节点 和 right节点
ListNode* head1 = new ListNode(0);
ListNode* head2 = new ListNode(0);
ListNode* start = head;
int k=1;
for(k=1; k <= left-2 ; k++)
{
start = start->next;
}//现在start指向的 就是 left前一个节点
if(left == 1)
{
start = head1;
head1->next = head;
}
head1 =start; //将这个节点给到head1
//cout<<"左节点"<val<next;
}
head2 = start; //现在head2就是 指向right节点
//cout<<"右节点"<val<next;
ListNode* tmp = start->next;
start->next = head2->next; //连上右侧
head2->next = NULL;
while(tmp!=NULL && start!=NULL)
{
ListNode* u =tmp->next;
tmp->next = start;
start= tmp;
tmp= u;
}
//此时start指向尾节点
head1->next = start;
if(left != 1)
{
return head;
}
else
{
return start;
}
}
};
T24:先 删除 部分 链表 ,然后 在对应位置连接 一个 新的 链表
给你两个链表 list1
和 list2
,它们包含的元素分别为 n
个和 m
个。
请你将 list1
中下标从 a
到 b
的全部节点都删除,并将list2
接在被删除节点的位置。
解:
1.关键:
(1) 坑: 这个题中 下标 从0开始计数
(2)其实和T23一样, 重点是 得到 left的前一个节点 head1 , 和 right的那个节点 head2
(3)然后只要 改变对应指针的 指向即可
2.代码:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeInBetween(ListNode* list1, int a, int b, ListNode* list2) {
//删除节点 + 插入节点
//(1)先找到a前面的 那个节点 和 b的那个节点
ListNode* head=new ListNode(0);
head->next = list1;
ListNode* tmp=head;
ListNode* head1= tmp;
a+=1;
b+=1;
for(int k=1; k<= a-1; k++)
{
tmp = tmp->next;
}
head1 = tmp ;//a前面的节点
cout<<"left: "<val<next;
}
ListNode* head2= tmp; //b的那个节点
cout<<"right: "<val<next = list2;
while(list2->next !=NULL)
{
list2 = list2->next; //找到list2的最后一个节点
}
list2->next = head2->next;
//
return head->next;
}
};
T25:删除 利用val区分不同节点的 链表中的给定节点
有一个单链表的 head
,我们想删除它其中的一个节点 node
。
给你一个需要删除的节点 node
。你将 无法访问 第一个节点 head
。
链表的所有值都是 唯一的,并且保证给定的节点 node
不是链表中的最后一个节点。
删除给定的节点。注意,删除节点并不是指从内存中删除它。这里的意思是:
node
前面的所有值顺序相同。node
后面的所有值顺序相同。解:
1.关键:
(1)利用不同 val进行区分 , 暗示 可以修改 节点的val值
(2)一句话:“先把自己变成儿子,然后干掉儿子”
2.代码:
class Solution {
public:
void deleteNode(ListNode* node) {
//妙啊:这题 可以改变节点的 数值,因为每个节点的数值都不同,所以利用val区分
//一句话:“先把自己变成儿子,然后干掉儿子”
//之所以不是 末尾节点,就是保证有儿子
node->val = node->next->val;
node->next=node->next->next;
}
};
T26:删除 链表的 “中间点”
给你一个链表的头节点 head
。删除 链表的 中间节点 ,并返回修改后的链表的头节点 head
。
长度为 n
链表的中间节点是从头数起第 ⌊n / 2⌋
个节点(下标从 0 开始),其中 ⌊x⌋
表示小于或等于 x
的最大整数。
对于 n
= 1
、2
、3
、4
和 5
的情况,中间节点的下标分别是 0
、1
、1
、2
和 2
。
解:
1.关键:
(1)这里的 中间点的 “下标”是从0开始计算的 ,但是 n又是从1开始算的
(2)如果fast的终止条件是 fast!=NULL && fast->next!=NULL 那么
奇数个点: slow指向的正好是“真中间点” 偶数个点: slow指向 “向下取整”的中间点
(3)所以终止条件改为 fast!=NULL && fast->next!=NULL && fast->next->next!=NULL
得到的slow才是 前一个节点(偶数点时)
2.代码:
class Solution {
public:
ListNode* deleteMiddle(ListNode* head) {
//利用 快慢指针 找到“向下取整的 ”中间节点 或者是 “真中间点”
//特殊
if(head == NULL || head->next == NULL)
{
return NULL;
}
//--
ListNode* fast = head->next;
ListNode* slow =head;
while(fast!=NULL && fast->next!=NULL && fast->next->next!=NULL)
{
fast = fast->next->next;
slow = slow->next;
}
slow->next = slow->next->next;
return head;
}
};
T27:最大孪生和
在一个大小为 n
且 n
为 偶数 的链表中,对于 0 <= i <= (n / 2) - 1
的 i
,第 i
个节点(下标从 0 开始)的孪生节点为第 (n-1-i)
个节点 。
n = 4
那么节点 0
是节点 3
的孪生节点,节点 1
是节点 2
的孪生节点。这是长度为 n = 4
的链表中所有的孪生节点。孪生和 定义为一个节点和它孪生节点两者值之和。
给你一个长度为偶数的链表的头节点 head
,请你返回链表的 最大孪生和 。
解:
1.关键:
(1)其实 主要考察 反转 或者 改用 vector 存储 利于 随机访问
2.代码:
class Solution {
public:
int pairSum(ListNode* head) {
//最大孪生和 , 可以反转 ,也可以 先存到vec中
vector vec;
while(head!=NULL)
{
vec.push_back(head->val);
head = head->next;
}
int size= vec.size();
//--打擂台
int half = size/2;
int max=-1;
for(int i=0;i<=half-1;i++)
{
int sum = vec[i] + vec[size-1-i];
if(sum > max)
{
max = sum;
}
}
return max;
}
};
T28:给一个 从高位开始 表示的 链表整数 +1
给定一个用链表表示的非负整数, 然后将这个整数 再加上 1 。
这些数字的存储是这样的:最高位有效的数字位于链表的首位 head
。
解:
1.关键:
(1) 等价于 只有个位 的一个进位c=1
(2)然而,无论如何 都只能 从低位开始向高位 串行进位
(3)所以,翻转+串行进位+翻转
2.代码:
class Solution {
public:
ListNode* plusOne(ListNode* head) {
//和2个链表 表示的 整数的 求和思路一样 , 都是串行进位求和
//(1)翻转
head = reverse(head);
//show(head);
ListNode* new_head =head;
//(2)串行进位求和
int c=1;
ListNode* before_head=head;
while(head!=NULL)
{
int sum = c + head->val;
c=sum/10;
head->val = sum%10;
before_head = head;
head = head->next;
}
//收尾 -- 很可惜 ,这是的head是一个NULL指针
if(c!=0)
{
ListNode* node = new ListNode(c);
before_head->next = node;
}
head = reverse(new_head);
return head;
}
//翻转函数
ListNode* reverse(ListNode* head)
{
ListNode* start = head; //start指向翻转后的 头节点
if(head!=NULL && head->next != NULL)
{
ListNode* tmp =start->next;
start->next =NULL;
while(tmp!=NULL && start!=NULL)
{
ListNode* u = tmp->next;
tmp->next = start;
start= tmp;
tmp= u;
}
}
return start;
}
//测试 函数
void show(ListNode* head)
{
ListNode* tmp = head;
while(tmp!=NULL)
{
cout<val <<" ";
tmp = tmp->next;
}
cout<
T29:交换单链表中 正数 第k个节点 和 倒数 第k个节点
给你链表的头节点 head
和一个整数 k
。
交换 链表正数第 k
个节点和倒数第 k
个节点的值后,返回链表的头节点(链表 从 1 开始索引)。
解:
1.关键:
(1)从head移动k-1次得到 正数第k个节点
(2)利用 相隔k个节点的 fast slow快慢指针 找到 倒数 第k个节点 , 当fast==NULL时终止
(3)值交换即可
2.代码:
class Solution {
public:
ListNode* swapNodes(ListNode* head, int k) {
//交换 链表 正数 和 倒数的 第k个 节点的!值!,好吧,“值交换”
//还不如 分别 取到 第k个节点 和 快慢指针得到的 倒数 第k个节点
ListNode* node1 = head;
for(int i=1;i<=k-1;i++)
{
node1 = node1->next;
}//此时 node1指向的 就是 正数 第k个节点
//(2)
ListNode* fast =head;
ListNode* slow =head; //fast==NULL时,如果2者相隔k个节点
for(int i=1;i<=k;i++)
{
fast = fast->next;
}
while(fast!=NULL)
{
fast = fast->next;
slow = slow->next;
}
//交换
swap(slow->val , node1->val);
return head;
}
};
T30:限定insert排序,对链表进行 insert排序
给定单个链表的头 head
,使用 插入排序 对链表进行排序,并返回 排序后链表的头 。
插入排序 算法的步骤:
下面是插入排序算法的一个图形示例。部分排序的列表(黑色)最初只包含列表中的第一个元素。每次迭代时,从输入数据中删除一个元素(红色),并就地插入已排序的列表中。
对链表进行插入排序。
解:
1.关键:
(1)设置 一个 tail节点 每次 都指向 已经排序好的 最后一个节点
(2)设置一个 u节点 ,指向 下一个 需要排序的节点
(3)内层 循环 判断 node1 的val是否在 xx和xx之间,或者放到 末尾 并且 更新tail指针
2.代码:
class Solution {
public:
ListNode* insertionSortList(ListNode* head) {
//训练 插入排序的熟练程度
//特殊:
if(head == NULL || head->next ==NULL)
{
return head;
}
//我需要 一个 tail 节点 -- 已经排序完成的 最后一个节点
//只有当 node1 放到 tail的后面时 才需要 更新tail
ListNode* tail =head;
//--一般:
ListNode* node1 = head->next;
while(node1 !=NULL)
{
//特殊:
ListNode* u = node1->next; //作为 最后 node1的更新节点
tail->next = u ;//将u连接到 tail后面
if(node1->val < head->val)
{
node1->next = head;
head = node1;
node1 = u;
continue;
}
ListNode* tmp = head;
//内层循环
int flag = 0;
while(tmp!= tail) //终点为 tail节点
{
if( node1->val >= tmp->val && node1->val <=tmp->next->val)
{
//可以放到它两之间了
node1->next = tmp->next;
tmp->next = node1;
node1 = u;
flag =1;
break;
}
else
{
tmp = tmp->next;
}
}
if(flag == 1)
{
continue;
}
else{
//需要更新tial节点,此时 tmp指向 tail
tmp->next = node1;
tail = node1;
node1 = u;
}
node1 = u;
}
return head;
}
};
T31: 逆序打印 节点不可变链表
给您一个不可变的链表,使用下列接口逆序打印每个节点的值:
ImmutableListNode
: 描述不可变链表的接口,链表的头节点已给出。您需要使用以下函数来访问此链表(您 不能 直接访问 ImmutableListNode
):
ImmutableListNode.printValue()
:打印当前节点的值。ImmutableListNode.getNext()
:返回下一个节点。输入只用来内部初始化链表。您不可以通过修改链表解决问题。也就是说,您只能通过上述 API 来操作链表。
解:
1.关键:
(1)其实 , 逆序 本质就是 “后进先出”的栈 结构
(2)这里有很多方法,比如先用vec存储再逆序,或者stack存储
(3)这里我 采用 递归 进行逆序输出(递归的 本质就是 stack 栈结构)
2.代码:
class Solution {
public:
void printLinkedListInReverse(ImmutableListNode* head) {
//递归打印,相当于 一个栈结构
//(1)出口
if(head == NULL)
{
return ;
}
//递归
printLinkedListInReverse(head->getNext());
head->printValue();
}
};
T32:判断 二叉树 中是否存在 一条 链表
给你一棵以 root
为根的二叉树和一个 head
为第一个节点的链表。
如果在二叉树中,存在一条一直向下的路径,且每个点的数值恰好一一对应以 head
为首的链表中每个节点的值,那么请你返回 True
,否则返回 False
。
一直向下的路径的意思是:从树中某个节点开始,一直连续向下的路径。
解:
1.关键:
(1)利用 递归的 写法travel(这里是 先序 ,中序和后序都可以)一遍二叉树中的节点,判断是否可以作为 链表的 第一个节点
(2)如果可以 ,就对 这个节点进行 dfs 深搜 , 观察是否 可以在 最深层返回 true, 或者返回false
2.代码
class Solution {
public:
bool Flag = false;
bool isSubPath(ListNode* head, TreeNode* root) {
//二叉树 中是否存在 一条链表值(和搜索 相关)
//先序 遍历的 同时 ,把 “访问 ” 换成 “dfs搜索即可”
travel(head,root);
return Flag;
}
void travel(ListNode* head,TreeNode* root)
{
//(1)出口
if(root == NULL)
{
return ;
}
//(dfs)--只有 root->val == head->val才需要
if(root->val == head->val)
{
if(dfs(root,head))
{
Flag = true;
return ;
}
}
//--递归
travel(head,root->left);
travel(head,root->right);
}
bool dfs(TreeNode* root ,ListNode* head)
{
bool flag=true;
//不要更改head
ListNode* tmp = head;
//(1)出口:
if(head == NULL)
{
//true
return true;
}
else if(head!=NULL && root==NULL)
{
return false;
}
//case1: root->val 和 head->val 不匹配
if(root->val != head->val)
{
//无需再搜索了
flag = false;
}
else
{
//继续 递归
if(dfs(root->left,tmp->next))
{
return true;
}
else if(dfs(root->right,tmp->next))
{
return true;
}
else
{
return false;
}
}
return flag;
}
};
T33:二叉树 展开为 单链表
给你二叉树的根结点 root
,请你将它展开为一个单链表:
TreeNode
,其中 right
子指针指向链表中下一个结点,而左子指针始终为 null
。解:
1.关键:
(1)递归形式的 二叉树 先序遍历, 将“访问” 改为 “链接” 即可
(2)
//核心:任何一个节点 只有在经过之后 left和right指针才会被修改,所以
//只要在 第一个经过的 时候 留下 原来的 left 和 right指针即可
2.代码:
class Solution {
public:
TreeNode* pc;
void flatten(TreeNode* root) {
//法一:利用O(n)空间,将先序遍历中的访问 改为连接即可
pc=new TreeNode(0);
//TreeNode* new_head = pc;
traval(root);
//他不是看变量root , 而是看 “那一块内存空间”
}
void traval(TreeNode* root)
{
//(1)递归出口
if(root == NULL)
{
return ;
}
//--先序 连接
//核心:任何一个节点 只有在经过之后 left和right指针才会被修改,所以
//只要在 第一个经过的 时候 留下 原来的 left 和 right指针即可
TreeNode* left_node = root->left;
TreeNode* right_node =root->right;
pc->right = root;
pc->left = NULL;
pc = pc->right;
//记得将root->right 和 root->left保留下来
traval(left_node);
traval(right_node);
}
};
T34:k个节点一组 翻转 一条链表
给你链表的头节点 head
,每 k
个节点一组进行翻转,请你返回修改后的链表。
k
是一个正整数,它的值小于或等于链表的长度。如果节点总数不是 k
的整数倍,那么请将最后剩余的节点保持原有顺序。
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
解:
1.关键:
(1)朴素就好了, 基础就是 那个题(一条链表的 部分节点的 翻转)
(2)第一次特殊处理,得到新的 new_head头节点作为返回值
(3)利用“死循环” 对后续的 节点进行 “相同的处理”, 重点是2中退出的case,case1是pc->next == NULL,说明上一次 已经是 k|n的最后一组了,可以直接返回new_head
case2:如果head2到达了一个NULL,说明 不够了,可以直接返回new_head
2.代码:
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
//特殊:
if(head == NULL || head->next == NULL || k==1)
{
return head;
}
//k个一组 进行 链表节点的 翻转
//没有更好的策略,只能依次翻转
//设置一个头节点
ListNode* pc=new ListNode(0);
pc->next = head;
//1.第一次翻转,找到left的前一个节点 和 right节点
//先以 前k个节点为例
ListNode* head1=pc; //指向left前一个
ListNode* head2=head; //指向right
for(int i=1;i<=k-1;i++)
{
if(head2 == NULL)
{
return head; //原来节点的数量数少于k个
}
head2 = head2->next;
}
if(head2 == NULL)
{
return head;
}
//现在 head2 就是right位置的 节点
//(1)先 让head1->next保留下来 然后更新为 head2-
ListNode* start = head1->next;
ListNode* static_start=start;
head1->next = head2;
//--翻转 , 最后 让这个 翻转后的 最后start->next 更新为head2->next
ListNode* tmp=start->next;
start->next = head2->next;
head2->next = NULL ; //记得置空!--用于tmp判断翻转的终点
while(tmp!=NULL && start!=NULL)
{
//cout<<"start:"<val<next;
tmp->next = start;
start = tmp;
tmp = u;
}
//完成了 依次翻转, 头节点为head1->next
//几个重要的 指针:
ListNode* new_head = head1->next; //可以返回的 “真”头节点
//下一次开始的left节点的 前一个节点
pc = static_start;//开始写之后的 循环
//设计好 循环的 终点 --利用2中情况的break作为循环的退出
//show(new_head); //结果显示 第一次交换没问题
//cout<< "pc:"<val<next如果是NULL,即为case1 ,k|n,正好分组
if(pc->next == NULL)
{
break; //正好分组
}
//找到head1 和 head2,如果没有找到head2,就是case2,退出循环
head1 = pc;
head2 = pc->next;
for(int i=1;i<=k-1;i++)
{
if(head2 == NULL)
{
return new_head;
}
head2 = head2->next;
}
if(head2 == NULL)
{
return new_head;
}
//找到了 head1 和 head2
ListNode* start = head1->next;
ListNode* static_start=start;
//pc->next!=NULL,所以start一定不是NULL
head1->next = head2;
ListNode* tmp=start->next;
start->next = head2->next; //所以它的意思是head2可能为NULL
head2->next = NULL ; //记得置空!--用于tmp判断翻转的终点
while(tmp!=NULL && start!=NULL)
{
ListNode* u=tmp->next;
tmp->next = start;
start = tmp;
tmp = u;
}
pc = static_start;
}
return new_head;
}
//这个错误 是 第1个节点 一直和后面的节点在交换
//--辅助 测试函数
void show(ListNode* head)
{
ListNode* tmp =head;
while(tmp!=NULL)
{
cout<val<<" ";
tmp = tmp->next;
}
cout<
T35:按照 一定严格递减规则 删除 节点
给你一个链表的头节点 head
。
对于列表中的每个节点 node
,如果其右侧存在一个具有 严格更大 值的节点,则移除 node
。
返回修改后链表的头节点 head
。
解:
1.关键:
(1)思路: 翻转 + 快慢指针遍历,打擂台删除节点 + 翻转
(2)学会转化,因为 前面看不到 后面有啥, 不如先翻转,逆向思维进行考虑
2.代码:
class Solution {
public:
ListNode* removeNodes(ListNode* head) {
//特殊:
if(head == NULL || head->next== NULL)
{
return head;
}
//先翻转, 然后设置一个max打擂台,如果 后续节点有比max严格小的节点
//就利用快慢(相隔为1)指针删除该节点
//最后再翻转 依次,并且返回 翻转后的头节点
//(1)第一次翻转
head = reverse(head);
//(2)遍历依次 打擂台 + 快慢指针删除节点
ListNode* fast = head->next;
ListNode* slow = head;
int max = slow->val;
while(fast!=NULL)
{
if(fast->val < max)
{
//删除fast节点
fast=fast->next;
slow->next = slow->next->next;
continue;
}
else
{
//是否需要更新max
if(fast->val > max)
{
max = fast->val;
}
fast = fast->next;
slow = slow->next;
}
}//此时fast已经指向NULL
//(3)再次翻转
head = reverse(head);
return head;
}
//--辅助翻转函数 , 返回翻转之后的 头节点
ListNode* reverse(ListNode* head)
{
//特殊:
if(head ==NULL || head->next == NULL)
{
return head;
}
//--一般
ListNode* start = head;
ListNode* tmp = start->next;
start->next =NULL;
while(tmp!=NULL && start!=NULL)
{
ListNode* u = tmp->next;
tmp->next = start;
start = tmp;
tmp = u;
}
return start;
}
};
T36:对按照 绝对值升序 排序的 链表 进行按照 值排序
给你一个链表的头结点 head
,这个链表是根据结点的绝对值进行升序排序, 返回重新根据节点的值进行升序排序的链表。
解:
1.关键:
(1)为了 方便 访问, 先改用 一个 vec1存储 所有的 数值
(2)“按照绝对值 排序”有一些 特点和规律, 本质 就是 把 负数 和 非负数 分开来观察:
可见,负数其实 后面的 更小, 非负数 前面的更小
(3)分成2次 遍历vec1 分别 取出 负数 和 非负数 ,然后 按照 从小到大的 次序存储到 vec2中,最后遍历依次链表 并且修改链表的 数值即可
2.代码
class Solution {
public:
ListNode* sortLinkedList(ListNode* head) {
//特殊:
if(head == NULL || head->next ==NULL)
{
return head;
}
//(1)先遍历依次 将数值存储到vec1中
//(2)然后 从后 向前 只取负数 存储到vec2中
//(3)然后 从前 往后 遍历vec1 取出>=0 的数字 存储到vec2中
//(4)最后 按照 vec2中的数值 更新链表的 数值
vector vec1;
//(1)
ListNode* tmp = head;
while(tmp !=NULL)
{
vec1.push_back(tmp->val);
tmp=tmp->next;
}
//(2)
int size= vec1.size();
vector vec2;
for(int i=size-1;i>=0;i--)
{
if(vec1[i] < 0)
{
vec2.push_back(vec1[i]);
}
}
//(3)
for(int i=0 ;i <=size-1;i++)
{
if(vec1[i] >= 0)
{
vec2.push_back(vec1[i]);
}
}
//(4)
tmp = head;
for(int i=0;i<=size-1;i++)
{
tmp->val = vec2[i];
tmp= tmp->next;
}
return head;
}
};
T37:删除 排序链表 中的 重复 节点
给定一个已排序的链表的头 head
, 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
解:
1.关键:
(1)利用 相隔 为1的 快慢指针进行判断
(2)如果fast->val == slow->val ,就删除这个fast节点
2.代码:
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
//利用 相隔为1的 快慢指针进行删除
//特殊:
if(head == NULL || head->next == NULL)
{
return head;
}
//--一般:
ListNode* fast = head->next;
ListNode* slow = head;
while(fast!=NULL)
{
if(fast->val == slow->val)
{
//删除fast节点
fast = fast->next;
slow->next = slow->next->next;
continue;
}
else
{
fast = fast->next;
slow = slow->next;
}
}
return head;
}
};
T38:多项式 链表求和
多项式链表是一种特殊形式的链表,每个节点表示多项式的一项。
每个节点有三个属性:
coefficient
:该项的系数。项 9x4
的系数是 9
。power
:该项的指数。项 9x4
的指数是 4
。next
:指向下一个节点的指针(引用),如果当前节点为链表的最后一个节点则为 null
解:
1.关键:(这是 一道上课 ppt的题目)
(1)构建一个新的 指针 p3用来 指向已经链接好的 最后一个节点
(2) p1 和 p2 指针 分别指向 list1 和 list2 遍历的 最新的 那个节点
(3)遍历终止条件 就是 p1==NULL or p2 == NULL ,
(4)每次 都是 比较2者的 系数 然后 分情况讨论
2.代码:
class Solution {
public:
PolyNode* addPoly(PolyNode* poly1, PolyNode* poly2) {
//上课的最后一题
//它是用的 原地 串联法
//分情况:case1,p1->power 更大 ,case2:power相等, case3:p2的power更大
//其中case2中 还要分情况考虑 coef之和为0与否
PolyNode* p1=poly1;
PolyNode* p2=poly2;
PolyNode* p3=new PolyNode(0,0);
PolyNode* new_head=p3;
while(p1!=NULL && p2!=NULL)
{
//(1)比较power
if(p1->power > p2->power)
{
p3->next = p1;
p1= p1->next;
p3=p3->next; //p3始终指向已经链接好的 最后一个节点
}
else if(p1->power < p2->power)
{
p3->next = p2;
p2 = p2->next;
p3 = p3->next;
}
else{
//power相等
int coef_sum = p1->coefficient + p2->coefficient;
//case1:
if(coef_sum != 0)
{
p1->coefficient = coef_sum;
p3->next = p1;
p1 = p1->next;
p3 = p3->next;
p2 = p2->next;
}
else{
//这2个节点都不要了
p2 = p2->next;
p1 = p1->next;
}
}
}
//收尾:
if(p1 != NULL)
{
p3->next = p1;
}
else if(p2 !=NULL)
{
p3->next = p2;
}
else{
p3->next = NULL;
}
return new_head->next;
}
};
T39:"两两交换"一条链表中 相邻 节点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
解:
1.关键:
(1)一种 链表结构 的 重要意识,设置好一个 永远指向 已经交换好的 部分的 最后一个节点指针pc
(2)然后就是以head == NULL or head->next == NULL 为结束标志的 遍历,注意next指针修改的 次序
2.代码:
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
//特殊:
if(head == NULL || head->next == NULL)
{
return head;
}
//交换 相邻的 节点
//(1)设置一个 交换好的部分的 最后一个节点的 指针pc
ListNode* pc = new ListNode(0);
ListNode* new_head = pc;
ListNode* tmp = head; //tmp 永远记录下一次开始的 第一个节点指针
//(2)
//cout<<"来过吗"<next!=NULL)
{
tmp = head->next->next; //保留 head->next->next
pc->next = head->next; //pc先链接到head->next
pc = pc->next; //pc到达next
pc->next = head; //pc再链接到head
pc = pc->next; //pc到next
head = tmp; //新的开始点给到head
}
if(head == NULL)
{
pc->next = NULL;
}
else
{
pc->next = head;
head->next = NULL;
}
return new_head->next;
}
//--辅助函数
void show(ListNode* head)
{
ListNode* tmp = head;
while( tmp !=NULL)
{
cout<val<<" ";
tmp = tmp->next;
}
cout<
T40:只保留 链表中 没有 重复的 数字
给定一个已排序的链表的头 head
, 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。
解:
1.关键:
(1)借鉴之前的 删除重复节点的思想 , 这里采用创新的 “快 中 慢 ”三个指针的思想进行遍历删除
(2)slow--指向 已经处理好的 部分的 最后一个节点
mid--指向 下一次需要 比较的 第一个节点
fast--指向mid之后的 节点 ,它的 值需要和mid节点的 值进行比价
2.代码:
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
//特殊:
if(head == NULL || head->next == NULL)
{
return head;
}
//这一次 需要删除所有 只要出现重复的 数字
//我建议 使用 fast mid slow 三指针"快中满"
//slow -- 永远指向 已经处理好的部分的 最后一个节点
//mid -- 指向 下一个部分的 第一个节点 即slow->next
//fast -- 一路高歌
ListNode* pc = new ListNode(0);
ListNode* slow = pc;
pc->next = head;
ListNode* mid = pc->next;
ListNode* fast = mid->next;
//遍历
while(fast!=NULL)
{
//存下这一次的 mid的val
int compare_val = mid->val;
if(fast->val != compare_val)
{
//不相等:
slow = mid;
mid = fast;
fast =fast->next;
continue;
}
while(fast!=NULL && fast->val == compare_val)
{
fast = fast->next;
}//出来的时候,fast要么为一个新的val,要么就是NULL
slow->next = fast;
mid = fast;
if(fast == NULL)
{
break;
}
fast = mid->next;
}
return pc->next;
}
};
T41:链表中 下一个 更大的 节点
给定一个长度为 n
的链表 head
对于列表中的每个节点,查找下一个 更大节点 的值。也就是说,对于每个节点,找到它旁边的第一个节点的值,这个节点的值 严格大于 它的值。
返回一个整数数组 answer
,其中 answer[i]
是第 i
个节点( 从1开始 )的下一个更大的节点的值。如果第 i
个节点没有下一个更大的节点,设置 answer[i] = 0
。
解:
法一: 朴素 暴力 搜索法
1.关键:模拟题目要求 , 对于当前的 这个节点 head 依次向后去找 比它大 的节点
2.代码:
class Solution {
public:
vector nextLargerNodes(ListNode* head) {
//特殊 :
if(head->next == NULL )
{
return {0};
}
//法一: 最朴素的 方法 , 利用 双层 循环进行 暴力搜索
ListNode* tmp = head;
vector ans;
while(head->next!=NULL)
{
tmp = head->next;
int flag = 0;
int num_head = head->val;
while(tmp!=NULL)
{
int num_tmp = tmp->val;
if(num_tmp > num_head)
{
ans.push_back(num_tmp);
flag = 1;
break;
}
tmp= tmp->next;
}
if(flag == 0)
{
ans.push_back(0);
}
head = head->next;
}
ans.push_back(0);
return ans;
}
};
法二:借鉴 讨论区的 思路 -- “单调栈!”(有点 贪婪思想的味道)
1.关键
(1) 遍历一次 ,将链表中的元素 存储到 vec中,方便访问
(2)从后向前遍历 , 利用一个stack栈作为 工具,分情况讨论
case1: 如果 stack为空, 说明ans中该位置只能填0
case2:依次检索 栈顶top元素,如果比tmp_val大,就说明是“它”,填入ans,同时把tmp_val入栈
case3:如果"不比" tmp_val大, 记得出栈
2.代码:
class Solution {
public:
vector nextLargerNodes(ListNode* head) {
//借助贪婪算法的 思想 设计一个 “单调栈结构”
stack Stack;
//(1)先 遍历一次 , 然后 从后 往前 进行 搜素
vector vec;
while(head!=NULL)
{
vec.push_back( head->val);
head = head->next;
}
int size = vec.size();
vector ans(size,0);
//(2)然后 从后 往前进行搜索
//分析:先从 栈顶开始遍历,如果 找比当前位置更大的 值, ans中对应位置填它
//如果“<=”当前位置的 值, 就出栈
//如果栈为空, 对应ans中 当前位置 填0 ,然后 将这个val入栈,继续遍历下一个位置
for(int i =size-1 ;i >=0 ;i--)
{
int tmp_val = vec[i];
int flag = 0; //flag == 0,代表栈 已空
while(!Stack.empty())
{
//栈非空,一次检查栈顶
int top = Stack.top();
if(top > tmp_val)
{
ans[i] = top;
//nice , 记得入栈
Stack.push(tmp_val);
flag= 1;
break;
}
else
{
//出栈
Stack.pop();
}
}
if(flag == 0)
{
//入栈,并且 ans中对应位置填0
Stack.push(tmp_val);
ans[i] = 0;
}
}
return ans;
}
};
T42:找出 极值点 之间的 最小 和 最大距离
链表中的 临界点 定义为一个 局部极大值点 或 局部极小值点 。
如果当前节点的值 严格大于 前一个节点和后一个节点,那么这个节点就是一个 局部极大值点 。
如果当前节点的值 严格小于 前一个节点和后一个节点,那么这个节点就是一个 局部极小值点 。
注意:节点只有在同时存在前一个节点和后一个节点的情况下,才能成为一个 局部极大值点 / 极小值点 。
给你一个链表 head
,返回一个长度为 2 的数组 [minDistance, maxDistance]
,其中 minDistance
是任意两个不同临界点之间的最小距离,maxDistance
是任意两个不同临界点之间的最大距离。如果临界点少于两个,则返回 [-1,-1]
。
解:(比较朴素的 方法 ,以后 有更好的 想法 再加以补充)
1.关键:
(1)转存到 vec中
(2)然后 遍历一次 ,找到 那些 符合 极值点的 下标 存到ans数组中
(3)最后遍历一次ans数组 找到 最小距离min
2.代码:
class Solution {
public:
vector nodesBetweenCriticalPoints(ListNode* head) {
//特殊:
if(head == NULL || head->next ==NULL)
{
return {-1,-1};
}
//返回2个临界节点之间的 最小距离 和 最大距离
//(1)先转存到vec中
vector vec;
while(head!=NULL)
{
vec.push_back(head->val);
head = head->next;
}
//(2)遍历一次,如果是临界点,记录将下标 丢到一个ans的数组中
vector ans;
int size= vec.size();
for(int i=1;i<=size-2;i++)
{
if((vec[i] > vec[i-1] && vec[i] > vec[i+1])||(vec[i] < vec[i-1] && vec[i] < vec[i+1]))
{
//是第i个点 是临界点
ans.push_back(i);
}
}
//(3)检查ans数组:
int size2= ans.size();
if(size2 < 2 )
{
return {-1,-1};
}
//max容易找但是min似乎需要遍历
int max = ans[size2-1] - ans[0];
int min=max;
//打擂台
for(int i=1;i<=size2-1;i++)
{
if(ans[i] - ans[i-1] < min)
{
min = ans[i] - ans[ i-1];
}
}
return {min,max};
}
};
T43:对一条 链表进行分组 ,然后 翻转其中偶数节点的 组
给你一个链表的头节点 head
。
链表中的节点 按顺序 划分成若干 非空 组,这些非空组的长度构成一个自然数序列(1, 2, 3, 4, ...
)。一个组的 长度 就是组中分配到的节点数目。换句话说:
1
分配给第一组2
和 3
分配给第二组4
、5
和 6
分配给第三组,以此类推注意,最后一组的长度可能小于或者等于 1 + 倒数第二组的长度
。
反转 每个 偶数 长度组中的节点,并返回修改后链表的头节点 head
解:(借助vec数组 进行 偷懒!!哎,没办法 ,现在的我 还是 太菜了)
1.关键:
(1)将链表 转存到 vec中
(2)这个题目 考察的 是数学中的 “高斯求和公式 ”n*(n+1)/2的一些处理
(3)对 那些 分组为 偶数的 部分 找到 起点 和 终点 后 进行 对撞指针的 翻转 操作
(4)最后 将 vec中的数组 再 存储回到 链表中即可
2.代码:
class Solution {
public:
ListNode* reverseEvenLengthGroups(ListNode* head) {
//特殊 :
if(head == NULL || head->next == NULL)
{
return head;
}
//虽然可以 直接按照 翻转链表的 思想进行处理,
//但是 需要结合长度的 判断, 感觉比较麻烦 -- 直接 考虑转存到 vec中
//最后 再根据vec中的数值 构造一条 新的链 / 或者 修改原链表的val
//(1)转存
vector vec;
ListNode* tmp = head;
while(tmp!=NULL)
{
vec.push_back(tmp->val);
tmp = tmp->next;
}
//(2) 根据题目要求 对 vec进行 变换
//分组 然后 翻转 偶数节点数的部分 -- 直接利用对撞指针 就可以 修改vec数组
int size= vec.size();
//其实 就是 高斯求和n* (n+1)/2 ,n=(sqrt(8*k+1)-1)/2向下取整,然后就是剩余的最后一组的 元素的 个数
int n = (sqrt(8*size+1)-1)/2;
int remain = size - (n*n + n)/2;
//每一个偶数部分的 起点 和 终点 都可以 利用数学公式进行计算
//最后 对 remain进行判断即可
//起点 (i-1)*i/2 终点 i*(i+1)/2-1
for(int i=2;i<=n;i+=2)
{
int start = (i-1)*i/2;
int end = i*(i+1)/2-1;
//翻转 从start 到 end 下标 之间的 数值
while(end > start)
{
swap(vec[start],vec[end]);
start++;
end--;
}
}
//这个for循环中 有问题 , 应该是 起点 和 终点 找错了
if(remain % 2 ==0)
{
int end = size-1;
int start = size-remain;
while(end > start)
{
swap(vec[start],vec[end]);
start++;
end--;
}
}
//修改原来链表中的所有数值val
tmp = head;
for(int i=0;ival = vec[i];
tmp = tmp->next;
}
return head;
}
};
T44: 删除 重复 出现的节点(无需连续)
编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。
解:
1.关键:
(1)Set容器 + 快慢指针即可
2.代码:
class Solution {
public:
ListNode* removeDuplicateNodes(ListNode* head) {
//tmd看错题了,原来 不需要连续,只要重复出现立刻删除这个节点
//利用set + 快慢指针即可
//特殊:
if(head == NULL|| head->next ==NULL)
{
return head;
}
//
unordered_set Set;
//(1)遍历 + 快慢指针
ListNode* fast = head->next;
ListNode* slow = head;
Set.insert(slow->val);
while(fast!=NULL)
{
//可能需要删除fast这个节点
if(Set.count(fast->val))
{
slow->next = fast->next;
fast = fast->next; // 删除 节点 fast
}
else
{
Set.insert(fast->val);
slow = slow->next;
fast = fast->next;
}
}
return head;
}
};
T45.利用 一个链表的数值 对一个矩阵 进行 螺旋填充
给你两个整数:m
和 n
,表示矩阵的维数。
另给你一个整数链表的头节点 head
。
请你生成一个大小为 m x n
的螺旋矩阵,矩阵包含链表中的所有整数。链表中的整数从矩阵 左上角 开始、顺时针 按 螺旋 顺序填充。如果还存在剩余的空格,则用 -1
填充。
返回生成的矩阵。
解:
1.关键:
(1)设置好 up down left right 4个边界 ,并且在循环中 不断更新,注意 x的方向(反直觉),其中down从1开始!
(2)
//内层:;以 下标 是否到达边界位置为准<=
//外层: 以head访问的 位置是否 为NULL为标准
(3)分4个方向按照一定的顺序进行 填充,特别,一个方向完成后,记得 更新边界值 + 跳到下一次开始的位置
2.代码:
class Solution {
public:
vector> spiralMatrix(int m, int n, ListNode* head) {
//用一个 链表的数值 对 一个 矩阵 进行螺旋填充
//总的来说就是 利用 一个 路径的 设计 然后 遍历一个 链表
//我觉得可以 设计up down left right 4个 二维数组访问边界,然后
int up=m-1 , down = 1 , left = 0 ,right =n-1;//这些边界 都是 从0开始的下标边界
//每次按照一定的 方向对 二维数组进行填充
//填充方向:右 - 下 - 左 - 上
//(1)利用-1对二维数组进行初始化
vector> vec(m,vector(n,-1)); //m行 n 列
//利用2层 while循环:
//内层:;以 下标 是否到达边界位置为准<=
//外层: 以head访问的 位置是否 为NULL为标准
int x=0,y=0; //x行 y 列
while( head!=NULL)
{
//记得 每次需要 更新4个边界的 数值
//(1) y++ ,while循环 , 向右填充 , 右边界right
while(head!=NULL && y<=right)
{
int val = head->val;
head = head->next;
vec[x][y] = val;
y++;
}
x++;
y--; //防止越界
right--;//更新边界
//(2)x++ ,while循环 ,向高的行数填充, 边界up
while(head!=NULL && x<=up)
{
int val = head->val;
head = head->next;
vec[x][y] =val;
x++;
}
y--;
x--;
up--; //更新 up行数边界
//(3)y--,while循环, 向左 填充,边界left
while(head!=NULL && y>=left)
{
int val = head->val;
head = head->next;
vec[x][y] = val;
y--;
}
x--;
y++;
left++; //更新 左边界
//(4)x--, while循环 ,向 低 行数 填充, 边界 down
while(head!=NULL && x>=down)
{
int val = head->val;
head = head->next;
vec[x][y] = val;
x--;
}
y++;
x++;
down++;
}
return vec;
}
};
T46: 将 二叉树 通过中序遍历 得到 一个链表
二叉树数据结构TreeNode
可用来表示单向链表(其中left
置空,right
为下一个链表节点)。实现一个方法,把二叉搜索树转换为单向链表,要求依然符合二叉搜索树的性质,转换操作应是原址的,也就是在原始的二叉搜索树上直接修改。
返回转换后的单向链表的头节点。
1.关键:
(1)还是 调用 递归形式的 中序遍历
(2)将 “访问” 修改为: !逻辑: 当代码执行到 这一步时 , 说明 root节点的 左节点 已经访问过了,但是右节点还没有 访问过 ,先将 root 链接到 全局链表上, 然后 再去访问root的right节点
2.代码
class Solution {
public:
TreeNode* f;
TreeNode* convertBiNode(TreeNode* root) {
f= new TreeNode(0);
TreeNode* pre= f;
dfs(root);
return pre->right;
}
void dfs(TreeNode* root)
{
if(root == NULL)
return ;
dfs(root->left);
f->right = root;
root->left = NULL;
f = f->right;
dfs(root->right);
}
};
T47:合并val为0之间的节点, 并且保证合并之后的链表中没有val为0的节点
给你一个链表的头节点 head
,该链表包含由 0
分隔开的一连串整数。链表的 开端 和 末尾 的节点都满足 Node.val == 0
。
对于每两个相邻的 0
,请你将它们之间的所有节点合并成一个节点,其值是所有已合并节点的值之和。然后将所有 0
移除,修改后的链表不应该含有任何 0
。
返回修改后链表的头节点 head
。
解:
1.关键:
(1)利用 “快慢指针” 加以实现
(2)slow指针 , 每次指向 “开始0”的 前一个节点 ,然后 再 让fast指针向前探索
2.代码:
class Solution {
public:
ListNode* mergeNodes(ListNode* head) {
//保证 开头 和 结尾的2个节点的val都为0
//将 val=0 之间的节点的数值求和,然后 作为一个节点
//利用 “快慢指针”:slow慢指针 指向“开始0”的前一个节点,fast快指针不断向前
ListNode* new_head = new ListNode(0);
new_head->next = head;
ListNode* slow = new_head;
ListNode* fast = head;
while(slow!=NULL && slow->next!=NULL) //先以 slow!=NULL作为结束条件
{
//(1)当slow->next->val == 0的时候 ,就可以开始合并了
if(slow->next->val == 0) // !!注意,slow->next??可能为NULL
{
fast = slow->next->next;
//--开始移动fast--不对,应该要fast 最后的位置处于0
int sum= 0;
while(fast!=NULL && fast->val!=0)
{
sum += fast->val;
fast = fast->next;
}
if(sum == 0) //无论fast == NULL or fast->val == 0都可以
{
//
slow->next = fast; //跳过中间的一些值
}
else //无论 fast==NULL 或者fast->val == 0都可以
{
//创建一个新的节点连接到slow 和 fast之间
ListNode* new_node= new ListNode(sum);
slow->next = new_node;
new_node->next = fast;
}
}
//这里只要移动slow指针即可
slow = slow->next;
}
return new_head->next;
}
};
T48: 判断一个 给定的 字符串string是否为 一个小数或者一个整数(似乎和 链表没关系)
请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。
数值(按顺序)可以分成以下几个部分:
'e'
或 'E'
,后面跟着一个 整数小数(按顺序)可以分成以下几个部分:
'+'
或 '-'
)'.'
'.'
,后面再跟着至少一位数字'.'
,后面跟着至少一位数字整数(按顺序)可以分成以下几个部分:
'+'
或 '-'
)部分数值列举如下:
["+100", "5e2", "-123", "3.1416", "-1E-16", "0123"]
部分非数值列举如下:
["12e", "1a3.14", "1.2.3", "+-5", "12e+5.4
解:
1.关键:
(1)这个题目 就是 按照 题干的要求不断判断当前检测的 字符是否符合要求
(2) 怎么说呢? 反正就是 有序的 一些 if判断 ,只要有一点不满足就直接返回false ,同时 注意结合 辅助函数的 调用 (尤其是 那个 判断substr是否全部为 空格 的函数)
2.代码:
class Solution {
public:
bool isNumber(string s) {
//看来是我误会了, 如果是空格不能 两边都有东西
//看来空格两边必须有一个全部为空格
//判断一个 字符串 是否 表示一个 正数 或小数, 返回true or false;
//从s[0] 一致判断到 s[size-1]多个if 中只要 1个 false直接false
int size= s.size();
//特殊:
if(size == 1)
{
if(is_num(s[0]))
{
return true;
}
else{
return false;
}
}
int num_flag=0; // ==1代表已经出现过数字
int dot_flag = 0; //dot_flag=1 代表小数点已经出现一次了
for(int i=0 ;i<=size-1 ;i++)
{
//(-1)其它字符,字节false
if(is_else_char(s[i]))
{
return false;
}
//(0)如果遇到的 是一个符号
if(s[i]=='+' || s[i] == '-')
{
if(num_flag ==1)
{
return false;
}
else
{
continue;
}
}
//(1)如果 遇到 空格,直接continue
if(s[i] == ' ')
{
if((!is_all_blank(s.substr(0,i))) && (!is_all_blank(s.substr(i+1,size-i-1))))
{
return false;
}
else{
continue;
}
}
//(2)如果是e 后面必须接一个 整数
if(s[i]=='e' || s[i]=='E')
{
if(i==size-1 || is_all_blank(s.substr(i+1,size-i-1)))
{
return false;
}
if(num_flag == 0)
{
return false;
}
cout<0 && is_num(s[i-1]))
{
dot_flag = 1;
continue;
}
//2.后面是数字
if(i!=size-1 && is_num(s[i+1]))
{
dot_flag = 1;
continue;
}
//3.后面是e和一个整数 ok
if(i>0 && is_num(s[i-1]) && i<=size-3 && (s[i+1]=='e' || s[i+1]=='E') && is_integer(s.substr(i+2,size-i-2)))
{
return true;
}
//cout<<"是这里返回的false吗?"<='0')
{
return false;
}
if(ch == '+'|| ch == '-')
{
return false;
}
if(ch == ' '|| ch=='.')
{
return false;
}
return true;
}
//判断一个字符串是否为整数
bool is_integer(string s)
{
int size = s.size();
if(size == 0)
{
return false;
}
//忘记考虑一种情况了,就是如果 遇到了 “末尾空格” 就可以直接返回true
//s.substr(start_index,len)返回一个string类型变量
if(s[0]=='+' || s[0]=='-')
{
if(size == 1)
{
return false;
}
for(int i=1;i<=size-1;i++)
{
if(s[i]==' ')
{
return is_all_blank(s.substr(i,size-i));
}
if(! is_num(s[i]))
{
return false;
}
}
return true;
}
for(int i =0;i<=size-1;i++)
{
if(s[i]==' ')
{
return is_all_blank(s.substr(i,size-i));
}
if(!is_num( s[i]))
{
return false;
}
}
return true;
}
//设计一个 判断 一个字符串全部为 空格的 辅助函数
bool is_all_blank(string s)
{
int size = s.size();
for(int i=0;i<=size-1;i++)
{
if( s[i]!=' ')
{
return false;
}
}
return true;
}
bool is_num(char ch)
{
if(ch <= '9' && ch>='0')
{
return true;
}
else
{
return false;
}
}
};