算法通关村第一关[白银挑战]-链表

算法通关村第一关[白银挑战]-链表

  • 1.两个链表第一个公共子节点(剑指Offer52)
    • 1.1 通过Hash辅助查找
    • 1.2 通过集合来辅助查找
    • 1.3 通过栈
    • 1.4 拼接两个字符串遍历
    • 1.5 差和双指针
  • 2.回文链表(LeetCode234)
    • 2.1 全部压栈
    • 2.2 只比较一半数据
    • 2.3 双指针+链表反转
  • 3.合并有序链表
    • 3.1 合并两个有序链表(LeetCode21)
      • 3.1.1 新建链表合并
      • 3.1.2 优化方法
    • 3.2 合并K个升序链表(LeetCode23)
    • 3.3 合并两个链表(LeetCode1669)
  • 4.双指针专题
    • 4.1寻找中间指针(LeetCode876)
    • 4.2 寻找倒数第K个元素(剑指Offer22)
    • 4.3 旋转链表(LeetCode61)
  • 5.删除链表元素专题
    • 5.1 移除链表元素(LeetCode203)
    • 5.2 删除链表的倒数第N个结点(LeetCode19)
      • 5.2.1 计算第N个结点位置
      • 5.2.2 双指针
      • 5.2.3 栈
    • 5.3 删除重复元素
      • 5.3.1 重复元素只保留一个(LeetCode83)
      • 5.3.2 重复元素都不要(LeetCode82)

1.两个链表第一个公共子节点(剑指Offer52)

算法通关村第一关[白银挑战]-链表_第1张图片

1.1 通过Hash辅助查找

    /**
     * 方法1:通过Hash辅助查找
     *
     * @param pHead1
     * @param pHead2
     * @return
     */
    public static ListNode findFirstCommonNodeByMap(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        ListNode current1 = pHead1;
        ListNode current2 = pHead2;
        //使用HashMap,结点作为key,value作为值
        HashMap<ListNode, Integer> map = new HashMap<ListNode, Integer>();
        while (current1 != null) {
            map.put(current1, null);
            current1 = current1.next;
        }
		//遍历另一个链表,判断每个结点是否有包含相同的结点
        while (current2 != null) {
            if (map.containsKey(current2))
                return current2;
            current2 = current2.next;
        }
        return null;
    }

1.2 通过集合来辅助查找

    /**
     * 方法2:通过集合来辅助查找
     *
     * @param headA
     * @param headB
     * @return
     */
    public static ListNode findFirstCommonNodeBySet(ListNode headA, ListNode headB) {
        if (headA==null||headB==null){
            return null;
        }
        //利用set集合遍历headA保存结点
        Set<ListNode> set = new HashSet<>();
        ListNode cur=headA;
        while (cur!=null){
            set.add(cur);
            cur=cur.next;
        }
        cur=headB;
        //遍历headB,判断是否有公共子节点
        while (cur!=null) {
            if (set.contains(cur)){
                return cur;
            }
            cur=cur.next;
        }
        return null;
        }

1.3 通过栈

   /**
     * 方法3:通过栈
     * 思路:因为栈是先进后出,但我们要拿到的是第一个公共子节点,所以要等两个栈弹出的元素不相同时,返回结果
     */
    public static ListNode findFirstCommonNodeByStack(ListNode headA, ListNode headB) {
        if (headA==null||headB==null){
            return null;
        }
        Stack<ListNode> stackA= new Stack<>();
        Stack<ListNode> stackB= new Stack<>();
        //将元素压入两个栈
        while (headA!=null){
            stackA.push(headA);
            headA=headA.next;
        }
        while (headB!=null){
            stackB.push(headB);
            headB=headB.next;
        }
        ListNode pNode= null;
        //当长度都大于0时,继续判断
        while (stackA.size()>0&&stackB.size()>0){
            //peek()函数返回栈顶的元素,但不弹出该栈顶元素。
            //pop()函数返回栈顶的元素,并且将该栈顶元素出栈。
            //如果弹出的元素相同,则为公共子节点,但因为栈是先进后出,所以要找到最后一个,即为第一个公共子节点
            //为什么这样写?因为链表的next只会指向一个元素,所以只会出现两个链表走着走着合并成一条链
            if (stackA.peek()==stackB.peek()){
                pNode=stackA.pop();
                stackB.pop();
            }else {
                break;
            }
        }
        return pNode;
        }

