力扣:632. 最小区间(贪心)

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

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

示例 1:

输入:nums = [[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] 中。

示例 2:

输入:nums = [[1,2,3],[1,2,3],[1,2,3]] 输出:[1,1]

提示:

  • nums.length == k
  • 1 <= k <= 3500
  • 1 <= nums[i].length <= 50
  • -105 <= nums[i][j] <= 105
  • nums[i] 按非递减顺序排列)

题目要求,给出若干个非递减序列,要算出一个区间[l,r],使得每个序列至少有一个数在区间内。

思路:

        既然题目要求每个序列中至少有一个数在区间内,那么索性先选取各个序列中最小的数也就是各个序列的首元素构成一个集合,此时,集合内的数就会生成一个区间 [l1,r1],l1就是所有序列首元素的最小值,r1就是首元素的最大值。这时候的区间一定满足 “每个序列至少有一个数在区间内”的条件,但未必是最小的,之后就要压缩区间。

        在压缩区间之前必须明确,在[l1,r1]与初始集合的基础上进行压缩,集合内必须保证含有每个序列中的数。而怎么压缩区间就体现了贪心的思想,每次压缩都从集合中弹出最小的数,之后,为了满足“每个序列至少有一个数在区间内”的条件,需要在其所属序列中选择下一个数加入集合,获取新集合的区间对比更新所求区间。

例如:当前从集合中弹出的元素是nums[i][j],那么就需要将nums[i][j+1]加入集合,假设弹出前集合的区间为[l1,r1],l1为集合中最小元素,r1为集合中最大元素,[l1,r1]为当前压缩到的最小区间,即ans区间;加入元素后的新集合对应的新区间为[l2,r2],此时就要将ans与新区间进行对比,如果新区间小于ans,就更新ans为新区间,否则ans不动。

解释:为什么每次弹出集合中最小的数,加入其序列中的下一个数?

答:因为题目保证所有序列都为非递减序列,则每次弹出集合中最小的数(也就是l1),并加入其原序列的下一个数x,那么x一定大于l1。如果,此时x为新集合中最小的数,那么新集合的新区间为[x,r1]的长度一定小于原区间长度,又因为x是从l1的序列中加入集合的,说明弹出、加入的过程不会影响到集合中其他序列的数,也就是一定不会破坏“每个序列至少有一个数在区间内”的条件,所以[x,r1]一定是当前满足题意的最小区间,随之更新ans,从而达到压缩区间的目的;如果此时x不为新集合中最小的数,最小的数为l2,x可能大于r1,也可能在[l2,r1]之间,此时就要对比新集合的新区间与ans的大小,判断是否更新ans同样可以压缩区间。即使某个序列中有多个数在区间也无所谓,只要满足“每个序列至少有一个数在区间内”的条件即可。

        至此,不断弹出,加入元素,保证集合内元素个数不变,不断在贪心的基础上压缩,直到集合不可再压缩,返回ans区间。

AC代码:

C++:

//力扣:632. 最小区间 class Solution { public: typedef struct node{ int data;//当前元素数据 int i;//原序列位置 int idx;//所在原序列下标 bool operator < (const node& a)const { if (data == a.data) { return i < a.i; } return data < a.data; } }Node; vector<int> smallestRange(vector<vector<int>>& nums) { vector<int> ans; if (nums.empty()) { return ans; } set<Node> s; for (int i = 0; i < nums.size(); i++) { //初始化集合,将每个序列最小数加入集合 if (nums[i].empty()) { continue; } Node t = { nums[i][0],i,0 }; s.insert(t); } if (s.empty()) { return ans; } int l = (*s.begin()).data; int r = (*s.rbegin()).data; //压缩区间 while (1) { auto it = s.begin(); int i = (*it).i; int idx = (*it).idx; if (idx == nums[i].size() - 1) { //最小元素弹出后无法再加入元素 break;//跳出循环,压缩结束 } else { s.erase(s.begin()); Node t = { nums[i][idx + 1],i,idx + 1 }; s.insert(t); if (((*s.rbegin()).data - (*s.begin()).data) < (r - l)) { //遇到更小的区间,更新 r = (*s.rbegin()).data; l = (*s.begin()).data; } else if (((*s.rbegin()).data - (*s.begin()).data) == (r - l)) { //同样长度的区间选择字典序更小的,更新 if ((*s.begin()).data < l) { r = (*s.rbegin()).data; l = (*s.begin()).data; } } } } ans.push_back(l); ans.push_back(r); return ans; } };

java:

