文章简述

大家好,本篇是个人的第4篇文章。

承接第3篇文章《开启算法之路,还原题目,用debug调试搞懂每一道题》,本篇文章继续分享关于链表的算法题目。

本篇文章共有5道题目

一,反转链表(经典题目)

1.1.1 题目分析

反转链表是经典的题目,题中信息描述很清晰,给定一个单链表,将其反转。

先说说有什么思路呢?从题中给的案例输出结果看,是不是只需要将输入的链表的指针改成相反方向,就可以得到要输出的结果。

就好比如下图所示:

但是问题来了,我们是单链表,是没办法将下个节点直接指向该节点的上个节点。

因此就需要定义一个辅助指针,用来指向该节点的上个节点,这样就能完成,如下图所示。

那按照我们上面分析也就是将cur指针指向pre节点就可以了。

注意:此处有坑

当我们将当前节点【cur】指向上一个节点【pre】的时候,如何将指针向下移动呢?

此时的节点【cur】已经指向了上一个节点【pre】了,所以我们还需要一个临时变量去保存当前节点的下个节点,具体为什么这么做,我们在下面代码演示的时候debug看下过程。

接着我们上面的步骤,将指针向下移动,如图所示。

移动指针后,再将当前节点的next指针指向上一个节点。

最后当前节点没有下个节点的时候,就结束遍历,如图所示。

1.1.2 代码分析

按照套路,先初始化节点对象。

class ListNode {
int val;
ListNode next;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next) {
this.val = val;
this.next = next;
}
@Override
public String toString() {
return "ListNode{" +
"val=" + val +
'}';
}
}

创建单链表结构。

				// 创建单链表
ListNode l1 = new ListNode(1);
ListNode l2 = new ListNode(2);
ListNode l3 = new ListNode(3);
ListNode l4 = new ListNode(4);
ListNode l5 = new ListNode(5); NodeFun nodeFun = new NodeFun();
nodeFun.add(l1);
nodeFun.add(l2);
nodeFun.add(l3);
nodeFun.add(l4);
// 返回创建的链表
ListNode node = nodeFun.add(l5);

反转链表的代码。

public ListNode reverseListIteration(ListNode head) {
// 定义上节点辅助指针
ListNode pre = null;
// 定义当前节点辅助指针
ListNode cur = head;
// 循环当前节点不为空
while (null != cur) {
// 临时变量保存当前节点的下个节点
ListNode temp = cur.next;
// 当前节点的next指向上节点
cur.next = pre;
// 上节点向下移动
pre = cur;
// 当前节点指向下个节点
cur = cur.next;
}
return pre;
}
1.1.3 debug调试

节点初始化完成了,按照分析我们定义了2个节点,如上图第一次遍历【pre】节点是null,【cur】从第一个节点开始。

下一步debug调试我们先不急,回顾之前说的一个问题,为什么要将当前节点的下一个节点用临时变量保存,那我们直接看debug调试。

第一次遍历的时候,修改完指针后当前节点已经指向上一个节点了,再看上述题目分析的图解。

这就是为啥要先把当前节点的下个节点缓存起来。

上图debug我们看出,【cur】当前节点的指针已经指向null,下一步就是移动指针指向下一个节点。

我们再接着进行debug调试,按照上述分析,第二步循环就是将节点【2】指向上一个节点【1】,如下图所示。

现在当前节点【cur】已经指向【2】,那它的下个节点就是【1】,如下图所示。

经过上面的两步循环,成功的将指针进行了反转,剩下的节点循环也就如出一辙了。

当循环到最后节点【5】时,下个节点为null,此时结束while循环,而节点【5】也是指向了上一个节点【4】。

最后我们再看下运行结果。

二,回文链表

1.2.1 题目分析

如果做过字符串的算法题,里面有个回文字符串的题目。没错,它俩的意思是一样的。

看题目描述得知一个链表是不是回文链表,就是看链表就是看链表正读和反读是不是一样的。

假如说,我们拿到了后半部分链表,再将其反转。去和链表的前半部分比较,值相等就是回文链表了。

注意:

