659.输入一个按升序排序的整数数组(可能包含重复数字),你需要将它们分割成几个子序列,其中每个子序列至少包含三个连续整数。返回你是否能做出这样的分割?
示例 1:
输入: [1,2,3,3,4,5]
输出: True
解释:
你可以分割出这样两个连续子序列 :
1, 2, 3
3, 4, 5
解题思路:
贪婪解法:第一个map用来建立数字和其出现次数之间的映射fre,第二个用来建立可以加在某个连续子序列后的数字及其可以出现的次数之间的映射seq。
对于第二个map,举个例子来说,就是假如有个连,[1,2,3],那么后面可以加上4,所以就建立4的映射。这样我们首先遍历一遍数组,统计每个数字出现的频率,然后我们开始遍历数组,对于每个遍历到的数字,首先看其当前出现的次数,如果为0,则继续循环;如果need中存在这个数字的非0映射,那么表示当前的数字可以加到某个连的末尾,我们将当前数字的映射值自减1,然后将下一个连续数字的映射值加1,因为当[1,2,3]连上4后变成[1,2,3,4]之后,就可以连上5了;如果不能连到其他子序列后面,我们来看其是否可以成为新的子序列的起点,可以通过看后面两个数字的映射值是否大于0,都大于0的话,说明可以组成3连儿,于是将后面两个数字的映射值都自减1,还有由于组成了3连儿,在need中将末尾的下一位数字的映射值自增1;如果上面情况都不满足,说明该数字是单牌,只能划单儿,直接返回false。
代码:
class Solution {
public:
bool isPossible(vector<int>& nums) {
map<int,int>fre,seq;
for(int &num:nums)
fre[num]++;
for(int &num:nums)
{
if(fre[num]<=0)
continue;
// 如果 fre[num] <= 0,说明在上一轮循环中新建了
// 一个包含当前数字 num 的连续序列,直接跳过扫描下一个数字
fre[num]--;
if(seq[num-1]>0)
{
seq[num-1]--;
seq[num]++;
}
else if(fre[num+1]&&fre[num+2])
{
fre[num+1]--;
fre[num+2]--;
seq[num+2]++;
}// 新建连续序列,要把包含进新序列的数字的出现次数也减 1
else
{
return false;
}
}
return true;
}
};
设计一个找到数据流中第K大元素的类(class)。注意是排序后的第K大元素,不是第K个不同的元素。
你的 KthLargest
类需要一个同时接收整数 k
和整数数组nums
的构造器,它包含数据流中的初始元素。每次调用 KthLargest.add
,返回当前数据流中第K大的元素。
示例:
int k = 3;
int[] arr = [4,5,8,2];
KthLargest kthLargest = new KthLargest(3, arr);
kthLargest.add(3); // returns 4
kthLargest.add(5); // returns 5
kthLargest.add(10); // returns 5
kthLargest.add(9); // returns 8
kthLargest.add(4); // returns 8
说明:
你可以假设 nums
的长度≥ k-1
且k
≥ 1。
解题思路:
最简单的想法是直接进行排序,算法复杂度是O(N*logN)。这么做很明显比较低效率,因为不要求别的信息只要计算出第K大的元素。当然,如果在某种情况下需要频繁访问第K大的元素就可以先进行一次排序在直接得出结果。
第一种方式是这样,用选择排序,冒泡法,或者交换排序这类的排序,对前K个元素进行排序。这三种算法也许不是最快的排序算法。但是都有个性质:计算出最大(小)的元素的算法复杂度是O(N)。这个过程不能中断,要计算第三大的元素必须建立在已经算出第二大的元素的基础上(因为每次都是计算当前数组最大)。所以它的算法复杂度是O(N*K);
第二种方法是用快速排序的思想。快速排序每次把一个元素交换到正确的位置,同时把左边的都方上大的,右边都放上小的。这个算法每一次选取一个枢纽元,排序之后,查看枢纽元的位置。如果它的位置大于K,就说明,要求出前面一个子序列的第K大的元素。反之,如果小于K,就说明要求出在后面一个序列的第K - 前一个序列的长度个元素。
如此,就把这个问题改变成了一个可以用快排思想解决的问题。对于快速排序,算法复杂度是O(N*logN)。而这个算法的算法复杂度是O(N)。为什么呢?
其实这个地方的算法复杂度分析很有意思。第一次交换,算法复杂度为O(N),接下来的过程和快速排序不同,快速排序是要继续处理两边的数据,再合并,合并操作的算法复杂度是O(1),于是总的算法复杂度是O(N*logN)(可以这么理解,每次交换用了N,一共logN次)。但是这里在确定枢纽元的相对位置(在K的左边或者右边)之后不用再对剩下的一半进行处理。也就是说第二次插入的算法复杂度不再是O(N)而是O(N/2),这不还是一样吗?其实不一样,因为接下来的过程是1+1/2+1/4+........ < 2,换句话说就是一共是O(2N)的算法复杂度也就是O(N)的算法复杂度。
这个算法目前我在数据结构和算法书上和剑指Offer上都看到过。算是一种很经典很经典的算法。原因是因为他通过努力把算法复杂度在每次递归中下降一些,最终让整个算法的复杂度下降极多,算是一种十分聪明的做法。
第三种方法很是简单,但是使用它需要某个条件,也就是输入数组的取值范围很小,最好的情况是能形成完全分布,也就是1000大小的数组里面的数字是从1到1000这样子。首先,生成一个能够完全装下原数组的数组,这个地方的装下是指数组大小等于原数组最大元素(也许还有优化,但这么描述简单一点),比如原数组是[1,2,3,4,5],我要生成的数组大小是5,如果原数组是[5,3,6,10],我要生成的数组大小是10。接下来遍历原数组,把每一个元素放到第二个数组对应的下标处,5就放在下标为5的地方(实际过程中要减1,因为是数组从0开始)。放的过程中增加元素值用来统计这个元素出现的次数。这一过程算法复杂度是O(N)。接下来,再遍历生成的数组,找出第K大的元素。
这个过程的算法复杂度是多少呢?其实这个和原数组很有关系,原数组越离散也就越糟糕。比如原数组是[1,1000],这样就十分糟糕。第二部的算法复杂度是O(M),M是前数组的最大值。总的算法复杂度O(N)+O(M);
由此可见第三种方法在这个问题的处理非常不好。虽然第三种方法限制颇多(浮点型和负数还有对原数组大小的要求),但是第三种方法的实质是一种散列。就是把原来的映射关系变成了一种反映射。也就是说如果形成了数据与地址的直接映射。但是这种映射的问题也体现的很明显,它这么做也只能算是捡了个漏子,如果输入数组稍微一边,还是一样要用hash算法计算其hash值。再把hash值映射到地址上。
第四种方法是用二叉堆来做。对大小为N的数组构建二叉堆的算法复杂度是O(N)。然后每次下滤的算法复杂度是O(logN),一共下滤K次,算法复杂度是O(N+K*logN)。
这种做法比较适合用来处理输入数组极大的情况,原因是如果输入数组大到不能放入内存,那么构建二叉堆(优先队列)的时候就可以只构造一个K个元素的优先队列。如果下一个元素比这个最小堆的堆顶还小就直接pass。第二个原因是算法二在对付一个极大的输入队列的时候算法复杂度的一个常数会很大。
代码:
class KthLargest {
public:
KthLargest(int k, vector<int> nums) {
//限制堆的大小为k
size = k;
for (int i = 0; i < nums.size(); i++){
heap.push(nums[i]);
//堆大小超过k,pop出一个元素
if (heap.size() > k) heap.pop();
}
}
//操作原理同上
int add(int val) {
heap.push(val);
if (heap.size() > size) heap.pop();
return heap.top(); }
private:
//升序队列(小根堆),STL中存在的一种优先队列,本质用堆实现的
priority_queue<int, vector<int>, greater<int>> heap;
int size;
};
743、网络延迟的时间
有 N
个网络节点,标记为 1
到 N
。
给定一个列表 times
,表示信号经过有向边的传递时间。 times[i] = (u, v, w)
,其中 u
是源节点,v
是目标节点, w
是一个信号从源节点传递到目标节点的时间。
现在,我们向当前的节点 K
发送了一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1
。
注意:
N
的范围在[1, 100]
之间。K
的范围在[1, N]
之间。times
的长度在[1, 6000]
之间。- 所有的边
times[i] = (u, v, w)
都有1 <= u, v <= N
且0 <= w <= 100
。
解题思路:其实这道题就是一个求从一个点到其他所有节点的最短路径的问题,只要把那个边的耗时看成边长,然后找出最远的那个节点的距离,即是我们要求的所有节点都收到信息的时长。这道题利用迪杰斯特拉算法就可以求得。
class Solution {
public:
int networkDelayTime(vector<vector<int>>& times, int N, int K) {
map<int, vector<pair<int, int> > > path;
//记录每个节点拥有的路径,距离。第一个参数表示节点编号,第二个参数每一个pair的first是目的节点,second是距离
for (int i = 0; i < times.size(); i++) {
path[times[i][0]].push_back(pair<int, int>(times[i][1], times[i][2]));
}
vector<int> gone; //标记已经算进来的点
gone.push_back(K);
map<int, int> distance; //标记从源节点到每个点的距离
distance[K] = 0;
bool is_gone[N + 1];
for (int i = 0; i < N + 1; i++) {
is_gone[i] = false;
}
is_gone[K] = true;
while (gone.size() < N) { // 如果还有节点没有被算进来
int min = -1;
int index_i = 0;
int index_j = 0;
// 遍历已经算进来的点,找到符合要求的下一个点
for (int i = 0; i < gone.size(); i++) {
for (int j = 0; j < path[gone[i]].size(); j++) {
if (is_gone[path[gone[i]][j].first]) {
continue;
}
// LS
if (min == -1 || min > (path[gone[i]][j].second + distance[gone[i]])) {
min = path[gone[i]][j].second + distance[gone[i]];
index_i = i;
index_j = j;
}
}
}
if (min == -1) { // 如果找不到下个点了,说明不连通
return -1;
} else { // 找到了则将其加入gone中,并记下此点到源节点的距离
gone.push_back(path[gone[index_i]][index_j].first);
distance[path[gone[index_i]][index_j].first] = path[gone[index_i]][index_j].second + distance[gone[index_i]];
is_gone[path[gone[index_i]][index_j].first] = true;
}
}
// 找出最远的那个点对应的距离
int max = -1;
for (map<int,int>::iterator it = distance.begin(); it != distance.end(); it++) {
if (it->second > max) {
max = it->second;
}
}
return max;
}
};