剑指offer链表题的双指针法总结

本篇博客旨在总结双指针法在剑指offer链表题中的应用

包括删除链表中重复的节点、链表中倒数第k个节点、链表中环的入口节点、反转链表、合并两个排序的链表、两个链表的第一个公共节点。

根据双指针的类型,可以大致分为三种:

第一种是间隔一定距离的双指针法,包括删除链表中重复的节点、链表中倒数第k个节点两题

  • 删除链表中重复的节点
    在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5

这道题类似于数组的删除重复节点,只需要设置前后两个指针,当碰到前后两个指针节点值相同时,后指针循环后移,直到前后指针囊括了一整个重复节点值的区间即可。

public class Solution {
    public ListNode deleteDuplication(ListNode pHead)
    {
        // 典型的前后双指针
        if(pHead == null || pHead.next == null){
            return pHead;
        }
        ListNode fast = pHead.next;
        ListNode slow = new ListNode(0);
        boolean needHead = true;
        if(pHead.val == fast.val){
            slow = pHead;
            needHead = false;
        }else{
            slow.next = pHead;
        }
        while(fast != null){
            if(fast.val != slow.next.val){
                fast = fast.next;
                slow = slow.next;
                continue;
            }
            while(fast.next != null && fast.val == slow.next.val){
                fast = fast.next;
            }
            if(fast.val == slow.next.val){
                slow.next = null;
                break;
            }
            slow.next = fast;
            fast = fast.next;
        }
        if(needHead){
            return pHead;
        }else if(pHead.next == null){
            return null;
        }
        return pHead.next;
    }
}
  • 链表中倒数第k个节点
    输入一个链表,输出该链表中倒数第k个结点。

这道题比较容易想到的是先遍历一次链表,求出链表的长度,然后第二次遍历链表即可求出倒数第k个节点。

但是如果想要只遍历一次链表就求出结果,就需要用到双指针了。可以设置前后两个指针的间隔为k,这样当后面的指针指向链表尾端时,前面的指针即指向目标节点