这种方式会破坏原链表的结构,为保证题目的一致性,最后再将链表再重新拼接

另外一种解题方式为:将整个链表节点遍历保存到数组中,而数组是有下标,并可以直接获取数组的大小,那么只需从数组的首尾去判断即可

反转链表上一道题我们已经分享了,现在重点是如何获取后半部分的链表。

我们再说说快慢指针的思想,通常我们定义2个指针,一个移动快,一个移动慢。详细的案例可以参考本人上一篇文章《开启算法之路,还原题目,用debug调试搞懂每一道题》,有一道关于快慢指针的题目。

定义慢指针每次移动1个节点,快指针每次移动2个节点,当然我们是需要保证快节点的下下个个节点不为空。

slow = slow.next;
fast = fast.next.next;

其实快慢指针的思想就是,假设链表是一个回文链表,快指针比慢指针是多走一步,当快指针走完的时候,慢指针也就刚好走到该链表的一半。

上图中slow指针正好走到链表的一半,此时也就得到链表的后半部分了,即slow.next

1.2.2 代码分析

老套路,先创建一个回文链表。

			  ListNode l1 = new ListNode(1);
ListNode l2 = new ListNode(2);
ListNode l3 = new ListNode(2);
ListNode l4 = new ListNode(1); NodeFun nodeFun = new NodeFun();
nodeFun.add(l1);
nodeFun.add(l2);
nodeFun.add(l3);
ListNode head = nodeFun.add(l4);

获取后半部分链表代码。

private ListNode endOfFirstHalf(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast.next != null && fast.next.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}

反转链表的代码与上题目是一样的。

最后将两个链表进行判断是否是一样的。

  			// 判断是否回文
ListNode p1 = head;
ListNode p2 = secondHalfStart;
boolean flag = true;
while (flag && p2 != null) {
if (p1.val != p2.val) {
flag = false;
}
p1 = p1.next;
p2 = p2.next;
}
1.2.3 debug调试

先获取链表的后半部分。


debug开始循环后,fast直接走到链表的第3个节点【2】

slow.next就是链表的后半部分,再将后半部分进行链表反转

最后我们也就得到如下2个链表。

最后将这2个链表进行比较是否相等,相等则是回文链表。

三,链表的中间节点

1.3.1 题目分析

获取链表的中间节点乍一看和回文链表中使用快慢指针获取后半链表有点类似呢?

没错,这波操作是类似的,但也并不是完全一样,其主要思想还是快慢指针。

换句话说,如果你已理解了上面的题,那这道题也就不是什么事了。话不多说,先来分析一波。

同样我们还是定义slow慢指针每次移动一个节点,fast快指针每次移动2个节点。


那么fast快指针移动到最后节点时,slow慢指针也就是要返回的链表。

我想,你是不是有个疑问。就是为什么慢指针是移动一个节点,快节点移动2个节点?如果是偶数个节点,这个规则还正确吗!那就验证下。

为了方便,就继续上面节点的遍历。

题目中描述,如果有2个中间节点,返回第二个节点,所以返回节点【4,5,6】也就符合要求了

1.3.2 代码分析

创建链表结构。

    		ListNode l1 = new ListNode(1);
ListNode l2 = new ListNode(2);
ListNode l3 = new ListNode(3);
ListNode l4 = new ListNode(4);
ListNode l5 = new ListNode(5); NodeFun nodeFun = new NodeFun();
nodeFun.add(l1);
nodeFun.add(l2);
nodeFun.add(l3);
nodeFun.add(l4);
ListNode head = nodeFun.add(l5);

获取后半部分链表代码。

			 // 快慢指针
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
//移动指针
fast = fast.next.next;
slow = slow.next;
}
return slow;
1.3.3 debug调试

快指针移动到节点【3】,慢指针移动到节点【2】

接着再走一步,快指针移动到节点【5】,慢节点移动到节点【3】,到此也就满足题意的要求了。

四,链表中倒数第k个节点

1.4.1 题目分析

这道题要求就是返回倒数K个节点,最笨的办法就是参考上面链表反转,先将链表反转。获取前K个节点,将获取的节点再次进行反转即可得到题目要求。

