题目:
你有 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>。重置代码模板后可以看到这项改动。
来源:力扣(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
此时的[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> 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> 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=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> 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};
}
}