leetcode_632_最小区间_双指针解法

题目:

你有 k 个升序排列的整数数组。找到一个最小区间,使得 k 个列表中的每个列表至少有一个数包含在其中。

我们定义如果 b-a < d-c 或者在 b-a == d-c 时 a < c,则区间 [a,b] 比 [c,d] 小。

示例 1:

输入:[[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]
输出: [20,24]
解释: 
列表 1:[4, 10, 15, 24, 26],24 在区间 [20,24] 中。
列表 2:[0, 9, 12, 20],20 在区间 [20,24] 中。
列表 3:[5, 18, 22, 30],22 在区间 [20,24] 中。
注意:

给定的列表可能包含重复元素,所以在这里升序表示 >= 。
1 <= k <= 3500
-105 <= 元素的值 <= 105
对于使用Java的用户,请注意传入类型已修改为List<List<Integer>>。重置代码模板后可以看到这项改动。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/smallest-range-covering-elements-from-k-lists
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。


思路:

这道题虽然是hard型,但是思路比较简单:“双”指针。之所以“双”要加引号呢,是因为这里的思路是双指针解决问题的思路,而不是真正的只使用两个指针来解决,因为这里的nums列表内部大概率不止两个内部列表。。。

好了,接下来 方法驱动 解决问题。

策略:

①定义:

k = nums.size()。 即k代表升序列表的个数。

p[k]:初始时指向第k个列表的尾部。在遍历的过程中向前移动(如果不知道为什么可以看看双指针解决问题时的做法)。

tleft:当前的区间左端点。

tright:当前的区间的右端点。

即[tleft,tright]。

[left,right]:最小的包含所有列表至少一个值的区间。

至于怎么找到这个tleft以及tright,则只需要遍历一次所有的列表即可:

for(int i = 1;i <= k;i++){
            if(p[i] == -1) continue;
            int tem = nums.get(i-1).get(p[i]);
            if(tem > max){
                maxindex = i;
                max = tem;
            }
            if(tem < min){
                min = tem;
                minindex = i;
            }
        }

找到之后,判断:

if(tright-tleft<right-left || (tright-tleft==right-left && tleft<left)){
                System.out.println("["+left+","+right+"] replaced by ["+tleft+","+tright+"]");
                left = tleft;
                right = tright;

            }

此时的[left,right]即更新为最新的符合条件的区间。

2)最后,只需要再更新与tright对应的列表 i 的尾指针p[i]即可。

这里需要注意:因为一个列表中会有重复的数,所以一定要在更新尾指针的时候去重!!!这个非常重要,否则会超时。。。:

do{
//                System.out.println("重复值...");
                p[pc[1]]--;
            }while(p[pc[1]]-1>=0 && nums.get(pc[1]-1).get(p[pc[1]])==nums.get(pc[1]-1).get(p[pc[1]]-1));

3)判断是否需要终止。

很显然,当有一个p[i] == -1即列表i已经被排除在外时,此时如果再循环没有意义了,因为不管怎么样,得到的[tleft,tright]都必定不可能将列表i包括在内至少一个值,因为列表i中的值必定比tright的值大(很明显,可以好好想一想,因为更新的时候都是只更新区间右端点对应的列表的尾指针)。

所以终止条件为:

 //判断是否全部查找过
    public boolean isOk(int []p){
        int k = p.length;

        for(int i = 1;i < k;i++){  //第i个列表
            if(p[i] == -1) return true;
        }
        return false;
    }

实现:

java版:

package leetcode;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/*
USER:LQY
DATE:2020/8/1
TIME:10:22
*/
public class leetcode_632 {

    public static void main(String []args){
        List<List<Integer>> nums = new ArrayList<>();
        nums.add(Arrays.asList(-5, -4, -3, -2, -1, 1));
        nums.add(Arrays.asList(1, 2, 3, 4, 5));
//        nums.add(Arrays.asList(5,18,22,30));
        new leetcode_632().smallestRange(nums);
    }
    public int[] smallestRange(List<List<Integer>> nums) {

        int k = nums.size();
        int []ans = new int[2];
        int left = -10000;
        int right = 100000;
        //定义k个指针
        int []p = new int[k+1];
        for(int i = 1;i <= k;i++){
            p[i] = nums.get(i-1).size() - 1;
        }
//        System.out.println(Arrays.toString(findMaxAndMinK(nums, p)));
        //遍历
        while(!isOk(p)){
            int []pc = findMaxAndMinK(nums, p);
//            if(pc[0] == pc[1]) break;
            int tleft = nums.get(pc[0]-1).get(p[pc[0]]);
            int tright = nums.get(pc[1]-1).get(p[pc[1]]);
            System.out.println("the min at "+pc[0]+" and the v is "+tleft+"\nthe max at "+pc[1]+" and the v is "+tright);
            if(tright-tleft<right-left || (tright-tleft==right-left && tleft<left)){
                System.out.println("["+left+","+right+"] replaced by ["+tleft+","+tright+"]");
                left = tleft;
                right = tright;

            }
            do{
//                System.out.println("重复值...");
                p[pc[1]]--;
            }while(p[pc[1]]-1>=0 && nums.get(pc[1]-1).get(p[pc[1]])==nums.get(pc[1]-1).get(p[pc[1]]-1));
        }

        for(int i : p)
            System.out.print(i+" ");

        return ans;
    }
    //判断是否全部查找过
    public boolean isOk(int []p){
        int k = p.length;

        for(int i = 1;i < k;i++){  //第i个列表
            if(p[i] == -1) return true;
        }
        return false;
    }
//    找出指向的数最小以及最大的那个指针 从1开始
    public int[] findMaxAndMinK(List<List<Integer>> nums, int[]p){
        int maxindex = 0;
        int max = 0;
        int min = Integer.MAX_VALUE;
        int minindex = -1;

        int k = nums.size();
        for(int i = 1;i <= k;i++){
            if(p[i] == -1) continue;
            int tem = nums.get(i-1).get(p[i]);
            if(tem > max){
                maxindex = i;
                max = tem;
            }
            if(tem < min){
                min = tem;
                minindex = i;
            }
        }

        return new int[]{minindex ,maxindex};
    }

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值