但是显然这种方式只能满足答案输出,经过上面的3道题目,有没有得到什么启发呢?

是的,这道题依然可以使用双指针解决,是不是感觉双指针可以解决所有的链表问题了(QAQ)。

再仔细一想,是不是感觉和上一道《链表的中间节点》题目很类似?获取链表的中间节点是返回后半部分节点,而本道题是要求返回指定K个节点。

那就直接说结论吧,同样是定义快慢指针。只不过在上道题中快指针是每次移动2个节点,本道题中给定的K,就是快指针移动的节点个数。

同样初始化指针都在首节点,如果我们先将fast指针移动K个节点。

到此才算初始化节点完成,剩下的操作就是遍历剩下的链表,直到fast指针指向最后一个节点。



一直遍历到fast节点为null,此时返回slow指针所指引的节点。

1.4.2 代码分析

初始化链表,由于和前几道题的操作是一样的,此处就不在展示。

获取倒数第K个节点的代码。

public ListNode getKthFromEnd(ListNode head, int k) {
ListNode slow = head;
ListNode fast = head;
// 先将快指针向前移动K
while (k-- > 0) {
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
return slow;
}
1.4.3 debug调试

按照上面图解分析,fast快指针指向节点【3】的时候才算真正初始化快慢指针完成。

当快指针指向节点【5】时,slow慢节点指向节点【3】

注意:中间省略了一步,即慢指针指向节点【2】时,快指针指向节点【4】

节点【5】是最后一个节点,再次进入while循环。

最后一次循环时,慢指针指向了4,快指针下一个节点已经为null,此时结束循环。

五,移除重复节点

1.5.1 题目分析

这道题和上一篇中的题目【删除排序链表中的重复元素】是一样的,简单的做法即利用Set集合保存未重复的节点,再遍历链表判断是否已存在Set集合中。

因此本道题就不在多分析,直接贴上代码。

1.5.2 代码分析
Set<Integer> set = new HashSet<>();
ListNode temp = head;
while(temp != null && temp.next != null){
set.add(temp.val);
if(set.contains(temp.next.val)){
temp.next = temp.next.next;
}else{
temp = temp.next;
}
}
return head;
}

六,总结

本次文章共分享总结5道题目,仔细分析有没有发现这些题套路都是一样的。都利用了双指针的思想,通过一定的规则移动快慢指针获取指定链表节点。

本次的5道题目和上次的3道题目,基本已经包含了链表简单题目的所有类型。当你把本篇文章的题目看完后,关于链表的简单题目你也已经做完了。

本人已经将链表的所有简单题目刷完,总结出来的结论即套路都是一样的。简单来说,大部分的题目都可以利用双指针,递归,数组来完成

在下篇文章中会对链表的简单题目做一个小总结。

最后,求关注

原创不易,每一篇都是用心在写。如果对您有帮助,就请一键三连(关注,点赞,再转发)

我是杨小鑫,坚持写作,分享更多有意义的文章。

感谢您的阅读,期待与您相识!