但是上述方法都要额外去创建栈或者集合,空间复杂度为O(N)

1.4 拼接两个字符串遍历

先看下面的链表A和B
A:0-1-2-3-4-5
B:a-b-4-5
如果分别拼接成AB和BA会怎么样呢?
AB: 0-1-2-3-4-5-a-b-4-5
BA: a-b-4-5-0-1-2-3-4-5
我们发现拼接后从最后的4开始,两人链表是一样的了,自然4就是要找的节点,所以可以通过拼接的方式来寻找交点。这么做的道理是什么呢?
我们可以从几何的角度来分析。我们假定A和B有相交的位置,以交点为中心,可以将两人链表分别分为left a和right a,left b和right b这样四个部分,并且right a和right b是一样的,这时候我们拼接AB和BA就是这样的结构:

算法通关村第一关[白银挑战]-链表_第2张图片
right a和right b是一样的,那这时候分别遍历AB和BA是不是从某个位置开始恰好就找到了相交的点了?
这里还可以进一步优化,如果建立新的链表太浪费空间了,我们只要在每人链表访问完了之后,调整到下链表的表头继续遍历就行了

    /**
     * 方法4:通过字符串拼接
     */
    public static ListNode findFirstCommonNodeByCombine(ListNode pHead1, ListNode pHead2) {
        if(pHead1==null||pHead2==null){
            return null;
        }
        ListNode p1=pHead1;
        ListNode p2=pHead2;
        while (p1!=p2){
            p1=p1.next;
            p2=p2.next;
            //如果序列不存在交集的时候陷入死循环,例如 list1是1 2 3,list2是4 5,
            // 这样的话遍历到结尾的时候p1和p2都为null,就会跳出循环
            if (p1!=p2){
                //一个链表访问完了跳到另一个链表继续访问,就达成了拼接的效果
                if (p1==null){
                    p1=pHead2;
                }
                if (p2==null){
                    p2=pHead1;
                }
            }
        }
        return p1;
    }

1.5 差和双指针

