LeetCode--链表2-双指针问题
LeetCode--链表2-双指针问题
思考问题:
判断一个链表是否有环
列举几种情况:
graph LR
A-->B
B-->C
C-->D
D-->E
E-->C
graph LR
A-->B
B-->A
你可能已经使用哈希表提出了解决方案。但是,使用双指针技巧有一个更有效的解决方案。在阅读接下来的内容之前,试着自己仔细考虑一下。
想象一下,有两个速度不同的跑步者。如果他们在直路上行驶,快跑者将首先到达目的地。但是,如果它们在圆形跑道上跑步,那么快跑者如果继续跑步就会追上慢跑者。
这正是我们在链表中使用两个速度不同的指针时会遇到的情况:
如果没有环,快指针将停在链表的末尾。
如果有环,快指针最终将与慢指针相遇。
所以剩下的问题是:
这两个指针的适当速度应该是多少?
一个安全的选择是每次移动慢指针一步,而移动快指针两步。每一次迭代,快速指针将额外移动一步。如果环的长度为 M,经过 M 次迭代后,快指针肯定会多绕环一周,并赶上慢指针。
那其他选择呢?它们有用吗?它们会更高效吗?
题1 环形链表1(简单)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
// 当链表中没有节点或只有一个节点时 false
if(!head || !head->next)
return false;
// 两个节点的初始值都是head
ListNode* p1 = head;
ListNode* p2 = head;
//
while( p1->next && p2->next)
{
if(p1->next == p2->next->next)
return true;
p1 = p1->next;
p2 = p2->next->next;
if( !p1 || !p2 )
return false;
}
return false;
}
};
题2 环形链表2(中等题)
自己的思路:
巴拉巴拉
然而
自己的思路并不对...
看看人家的想法好了
graph LR
0-->1
1-->2
2-->3
3-->4
4-->5
5-->2
剑指offer上这道题的思路,主要就是运用双指针,起点不同。
设环内节点个数为n,那就一个从0节点出发,另一个从第n+1个节点出发。
相遇处,就是入口处。
说白了就是带环的相遇问题。
所以这道题需要解决几个问题
- 确定链表是否有环
- 确定链表内节点个数
- 确定入口节点
/**
* 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) {
int num = counter(head);
if ( num == 0)
return NULL;
ListNode* pa = head;
ListNode* pb = head;
for (int i = 0 ; i < num; i++)
{
pb = pb->next;
}
while (pa->next && pa->next)
{
if(pa == pb)
return pb;
pa = pa->next;
pb = pb->next;
}
return NULL;
}
int counter( ListNode* head )
{
// 链表为空或链表中只有一个节点-->不存在环-返回0
if( !head || !head->next )
return 0;
// 设置双指针
ListNode* p1 = head;
ListNode* p2 = head;
//
int count = 0;
while( p1->next && p2->next )
{
// 若p1和P2即将相遇,重新赋值,并开始计数
if( p1->next == p2->next->next)
{
p1 = p1->next;
p2 = p1->next;
count = 2;
while(p2->next)
{
if( p1 == p2 )
{
return count;
}
p2 = p2->next;
count ++;
}
}
p1 = p1->next ;
p2 = p2->next->next;
if(!p1||!p2)
return 0;
}
return 0;
}
};
超出时间限制-
修改为下面的代码,通过了测试;
修改内容:
- 避免了双重的while循环
- 避免while的循环的终止条件是真值
/**
* 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) {
// 确定快慢指针相遇的节点
ListNode* pmeet = meeting(head);
if ( pmeet == NULL)
return NULL;
// 确定环内节点的个数
int count = 1 ;
ListNode* p1 = pmeet;
while( p1->next != pmeet )
{
p1 = p1->next;
count ++;
}
// 确定环的入口节点
ListNode* pa = head;
ListNode* pb = head;
for (int i = 0 ; i < count; i++)
{
pb = pb->next;
}
while ( pa != pb )
{
pa = pa->next;
pb = pb->next;
}
return pa;
}
// 确定快慢指针相遇的节点
ListNode* meeting (ListNode* head )
{
// 链表为空或链表中只有一个节点-->不存在环-返回0
if( !head || !head->next )
return NULL;
// 设置双指针
ListNode* p1 = head;
ListNode* p2 = head;
//
ListNode* meet = head;
while( p1->next && p2->next )
{
// 若p1和P2即将相遇,重新赋值,并开始计数
if( p1->next == p2->next->next)
{
meet = p1->next;
return meet;
}
p1 = p1->next ;
p2 = p2->next->next;
if(!p1||!p2)
return NULL;
}
return NULL;
}
};
题3 相交链表
graph LR
A-->B
B-->C
C-->F
E-->F
D-->E
F-->G
G-->H
/**
* 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) {
// 如果两个链表其中任意一个为空就不会有相交节点
if( !headA || !headB )
return NULL;
// 两个链表从头节点就相交了
if( headA == headB )
return headA;
ListNode* pa = headA;
ListNode* pb = headB;
// 求两个链表的长度
int numa = counter(headA);
int numb = counter(headB);
// 哪一个链表长,其指针就往前步进长度差的步长
int step = 0;
if ( numa >= numb )
{
step = numa - numb;
for(int i = 0; i < step ; ++i)
{
pa = pa->next;
}
}
else
{
step = numb - numa;
for(int j = 0 ; j < step; ++j)
{
pb = pb->next;
}
}
// 定位第一个相同的节点
while ( pa && pb && (pa != pb) )
{
pa = pa->next;
pb = pb->next;
}
return pb;
// 第二种循环的写法
/*
while ( pa && pb )
{
if ( pa == pb )
return pa;
pa = pa->next;
pb = pb->next;
}
return NULL;
*/
}
// 返回单链表中的节点数
int counter(ListNode* head)
{
ListNode* p = head;
int count = 1;
if( !p )
return 0;
if( !p->next )
return 1;
while( p->next )
{
p = p->next;
++count;
}
return count;
}
};
题4 删除链表中的倒数第n个节点
第一想法就是通过辅助栈求解
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
//
if( !head || n <= 0 )
return NULL;
// 建立一个辅助栈
stack<ListNode*> nodes;
// 遍历链表,依次放入栈中
ListNode* p = head;
while(p)
{
nodes.push(p);
p = p->next;
}
if(n == 1)
{
nodes.pop();
ListNode* pend = nodes.top();
pend->next = nullptr;
return head;
}
// 遍历栈中的节点到第n-1个节点
int i = 1;
while ( i != n-1 && n > 1 )
{
if(nodes.empty())
return NULL;
nodes.pop();
++i;
}
ListNode* pe = nodes.top();
nodes.pop();
nodes.pop();
ListNode* ps = nodes.top();
ps->next = pe;
return head;
}
};
测试用例通过,但是提交解答报错
Line 152: Char 17: runtime error: reference binding to misaligned address 0xbebebebebebec0b6 for type 'struct ListNode *', which requires 8 byte alignment (stl_deque.h)
修改后代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
//
if( !head || n <= 0 )
return NULL;
// 建立一个辅助栈
stack<ListNode*> nodes;
// 遍历链表,依次放入栈中
ListNode* p = head;
while(p)
{
nodes.push(p);
p = p->next;
}
// 倒数第1个节点
if(n == 1)
{
nodes.pop();
if(nodes.empty())
return NULL;
ListNode* pend = nodes.top();
pend->next = NULL;
return head;
}
// 倒数n-1个节点之前的节点出栈
int i = 1;
while ( i != n-1 && n >= 2 )
{
if(nodes.empty())
return NULL;
nodes.pop();
++i;
}
// 得到第n-1个节点,使其出栈
ListNode* pe = nodes.top();
nodes.pop();
// 第n个节点出栈
nodes.pop();
// 如果倒数第n个节点之前再无节点,head = pe
if(nodes.empty())
{
head = pe;
return head;
}
ListNode* ps = nodes.top();
ps->next = pe;
return head;
}
};
使用辅助栈的时候,代码的鲁棒性要十分注意
出栈后,栈是否为空一定要注意!!!
小结 - 链表中的双指针问题
代码模板:
// Initialize slow & fast pointers
ListNode* slow = head;
ListNode* fast = head;
/**
* Change this condition to fit specific problem.
* Attention: remember to avoid null-pointer error
**/
while (slow && fast && fast->next) {
slow = slow->next; // move slow pointer one step each time
fast = fast->next->next; // move fast pointer two steps each time
if (slow == fast) { // change this condition to fit specific problem
return true;
}
}
return false; // change return value to fit specific problem
提示
它与我们在数组中学到的内容类似。但它可能更棘手而且更容易出错。你应该注意以下几点:
在调用 next 字段之前,始终检查节点是否为空。
获取空节点的下一个节点将导致空指针错误。例如,在我们运行 fast = fast.next.next 之前,需要检查 fast 和 fast.next 不为空。仔细定义循环的结束条件。运行几个示例,以确保你的结束条件不会导致无限循环。在定义结束条件时,你必须考虑我们的第一点提示。
复杂度分析
空间复杂度分析容易。如果只使用指针,而不使用任何其他额外的空间,那么空间复杂度将是 O(1)。但是,时间复杂度的分析比较困难。为了得到答案,我们需要分析运行循环的次数。
在前面的查找循环示例中,假设我们每次移动较快的指针 2 步,每次移动较慢的指针 1 步。
如果没有循环,快指针需要 N/2 次才能到达链表的末尾,其中 N 是链表的长度。
如果存在循环,则快指针需要 M 次才能赶上慢指针,其中 M 是列表中循环的长度。
显然,M <= N 。所以我们将循环运行 N 次。对于每次循环,我们只需要常量级的时间。因此,该算法的时间复杂度总共为 O(N)。
自己分析其他问题以提高分析能力。别忘了考虑不同的条件。如果很难对所有情况进行分析,请考虑最糟糕的情况。
LeetCode--链表2-双指针问题的更多相关文章
- Leetcode链表
Leetcode链表 一.闲聊 边学边刷的--慢慢写慢慢更 二.题目 1.移除链表元素 题干: 思路: 删除链表节点,就多了一个判断等值. 由于是单向链表,所以要删除节点时要找到目标节点的上一个节点, ...
- [LeetCode] [链表] 相关题目总结
刷完了LeetCode链表相关的经典题目,总结一下用到的技巧: 技巧 哑节点--哑节点可以将很多特殊case(比如:NULL或者单节点问题)转化为一般case进行统一处理,这样代码实现更加简洁,优雅 ...
- LeetCode 链表题 ( Java )
leetcode 237. 删除链表中的节点 链接:https://leetcode-cn.com/problems/delete-node-in-a-linked-list/ 示例 : 输入: he ...
- C#LeetCode刷题-双指针
双指针篇 # 题名 刷题 通过率 难度 3 无重复字符的最长子串 24.5% 中等 11 盛最多水的容器 43.5% 中等 15 三数之和 16.1% 中等 16 最接近的三数之和 3 ...
- [LeetCode] 链表反转相关题目
暂时接触到LeetCode上与链表反转相关的题目一共有3道,在这篇博文里面总结一下.首先要讲一下我一开始思考的误区:链表的反转,不是改变节点的位置,而是改变每一个节点next指针的指向. 下面直接看看 ...
- LeetCode链表解题模板
一.通用方法以及题目分类 0.遍历链表 方法代码如下,head可以为空: ListNode* p = head; while(p!=NULL) p = p->next; 可以在这个代码上进行修改 ...
- LeetCode链表相加-Python<二>
上一篇:LeetCode两数之和-Python<一> 题目:https://leetcode-cn.com/problems/add-two-numbers/description/ 给定 ...
- leetcode 链表类型题总结
链表测试框架示例: // leetcodeList.cpp : 定义控制台应用程序的入口点.vs2013 测试通过 // #include "stdafx.h" #include ...
- leetcode链表相关
目录 2/445两数相加 综合题(328奇偶链表, 206反转链表, 21合并两个有序链表 ) 92反转链表 II 链表排序(148排序链表, 876链表的中间结点) 142环形链表 II 160相交 ...
- 【Leetcode链表】移除链表元素(203)
题目 删除链表中等于给定值 val 的所有节点. 示例: 输入: 1->2->6->3->4->5->6, val = 6 输出: 1->2->3-&g ...
随机推荐
- [HDU多校]Ridiculous Netizens
[HDU多校]Ridiculous Netizens 点分治 分成两个部分:对某一点P,连通块经过P或不经过P. 经过P采用树形依赖背包 不经过P的部分递归计算 树型依赖背包 v点必须由其父亲u点转移 ...
- CentOS下MySQL忘记root密码解决方法【亲测】
1.修改MySQL的登录设置: # vim /etc/my.cnf 在[mysqld]的段中加上一句:skip-grant-tables 例如: [mysqld] datadir=/var/lib/m ...
- 吴裕雄--天生自然python学习笔记:python安装配置tesseract-ocr-setup-3.05.00dev.exe
下载地址:https://digi.bib.uni-mannheim.de/tesseract/tesseract-ocr-setup-3.05.00dev.exe 点击安装,记得复制安装的路径,待会 ...
- python实现个人信息随机生成
""" 生成随机姓名.电话号码.身份证号.性别.应行卡号.邮箱 """ import random from firstname impor ...
- python后端面试第五部分:Linux操作系统--长期维护
################## Linux操作系统 ####################### 1,讲一下你常用的Linux/git命令和作用: 2,查看当前进程是用什么命 ...
- deeplearning.ai 序列模型 Week 2 NLP & Word Embeddings
1. Word representation One-hot representation的缺点:把每个单词独立对待,导致对相关词的泛化能力不强.比如训练出“I want a glass of ora ...
- spring和mybatis整合报错:org.springframework.beans.MethodInvocationException: Property 'dataSource' threw exception; nested exception is java.lang.NoClassDefFoundError
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyExceptio ...
- SpringBoot webjars 映射
添加静态资源映射 @Override protected void addResourceHandlers(ResourceHandlerRegistry registry) { registry.a ...
- java面试题 - 框架
1.servlet执行流程 客户端发出http请求,web服务器将请求转发到servlet容器,servlet容器解析url并根据web.xml找到相对应的servlet,并将request.resp ...
- 吴裕雄--天生自然python学习笔记:Python3 正则表达式
Python 自1.5版本起增加了re 模块,它提供 Perl 风格的正则表达式模式. re 模块使 Python 语言拥有全部的正则表达式功能. compile 函数根据一个模式字符串和可选的标志参 ...