双指针的应用场景

双指针指的是在遍历对象的过程中,不是普通的使用单个指针进行访问,而是使用两个指针(特殊情况甚至可以多个),两个指针或是同方向访问两个链表、或是同方向访问一个链表(快慢指针)、或是相反方向扫描(对撞指针),从而达到我们需要的目的。

首先我们先来看一个简单的算法应用题,以下例题皆来自牛客网牛客网在线编程_算法篇_面试必刷TOP101 (nowcoder.com)

合并K个排序的链表

描述

合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。

数据范围:节点总数 0≤𝑛≤50000≤n≤5000,每个节点的val满足 ∣𝑣𝑎𝑙∣<=1000∣val∣<=1000

要求:时间复杂度 𝑂(𝑛𝑙𝑜𝑔𝑛)

解题思路:

如果是两个有序链表合并,我们可能会利用归并排序合并阶段的思想:准备双指针分别放在两个链表头,每次取出较小的一个元素加入新的大链表,将其指针后移,继续比较,这样我们出去的都是最小的元素,自然就完成了排序。

这道题我们同样可以采取这种思想,先取出最前面的两个链表进行合并,在与后面的进行合并,如此循环下去便可得到一个最终的链表,但这样比较费时。

既然都是归并排序的思想了,那我们可不可以直接归并的分治来做,而不是顺序遍历合并链表呢?答案是可以的!

归并排序是什么?简单来说就是将一个数组每次划分成等长的两部分,对两部分进行排序即是子问题。对子问题继续划分,直到子问题只有1个元素。还原的时候呢,将每个子问题和它相邻的另一个子问题利用上述双指针的方式,1个与1个合并成2个,2个与2个合并成4个,因为这每个单独的子问题合并好的都是有序的,直到合并成原本长度的数组。

对于这k个链表,就相当于上述合并阶段的k个子问题,需要划分为链表数量更少的子问题,直到每一组合并时是两两合并,然后继续往上合并,这个过程基于递归:

  • 终止条件: 划分的时候直到左右区间相等或左边大于右边。
  • 返回值: 每级返回已经合并好的子问题链表。
  • 本级任务: 对半划分,将划分后的子问题合并成新的链表。

具体做法:

  • step 1:从链表数组的首和尾开始,每次划分从中间开始划分,划分成两半,得到左边𝑛/2n/2个链表和右边𝑛/2n/2个链表。
  • step 2:继续不断递归划分,直到每部分链表数为1.
  • step 3:将划分好的相邻两部分链表,按照两个有序链表合并的方式合并,合并好的两部分继续往上合并,直到最终合并成一个链表。

import java.util.*;

/*
 * public class ListNode {
 *   int val;
 *   ListNode next = null;
 *   public ListNode(int val) {
 *     this.val = val;
 *   }
 * }
 */

public class Solution {
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 
     * @param lists ListNode类ArrayList 
     * @return ListNode类
     */
    public ListNode mergeKLists (ArrayList<ListNode> lists) {
        // write code here
        return divide(lists,0,lists.size()-1);
    }
    public ListNode Merge(ListNode head1,ListNode head2){
         //加一个表头
        ListNode head = new ListNode(-1);
        ListNode cur = head;
         //两个链表都要不为空
        while(head1!=null&&head2!=null){
             //取较小值的节点
            if(head1.val<head2.val){
                cur.next=head1;
                 //只移动取值的指针
                head1=head1.next;
            }else{
                cur.next=head2;
                 //只移动取值的指针
                head2=head2.next;
            }
            cur=cur.next;
        }
         //哪个链表还有剩,直接连在后面
        if(head1!=null){
            cur.next=head1;
        }else{
            cur.next=head2;
        }
        //返回值去掉表头
        return head.next;
    }
    //划分合并区间函数
    public ListNode divide(ArrayList<ListNode> lists,int left,int right){
        if(left>right){
            return null;
             //中间一个的情况
        }else if(left==right){
            return lists.get(left);
        }
         //从中间分成两段,再将合并好的两段合并
        int mid = (left+right)/2;
        return Merge(divide(lists,left,mid),divide(lists,mid+1,right));
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值