链表算法题二,还原题目,用debug调试搞懂每一道题的更多相关文章

  1. 链表算法题之中等级别,debug调试更简单

    文章简述 大家好,本篇是个人的第 5 篇文章 从本篇文章开始,分享关于链表的题目为中等难度,本次共有 3 道题目. 一,两数相加 1.1 题目分析 题中写到数字是按照逆序的方式存储,从进位的角度看,两 ...

  2. 开启算法之路,还原题目,用debug调试搞懂每一道题

    文章简述 大家好,本篇是个人的第 3 篇文章. 承接第一篇文章<手写单链表基础之增,删,查!附赠一道链表题>,在第一篇文章中提过,在刷算法题之前先将基础知识过一遍,这样对后面的做算法题是很 ...

  3. LeetCode 上最难的链表算法题,没有之一!

    题目来源于 LeetCode 第 23 号问题:合并 K 个排序链表. 该题在 LeetCode 官网上有关于链表的问题中标注为最难的一道题目:难度为 Hard ,通过率在链表 Hard 级别目前最低 ...

  4. 《剑指Offer》算法题——二维数组查找

    题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. class Solutio ...

  5. JAVA常见算法题(二十七)

    题目:给定一个存放整数的数组,请写一个算法,把偶数移动到该数组的右边,奇数放在该数组的左边,请考虑时间和空间的最优算法. package com.forezp.util; /** * 题目:给定一个存 ...

  6. JAVA常见算法题(二十八)

    package com.forezp.util; import java.util.Arrays; /** * 两个int数组,都是从小到大的的排列,请合并为一个新的数组,也是从小到到大的排列, * ...

  7. JAVA常见算法题(二十六)

    package com.xiaowu.demo; import java.util.Scanner; /** * Java实现将阿拉伯数字转为汉字 * * @author WQ * */ public ...

  8. JAVA常见算法题(二十五)

    /** * Java实现中文数字转换为阿拉伯数字 * * * @author WQ * */ public class Demo26 { public static void main(String[ ...

  9. JAVA常见算法题(二十四)

    package com.xiaowu.demo; //一个5位数,判断它是不是回文数.即12321是回文数,个位与万位相同,十位与千位相同. public class Demo24 { public ...

随机推荐

  1. 并发队列:ArrayBlockingQueue实际运用场景和原理

    ArrayBlockingQueue实际应用场景 之前在某公司做过一款情绪识别的系统,这套系统通过调用摄像头接口采集人脸信息,将采集的人脸信息做人脸识别和情绪分析,最终经过一定的算法将个人情绪数据转化 ...

  2. Array循环for、for in、for of、forEach各间优劣

    JavaScript中有多种循环Array的方式,你是否常常分不清他们的细微差别,和适用场景.本文将详细梳理各间的优缺点,整理成表以便对比. 循环 可访问element 可访问index 可迭代pro ...

  3. c#的dllimport使用方法详解(Port API)

    DllImport是System.Runtime.InteropServices命名空间下的一个属性类,其功能是提供从非托管DLL(托管/非托管是微软的.net framework中特有的概念,其中, ...

  4. Codeforces750E. New Year and Old Subsequence (线段树维护DP)

    题意:长为2e5的数字串 每次询问一个区间 求删掉最少几个字符使得区间有2017子序列 没有2016子序列 不合法输出-1 题解:dp i,p(0-4)表示第i个数匹配到2017的p位置删掉的最少数 ...

  5. 矩阵树定理(Kirchhoff || Laplace)初探——Part 1(无向图计数)

    必备知识: 高斯消元,图论基本知识(好像就这...(雾)) 这里是无向图部分,请不要走错场... 定义 我们将邻接矩阵定义为矩阵A(u,v),我想邻接矩阵就不用再多说了: 我们将每个点的度数矩阵定义为 ...

  6. UVA-257 哈希算法

    UVA-257 题意: 给你很多串,你需要找到这个串内有没有两个长度大于3的回文字符串,且要保证这两个回文字符串不相同,也不能完全覆盖,但可以重合一部分 题解: 首先判断回文的话可以通过马拉车算法(M ...

  7. CodeForces - 612D 思维

    题意: 给你n个线段和一个整数k,你需要找出来所有能被任意k条线段同时覆盖的区间个数的最小值,并按从左到右的顺序输出每个区间. 题解: 对于题目输入的n个线段的左端点L,右端点R,把它们分开放在结构体 ...

  8. ASP.NET 部署IIS后如何访问共享目录文件

    1.我的电脑-->管理-->系统工具-->本地用户和组-->用户-->右键新建用户-->创建一个与远程文件夹相同的账号密码! 如下图: 以上为部署接口服务器中的用户 ...

  9. Please commit your changes or stash them before you merge问题解决

    问题描述 error: Your local changes to the following files would be overwritten by merge: xxx/xxx/xxx.c P ...

  10. mysql 索引类型以及创建

    明天就去面浦发了,感觉对数据库有些忘了,时间紧迫,就直接把链接贴这了,有空再整理. 参考: 1. https://www.cnblogs.com/crazylqy/p/7615388.html