使用差和双指针来解决问题的方法。假如公共子节点一定存在第一轮遍历,假设La长度为L1,Lb长度为L2.则|L2-L1| 就是两个的差值。第二轮遍历,长的先走|L2-L1|,然后两个链表同时向前走,结点一样的时候就是公共结点了

    /**
     * 方法5:通过差值来实现
     *
     * @param pHead1
     * @param pHead2
     * @return
     */
    public static ListNode findFirstCommonNodeBySub(ListNode pHead1, ListNode pHead2) {
        if (pHead1 == null || pHead2 == null) {
            return null;
        }
        ListNode p1 = pHead1;
        ListNode p2 = pHead2;
        int len1 = 0;
        int len2 = 0;
        //获取长度
        while (p1 != null) {
            p1 = p1.next;
            len1++;
        }
        while (p2 != null) {
            p2 = p2.next;
            len2++;
        }
        ListNode fast = pHead1;
        ListNode slow = pHead2;
        //获取差值
        int cha = len1 > len2 ? len1 - len2 : len2 - len1;
        //使长的一方先走掉差值步,只是走掉差值,链表不会为空,无需判
        while (len1 > len2 && cha > 0) {
            fast = fast.next;
            cha--;
        }
        while (len1 < len2 && cha > 0) {
            slow = slow.next;
            cha--;
        }
        //同时遍历到结束
        while (fast != slow) {
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }

2.回文链表(LeetCode234)

算法通关村第一关[白银挑战]-链表_第3张图片

2.1 全部压栈

    /** 方法1.全部压栈
    思路:栈先进后出,所以就将链表中的元素倒转了,然后再一边出栈、一边遍历链表进行比较,一旦有不相同的就直接返回false
     * @param head
     * @return boolean
     */
    public static boolean isPalindrome(ListNode head) {
        if (head == null)
            return true;
        Stack<Integer> stack=new Stack<Integer>();
        ListNode cur=head;
        //将链表结点全部压入栈
        while(cur!=null){
            stack.push(cur.val);
            cur=cur.next;
        }
        //一边出栈,一边比较
        while(head!=null){
            if(stack.pop()!=head.val) {
                return false;
            }
            head = head.next;
        }
        return true;
    }

2.2 只比较一半数据

    /**
     * 方法2:只将一半的数据出栈
     * 思路:只要前一半和倒着的后一半相同,就代表是回文链表,只需要在压栈的时候顺带计算链表长度,然后长度减半比较即可
     *
     * @param head
     * @return
     */
    public static boolean isPalindromeByHalfStack(ListNode head) {
        if (head == null)
            return true;
        Stack<Integer> stack=new Stack<Integer>();
        ListNode cur=head;
        //将链表结点全部压入栈
        //计算链表的长度
        int len=0;
        while(cur!=null){
            stack.push(cur.val);
            cur=cur.next;
            len++;
        }
        //len长度除2
        len>>=1;
        //一边出栈,一边比较
        while(len-->0){
            if(stack.pop()!=head.val) {
                return false;
            }
            head = head.next;
        }
        return true;
    }

2.3 双指针+链表反转

    /**
     * 方法3:通过双指针+链表反转的方式来判断
     *
     * @param head
     * @return
     */
    public static boolean isPalindromeByTwoPoints(ListNode head) {
        if (head == null || head.next == null) {
            return true;
        }
        ListNode slow = head, fast = head;
        ListNode pre = head, prepre = null;
        //pre是反转存储了前半段倒置的链表
        while (fast != null && fast.next != null) {
            //存储slow遍历的每个结点,直到中间
            pre = slow;
            slow = slow.next;
            fast = fast.next.next;
            //指向前一个结点
            pre.next = prepre;
            //存储前一个结点
            prepre = pre;
        }
        //如果奇数情况,需要跳过中间元素
        if (fast != null) {
            slow = slow.next;
        }
        //遍历判断值是否相同
        while (pre != null && slow != null) {
            if (pre.val != slow.val) {
                return false;
            }
            pre = pre.next;
            slow = slow.next;
        }
        return true;
    }

3.合并有序链表

3.1 合并两个有序链表(LeetCode21)

算法通关村第一关[白银挑战]-链表_第4张图片

链表合并的思路:
1.新建一个链表,然后分别遍历两个链表,每次都选最小的结点接倒新链表上,直到结束
2.将一个链表结点拆下来,逐个合并倒另外一个对应位置上去

3.1.1 新建链表合并

    /**
     * 方法1:新建一个链表,然后分别遍历两个链表,每次都选最小的结点接倒新链表上,直到结束
     *
     * @param list1
     * @param list2
     * @return
     */
    public static ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        //创建一个新链表
        ListNode newHead = new ListNode(-1);
        //记录新链表的头结点,res.next为两个链表合并的头结点
        ListNode res = newHead;
        //遍历两个个链表,当两个链表都不为空时,有三种情况
        while (list1 != null && list2 != null) {
            //大于
            if (list1.val > list2.val) {
                newHead.next = list2;
                list2 = list2.next;
            //小于
            } else if (list1.val < list2.val) {
                newHead.next = list1;
                list1 = list1.next;
            //等于时,两个链表的结点都拼接到新链表
            } else if (list1.val == list2.val) {
                newHead.next = list2;
                list2 = list2.next;
                newHead = newHead.next;
                newHead.next = list1;
                list1 = list1.next;
            }
            //保持新链表遍历
            newHead = newHead.next;
        }
        //判断某个链表还没遍历结束的情况,将该链表所有结点拼接到新链表
        while (list1 != null) {
            newHead.next = list1;
            list1 = list1.next;
            newHead = newHead.next;
        }
        while (list2 != null) {
            newHead.next = list2;
            list2 = list2.next;
            newHead = newHead.next;
        }
        return res.next;
    }