class Solution { public class Node implements Comparable <Node> { int data; int i; int idx; public Node(int data, int i, int idx) { this.data = data; this.i = i; this.idx = idx; } public int compareTo(Node a){ if(this.data==a.data){ return this.i-a.i; } return this.data-a.data; } } public int[] smallestRange(List<List<Integer>> nums) { int ans[]=new int[2]; if(nums.size()==0){ return ans; } TreeSet<Node> s=new TreeSet<>(); for (int i = 0; i < nums.size(); i++) { if(nums.get(i).size()==0){ continue; } Node t=new Node(nums.get(i).get(0),i,0); s.add(t); } if(s.size()==0){ return ans; } int l=s.first().data; int r=s.last().data; while(true){ Node it=s.first(); int i=it.i; int idx=it.idx; if(idx==nums.get(i).size()-1){ break; } else{ s.remove(s.first()); Node t=new Node(nums.get(i).get(idx+1),i,idx+1); s.add(t); int len=s.last().data-s.first().data+1; if(len<(r-l+1)){ l=s.first().data; r=s.last().data; } else if(len==(r-l+1)){ if(l>s.getFirst().data){ l=s.first().data; r=s.last().data; } } } } ans[0]=l; ans[1]=r; return ans; } }

Read more

优选算法——滑动窗口2

优选算法——滑动窗口2

优选算法——滑动窗口 1.1004. 最大连续1的个数 III 题目描述 思路分析 这道题的核心是:找一个最长的子数组,其中最多包含 k 个 0。 经典的 滑动窗口 问题。 为什么用滑动窗口? * 我们需要连续区间 → 滑动窗口天然适合 * 窗口内维护「0 的个数 ≤ k」这个约束 * 窗口扩张:右指针右移,遇到 0 就计数 * 窗口收缩:当 0 的个数超过 k,左指针右移直到满足条件 算法流程 1. 初始化:left = 0, zeroCount = 0, maxLen = 0 2. 遍历数组,right 指针右移: -

By Ne0inhk
【数据结构】排序详解:从快速排序分区逻辑,到携手冒泡排序的算法效率深度评测

【数据结构】排序详解:从快速排序分区逻辑,到携手冒泡排序的算法效率深度评测

🔥@晨非辰Tong: 个人主页 👀专栏:《C语言》、《数据结构与算法入门指南》 💪学习阶段:C语言、数据结构与算法初学者 ⏳“人理解迭代,神理解递归。” 文章目录 * 引言 * 一、介绍交换排序 * 二、高效交换--快速排序“:递归版 * 2.1 介绍:创造背景以及基本思想 * 2.2 基于二叉树结构的主体框架 * 三、找基准值key的三种==递归版==实战方法 * 3.1 快排核心构成:寻找key的算法之"hoare"版本 * 3.3.1 画图理解算法 * 3.3.2 代码实战 * 3.1.3 ==**代码分析**== * 3.2

By Ne0inhk
动态规划 线性 DP 五大经典模型:LIS、LCS、合唱队形、编辑距离 详解与模板

动态规划 线性 DP 五大经典模型:LIS、LCS、合唱队形、编辑距离 详解与模板

文章目录 * 最长上升子序列 * 【模板】最长上升子序列 * 合唱队形 * 牛可乐和最长公共子序列 * 编辑距离 经典线性 dp 问题有两个:最⻓上升⼦序列(简称:LIS)以及最⻓公共⼦序列(简称:LCS),这两道题⽬的很多⽅⾯都是可以作为经验,运⽤到别的题⽬中。⽐如:解题思路,定义状态表⽰的⽅式,推到状态转移⽅程的技巧等等。 因此,这两道经典问题是需要我们重点掌握的。 最长上升子序列 题目描述 题目解析 本题介绍最长上升子序列的一般解法,当数据量不大时用这种解法。 在此之前,小编先区分一下子数组和子序列,子数组需要是连续的,而子序列可以是间断的。 1、状态表示 dp[i]表示以i结尾的所有子序列中,最长的上升子序列。

By Ne0inhk
【鼠鼠优选算法-双指针】001:移动零 & 002:复写零

【鼠鼠优选算法-双指针】001:移动零 & 002:复写零

🎈主页传送门:良木生香 🔥个人专栏:《C语言》 《数据结构-初阶》  🌟人为善,福随未至,祸已远行;人为恶,祸虽未至,福已远离 在学习了这么多基础知识之后,我们就从今天开始操练一下我们的基本技能吧,先来两道简单的题目试试手: 1.移动零:题目链接~~~ 2.复写零:复写零 那我们就一题一题来讲讲吧~~~ 一、移动零 题目描述: 看到题目,这道题是想让我们将一个数组中的所有0移动到数组的末尾. 题目意思明了,但是我们该怎么操作呢? 在这道题中我们第一个想到的就是重新创建新的数组,将数值不为0的元素移动到新的数组中,但是题目明确要求说了,只能再原地进操作,我们该怎么实现这个操作呢?又不能创建新的数组不急,我有妙招. 原理解析: 在这道题目中,我们可以用两个指针,current和dentist,一个用来遍历整个数组,另一个用来处理当下的数据 当cur遍历到值为0的元素时,就与dest交换,随后两者同时向后移动一步 但是不管cur碰到的元素是否等于0,都会向后移动一步   代码实现: 下面是用C语言实现的代码: void Swap(

By Ne0inhk