public ListNode FindKthToTail(ListNode head, int k) {
        // 考虑k的值有哪些情况
        // k值可能为负值,0,或者超过链表长度的值,当出现这些情况的时候,要进行相应的提示
        //快慢指针法
        if(head == null || k<=0){
            return null;
        }
        ListNode fast = head;
        for(int i = 0; i < k-1; i++){
            if(fast.next != null){
                fast = fast.next;
            }else{
                return null;
            }
        }
        ListNode slow = head;
        while(fast.next != null){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
    }

第二种是快慢指针法,包括链表中环的入口节点。

  • 链表中环的入口节点
    给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。

Java语言的话有两种解法:

  1. 运用Java中的泛型,定义HashMap<TreeNode,Integer>,在遍历链表的过程中不断检测所遍历的节点是否已在map中存在,存在即为环的入口
  2. 可以利用快慢指针法求解。这里详细讲一下快慢指针法

第一步,检测链表中是否真的存在环。
由于链表中存在环,快指针在进入环后会不断循环,因此慢指针一定会追上快指针。反过来也表示,如果慢指针追上快指针了,即表示链表中存在环。
第二步,找到环的入口。
假设环的长度为n,我们可以使用上文间隔一定距离的双指针法进行求解。定义前指针在链表开头,定义后指针在前指针的n个节点后,两指针同时后移,当两个指针相同时,即为环的入口。
第三步,为了得到环的入口,我们需要知道环的长度。
同理可以使用间隔一定距离的双指针法。由第一步可得,在环中已经有两个指针,可以保持一个指针不动,另一个指针向前移动,同时计数,当两个指针再一次重合时,即可得到环的长度。

public ListNode EntryNodeOfLoop(ListNode pHead)
    {
        if(pHead == null){
            return null;
        }
        // 1.判断链表中有环
        ListNode l=pHead,r=pHead;
        boolean flag = false;
        while(r != null && r.next!=null){
            l=l.next;
            r=r.next.next;
            if(l==r){
                flag=true;
                break;
            }
        }
        if(!flag){
            return null;
        }else{
            // 2.得到环中节点的数目
            int n=1;
            r=r.next;
            while(l!=r){
                r=r.next;
                n++;
            }
            // 3.找到环中的入口节点
            l=r=pHead;
            for(int i=0;i<n;i++){
                r=r.next;
            }
            while(l!=r){
                l=l.next;
                r=r.next;
            }
            return l;
        }

    }

第三种是操作需要指向多个地址,所以设置多指针的题,包括反转链表、合并两个排序的链表以及两个链表的第一个公共节点。

  • 反转链表
    输入一个链表,反转链表后,输出新链表的表头

Java中有两种解法:

  1. 反转题最直观的解法是是使用链表。定义Java节点类型的栈,从头到尾入栈再出栈,即可得到反转链表。
  2. 解法一需要耗费额外的空间,更好的解法是就地反转。

我们可以从前到后遍历链表,遍历的过程中可以调转前后节点的指向顺序,但是需要注意的是,反转完前后节点的指向顺序后,后节点后面的节点就不能拼接当前指针了,因此我们还需要额外添加一个结点指向后节点的后节点。

public ListNode ReverseList(ListNode head) {
         // 判断链表为空或长度为1的情况
        if(head == null || head.next == null){
            return head;
        }
        ListNode pre = null; // 当前节点的前一个节点
        ListNode next = null; // 当前节点的下一个节点
        while(head != null){
            next = head.next; // 记录当前节点的下一个节点位置;
            head.next = pre; // 让当前节点指向前一个节点位置,完成反转
            pre = head; // pre 往右走
            head = next;// 当前节点往右继续走
        }
        return pre;
    }
  • 合并两个排序的链表
    输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

简单的合并链表,可以以其中一个链表为主体,将另一个链表逐渐加进来。需要注意的是初始指针要设置为null,并指向两个链表的首部。

public ListNode Merge(ListNode list1,ListNode list2) {
        //创建一个头结点会更方便
        if(list1 == null){
            return list2;
        }else if(list2 == null){
            return list1;
        }
        ListNode h = new ListNode(Integer.MIN_VALUE);
        ListNode r = h;
        while(list1 != null && list2 != null){
            if(list1.val < list2.val){
                r.next = list1;
                list1 = list1.next;
            }else{
                r.next = list2;
                list2 = list2.next;
            }
            r = r.next;
        }
        if(list1 != null) r.next = list1;
        if(list2 != null) r.next = list2;
        return h.next;
    }
  • 两个链表的第一个公共节点
    输入两个链表,找出它们的第一个公共结点。

这道题类似于树中的寻找两节点的最小公共祖先。因此可以类比树的解法。

  1. 首先将两个指针指向的两个链表中的结点调整到相对于公共节点相同的位置。由于在公共节点后的链表相同,可以先遍历两个链表,求出长度的差值,即可借此调整至同一水平线。
  2. 在调整至同一水平线后,两个指针每次移动相同步数即可最终得到第一个公共节点。
  3. 在遇到链表长度很长需要优化时间复杂度时,可以在第二步使用整数快速幂的解法。可以将第二步的时间复杂度降为logn。
public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        // 类似于查找树的最早祖先,使用快速幂来算
        // 第一步是让两个链表的指针调整到同一水平线
        int count1 = 0;
        int count2 = 0;
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        while(p1 != null){
            p1 = p1.next;
            count1++;
        }
        while(p2 != null){
            p2 = p2.next;
            count2++;
        }
        while(count1 != count2){
            if(count1 > count2){
                pHead1 = pHead1.next;
                count1--;
            }else{
                pHead2 = pHead2.next;
                count2--;
            }
        }
        while(pHead1 != pHead2){
            pHead1 = pHead1.next;
            pHead2 = pHead2.next;
        }
        return pHead1;
    }

原文地址:https://www.cnblogs.com/Water2Wine/p/12416603.html

时间: 2024-10-13 22:15:05

剑指offer链表题的双指针法总结的相关文章

剑指 offer 第一题: 二维数组中的查找

打算写 图解剑指 offer 66 题 的系列文章,不知道大家有没有兴趣 ?? 题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数. 题目分析 图 1 如果没有头绪的话,很显然使用 暴力解法 是完全可以解决该问题的. 即遍历二维数组中的每一个元素,时间复杂度:O(n^2). 其实到这里我们就可以发现,使用这种暴力解法并没有充分利用题目给出的信息.这

整理剑指offer&mdash;链表操作