3.1.2 优化方法

    /**
     * 方法2:优化1:方法1的大while有三种情况,可以合并成两个,当存在相同元素时,第一次出现使用
     * if(list1.val<=list2.val)处理,第二次使用else处理
     * 因为,当A和B两个链表结点相同时,新链表先接上A的结点,A链表结点向后走了,如果A链表结点元素大于B链表结点,就会走else,就节省了判断
     *优化2:当其中一个链表结束后,新链表可以直接拼接还有元素的链表,无需遍历
     * @param list1
     * @param list2
     * @return
     */
    public static ListNode mergeTwoListsMoreSimple(ListNode list1, ListNode list2) {
        ListNode newHead=new ListNode(-1);
        ListNode res=newHead;
        while(list1!=null&&list2!=null){
            if(list1.val<=list2.val){
                newHead.next=list1;
                list1=list1.next;
            }else{
                newHead.next=list2;
                list2=list2.next;
            }
            newHead=newHead.next;
        }
        newHead.next=list1==null?list2:list1;
        return res.next;
    }

3.2 合并K个升序链表(LeetCode23)

算法通关村第一关[白银挑战]-链表_第5张图片

    /**
     * 合并K个链表
     *
     * @param lists
     * @return
     */
    public ListNode mergeKLists(ListNode[] lists) {
    //只要合并两个写的出来,合并k个直接薄纱
        ListNode res=null;
        for(ListNode list:lists){
            res=mergeTwoLists(res,list);
        }
        return res;
    }

3.3 合并两个链表(LeetCode1669)

算法通关村第一关[白银挑战]-链表_第6张图片

这里需要留意题目中是否有开闭区间的情况,例如如果是从a到b,那就是闭区间[a,b]。还有的会说一人开区间(a,b),此时是不包括a和b两个元素,只需要处理a和b之间的元素就可以了。比较特殊的是进行分段处理的时候,例如K个一组处理,此时会用到左闭右开区间,也就是这样子[a,b),此时需要处理a,但是不用处理b,b是在下一个区间处理的。此类题目要非常小心左右边界的问题

    /**合并两个链表,[a,b]
     * @param list1 
     * @param a
     * @param b
     * @param list2
     * @return {@link ListNode}
     */
    public ListNode mergeInBetween(ListNode list1, int a, int b, ListNode list2) {
        ListNode nodeA=list1;
        ListNode nodeB=list1;
        for(int i=0;i<=b;i++){
            //找到a-1结点位置
            if(i<a-1){
                nodeA=nodeA.next;
            }
            //找到b+1结点位置
            nodeB=nodeB.next;
        }
        //拼接list2
        nodeA.next=list2;
        //遍历list2
        while(list2.next!=null){
            list2=list2.next;
        }
        //拼接后续
        list2.next=nodeB;
        return list1;
    }

4.双指针专题

4.1寻找中间指针(LeetCode876)

算法通关村第一关[白银挑战]-链表_第7张图片

用两个指针 slow 与 fast 一起遍历链表。slow 一次走一步fast 一次走两步。那么当 fast 到达链表的未尾时,slow必然位于中间。
这里还有个问题,就是偶数的时候该返回什么,例如上面示例2返回的是4,而3貌似也可以,那该使用哪个呢?如果我们使用标准的快慢指针就是后面的4,而在很多数组问题中会是前面的3,想一想为什么会这样。

	//利用快慢指针
    public ListNode middleNode(ListNode head) {
        ListNode fast=head;
        ListNode slow=head;
        while(fast!=null&&fast.next!=null){
        	//当偶数时,遍历到倒数第二个时,仍满足循环条件,只不过fast指针最后会为null
        	//但是中间指针会走到中间结点两个中的后一个
        	//而数组因为会导致数组越界异常,所以slow会是中间两个元素的前一个
            fast=fast.next.next;
            slow=slow.next;
        }
        return slow;
    }

4.2 寻找倒数第K个元素(剑指Offer22)

算法通关村第一关[白银挑战]-链表_第8张图片

    /** 快慢指针,fast指针与slow指针保持k的距离,当fast走完,slow指向的就是倒数第k个
     * @param head
     * @param k
     * @return {@link ListNode}
     */
    public ListNode getKthFromEnd(ListNode head, int k) {
        ListNode fast=head;
        ListNode slow=head;
        //遍历fast指针到k+1结点
        while(fast!=null&&k>0){
            fast=fast.next;
            k--;
        }
        //这时候slow指针没动,fast和slow相差k个结点
        while(fast!=null){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;
    }

4.3 旋转链表(LeetCode61)

算法通关村第一关[白银挑战]-链表_第9张图片

观察链表调整前后的结构,我们可以发现从旋转位置开始,链表被分成了两条,例如上面的(1,2,3)和(4.5,这里我们可以参考上一题的倒数K的思路,找到这个位置,然后将两个链表调整一下重新接起来就行了。
第一种是将整个链表反转变成(5,4,3,2,1},然后再将前K和N-K两个部分分别反转,也就是分别变成了14.5)和f1,2,3],这样就轻松解决了。通过链表反转
第二种思路就是先用双指针策略找到倒数K的位置,也就是1,2,3和4.5两个序列,之后再将两人链表拼接成(4.5,1,2,.3}就行了。

具体思路:
因为k有可能大于链表长度,所以首先获取一下链表长度len,如果然后k=k % len,如果k ==0,则不用旋转,直接返回头结点。否则:
1.快指针先走k步
2.慢指针和快指针一起走
3.快指针走到链表尾部时,慢指针所在位置刚好是要断开的地方。把快指针指向的节点连到原链表头部,慢指针指向的节点断开和下一节点的联系。
4.返回结束时慢指针指向节点的下一节点。

    /**依旧使用快慢指针,通过保持fast指针和slow指针k个结点距离,使fast指针移到末尾,让slow移动到倒数第k个结点位置
     * @param head 头结点
     * @param k 移动k个位置
     * @return {@link ListNode}
     */
    public ListNode rotateRight(ListNode head, int k) {
        if(head==null||k==0){
            return head;
        }
        ListNode fast=head;
        ListNode slow=head;
        ListNode temp=head;
        int len=0;
        //统计链表的元素个数
        while(head!=null){
            head=head.next;
            len++;
        }
        if(k%len==0){
            return temp;
        }
        //从这里开始fast从头结点向后走
        //这里使用取模,是为了防止k大于len的情况
        //例如,如果len=5,那么k=2和7,效果是一样的
        while((k%len)>0){
            k--;
            fast=fast.next;
        }
        //快指针走了k步,然后快慢指针一起向后走
        //当fast到尾结点时,slow刚好在倒数第k个位置上
        while(fast.next!=null){
            fast=fast.next;
            slow=slow.next;
        }
        //得到k个要移动的后一段
        ListNode res=slow.next;
        //将链表中某一结点的地址值赋值为空,会将原先的链表拆成两份
        slow.next=null;
        //因为fast已经在尾巴了,将前一段拼接,这时候的temp虽然是头结点,但是它只是原先链表的前一段
        fast.next=temp;
        return res;
    }

事实上,不用这么麻烦,直接暴力点,假如我们要找倒数第K个,那就是要找正数第Len-k+1个。因此可以先遍历遍,计算出LEN,然后直接通过计算得到需要走的步数,只会从头开始遍历,到第Len-k+1即可。当然也要通过取模来计算防止K越界。

5.删除链表元素专题

5.1 移除链表元素(LeetCode203)

删除节点cur时,必须知道其前驱pre节点和后继next节点,然后让pre.next=next。这时候cur就脱离链表了,cur节点会在某个时刻被gc回收掉。
对于删除,注意首元素的处理方式与后面的不一样。为此,可以先创建一个虚拟节点dummyHead,使其指向head,也就是dummyHead.next=head,这样就不用单独外理首节点了完整的步骤是:
1.我们创建一个虚拟链表头dummyHead,使其next指向head
2.开始循环链表寻找目标元素,注意这里是通过curnext.val来判断的
3.如果找到目标元素,就使用cur.next = cur.next.next;来删除.
4.注意最后返回的时候要用dummyHead.next,而不是dummyHead

算法通关村第一关[白银挑战]-链表_第10张图片

    /**关键:整一个dummyHead指向head,使头结点可以一起处理
     * @param head 
     * @param val 
     * @return {@link ListNode}
     */
    public ListNode removeElements(ListNode head, int val) {
        if(head==null){
            return head;
        }
        ListNode dummyHead=new ListNode(-1);
        dummyHead.next=head;
        ListNode cur=dummyHead;
        while(cur.next!=null){
            if(cur.next.val==val){
                cur.next=cur.next.next;
            }else{
                cur=cur.next;
            }
        }
        return dummyHead.next;
    }

5.2 删除链表的倒数第N个结点(LeetCode19)

算法通关村第一关[白银挑战]-链表_第11张图片