在链表中找到第一个含有某值的节点并删除该节点. 在这里我创建的链表里,头节点是不包含有效数据的,它只是起一个辅助作用,真正的链表数据从首节点开始. typedef struct Node{    int data; //数据域    struct Node * pNext; //指针域}NODE, *PNODE; 1: void RemoveNode(PNODE pHead, int val) 2: { 3: if(pHead == NULL || pHead->pNext == NULL) 4

剑指offer编程题Java实现——面试题12相关题大数的加法、减法、乘法问题的实现

用字符串或者数组表示大数是一种很简单有效的表示方式.在打印1到最大的n为数的问题上采用的是使用数组表示大数的方式.在相关题实现任意两个整数的加法.减法.乘法的实现中,采用字符串对大数进行表示,不过在具体的计算中,还是要将字符串转化成字符数组来进行计算. 实现两个大数的加法,要考虑到两个问题,两个数的和的位数问题,以及如何处理两个数按位相加产生的进位问题.首先两个整数相加,两个数的和的位数最多比最大的整数的位数多1:这样和的位数就确定了.对于进位问题,我的做法是先进行按位相加,相加操作完成后再按照

剑指offer编程题Java实现——面试题9斐波那契数列

题目:写一个函数,输入n,求斐波那契数列的第n项. 1 package Solution; 2 3 /** 4 * 剑指offer面试题9:斐波那契数列 5 * 题目:写一个函数,输入n,求斐波那契数列的第n项. 6 * 0, n=1 7 * 斐波那契数列定义如下:f(n)= 1, n=2 8 * f(n-1)+f(n-2), n>2 9 * @author GL 10 * 11 */ 12 public class No9Fibonacci { 13 14 public static void

剑指offer编程题Java实现——面试题7相关题用两个队列实现一个栈

剑指offer面试题7相关题目:用两个队列实现一个栈 解题思路:根据栈的先入后出和队列的先入先出的特点1.在push的时候,把元素向非空的队列内添加2.在pop的时候,把不为空的队列中的size()-1份元素poll出来,添加到另为一个为空的队列中,再把队列中最后的元素poll出来两个队列在栈不为空的情况下始终是有一个为空,另一个不为空的.push添加元素到非空的队列中,pop把非空队列的元素转移到另一个空的队列中,直到剩下最后一个元素,这个元素就是要出栈的元素(最后添加到队列中的元素). 1

剑指offer编程题Java实现——面试题14调整数组顺序使奇数位于偶数之前

题目: 输入一个整数数组,实现一个函数来调整该数组中数组的顺序,使得所有的奇数位于数组的前半部分,偶数位于数组的后半部分. 解题思路:数组中维护两个指针,第一个指针初始化时候指向数组头部,第二个指针初始化时候指向数组尾部,第一个指针指向的数字总是偶数,第二个指针指向的数字总是奇数,如果第一个指针在第二个指针之前,则交换两指针指向的元素. 1 package Solution; 2 3 /** 4 * 剑指offer面试题14:调整数组顺序是奇数位于偶数前面 5 * 题目:输入一个整数数组,实现一

剑指offer编程题Java实现——面试题10二进制中1的个数

题目: 请实现一个函数,输入一个整数,输出该整数二进制表示中1的个数.例如,把9表示成二进制是1001,有2位是1,该函数输出2解法:把整数减一和原来的数做与运算,会把该整数二进制表示中的最低位的1变成0,与运算进行多少次就有多少个1. 1 package Solution; 2 /** 3 * 剑指offer面试题10:二进制中1的个数 4 * 题目:请实现一个函数,输入一个整数,输出该整数二进制表示中1的个数. 5 * 例如,把9表示成二进制是1001,有2位是1,该函数输出2 6 * 解法

剑指offer刷题—二维数组的查找

最近接触到一本书叫做剑指offer,在这里准备在这个2个月左右将这本书刷完,当然,不需要每天多少道什么的,不在多,一天理解一道就好了,希望能成为一种习惯,另外,准备在github上也进行同步分享. 今天第一道题: 面试题3:二位数组中的查找 当我们需要解决一个复杂问题时,一个很有效的方法就是从具体的问题出手,通过分析具体的例子,得到规律. 再一个二维数组中,每一行都要按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排列.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含

剑指Offer编程题(Java实现)——反转链表

题目描述 输入一个链表,反转链表后,输出新链表的表头. 思路一 使用头插法迭代进行反转 实现 /* public class ListNode { int val; ListNode next = null; ListNode(int val) { this.val = val; } }*/ public class Solution { public ListNode ReverseList(ListNode head) { ListNode newHead = new ListNode(-1