5.2.1 计算第N个结点位置

    /**方法1.倒数第n个结点=len+1-n
     * @param head
     * @param n
     * @return {@link ListNode}
     */
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if(head==null){
            return null;
        }
        ListNode dummyHead=new ListNode(0,head);
        int len=0;
        while(head!=null){
            head=head.next;
            len++;
        }
        ListNode cur=dummyHead;
        for(int i=1;i<len-n+1;i++){
            cur=cur.next;
        }
        cur.next=cur.next.next;
        return dummyHead.next;
    }

5.2.2 双指针

	//通过快慢指针
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if(head==null){
            return null;
        }
        ListNode dummyHead=new ListNode(0,head);
        ListNode fast=head;
        //这里慢指针是指向虚拟结点,因为可能会出现删除第一个结点
        //如果指向head,会导致slow.next空指针
        ListNode slow=dummyHead;
        while(fast!=null&&n>0){
            fast=fast.next;
            n--;
        }
        while(fast!=null){
            fast=fast.next;
            slow=slow.next;
        }
        slow.next=slow.next.next;
        return dummyHead.next;
    }

5.2.3 栈

    /**通过栈反向遍历得到结点,不过空间复杂度O(N),还要考虑链表和栈的关系
     * @param head
     * @param n
     * @return {@link ListNode}
     */
    public ListNode removeNthFromEnd(ListNode head, int n) {
        if(head==null){
            return null;
        }
        ListNode dummyHead=new ListNode(0,head);
        Stack<ListNode> stack=new Stack<>();
        //可能删除第一个结点,所以拿虚拟结点开始压栈
        ListNode cur=dummyHead;
        while(cur!=null){
            stack.push(cur);
            cur=cur.next;
        }
        //弹出倒数第k个
        for(int i=0;i<n;i++){
            stack.pop();
        }
        //这里拿到的就说第k个结点的前一个
        ListNode pre=stack.peek();
        pre.next=pre.next.next;
        return dummyHead.next;
    }

5.3 删除重复元素

5.3.1 重复元素只保留一个(LeetCode83)

算法通关村第一关[白银挑战]-链表_第12张图片

由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的,因此只需要对链表进行次遍历,就可以删除重复的元素。
思路:我们从指针 cur 指向链表的头节点,随后开始对链表进行遍历。如果当前 cur 与curnext 对应的元素相同,那么我们就将curnext 从链表中移除,否则说明链表中已经不存在其它与cur 对应的元素相同的节点,因此可以将 cur 指向 curnext。当遍历完整链表之后,我们返回链表的头节点即可。
另外要注意的是 当我们遍历到链表的最后一个节点时,cur next 为空节点,此时要加以判断

    /**
     * @param head
     * @return {@link ListNode}
     */
    public ListNode deleteDuplicates(ListNode head) {
        if(head==null){
            return head;
        }
        ListNode dummy=new ListNode(0,head);
        ListNode cur=dummy.next;
        while(cur.next!=null){
            if(cur.val==cur.next.val){
                cur.next=cur.next.next;
            }else{
                cur=cur.next;
            }
        }
        return dummy.next;
    }

5.3.2 重复元素都不要(LeetCode82)

算法通关村第一关[白银挑战]-链表_第13张图片

当一个都不要时,链表只要直接对curnext 以及 cur.next.next两个node进行比较就行了,这里要注意两个node可能为空,稍加判断就行了。

    /** cur.next和cur.next.next比较,并判断它们的值是否相同,如果相同的话,记录值
    并且内部进行遍历判断。如果cur.next不为空且值相同,则cur.next=cur.next.next;
     * @param head
     * @return {@link ListNode}
     */
    public ListNode deleteDuplicates(ListNode head) {
        ListNode dummy=new ListNode(0,head);
        ListNode cur=dummy;
        while(cur.next!=null&&cur.next.next!=null){
            if(cur.next.val==cur.next.next.val){
                int temp=cur.next.val;
                //判断重复元素,当值不同时跳出循环
                while(cur.next!=null&&cur.next.val==temp){
                    cur.next=cur.next.next;
                }
            }else{
                cur=cur.next;
            }
        }
        return dummy.next;
    }

你可能感兴趣的:(算法通关村,算法,链表,数据结构)