本周两道题,一道是图的题,一道是贪心的题。
Clone Graph
1.问题描述
克隆一个无向图,已知节点的形式,是一个结构体,有一个label和一个邻接表,表示所有邻居节点。节点的label值是唯一的,图可能是有环,甚至是自环。
2.思路
题目思路很简单,但是做起来就发现还是要注意很多细节。首先这个题目可使用BFS或者DFS形式来实现,BFS的话就是对于一个点,把它的邻接节点都建了,压入队列中,接着取队列中下一个节点,再对其邻接节点进行创建或者链接。DFS的方法是使用递归,建立自己的节点,然后递归地取建立子图。
下面是BFS的形式。对于一个点来说,它的邻接节点可能是已经在之前创建过的,也就那么此时就只能找出已创建过的那个节点,然后添加到该节点的邻接表中。所以,我们需要一个容器来存放已经创建的节点。在实现时,我使用vector容器vec存储已经创建的节点,当遍历一个节点的子节点时,通过查找vec判断该子节点是否已经创建,如果已经创建,则无需再new一次,直接使用即可。
在这里注意,需要维持两个队列,一个队列存放的是原图的节点,另一个队列存放的是新图的节点,因为你需要遍历原图节点,得知当前节点的邻接表,同时需要根据当前节点的邻接表,为新图的同样是该节点,创建或查找到邻接节点,并链接到新图节点上。两个队列的遍历顺序,入队顺序都是一样的。
3.代码
struct UndirectedGraphNode {
int label;
vector<UndirectedGraphNode *> neighbors;
UndirectedGraphNode(int x) : label(x) {};
};
UndirectedGraphNode *cloneGraph(UndirectedGraphNode *node) {
if (node == NULL)
return NULL;
queue<UndirectedGraphNode*> q;
queue<UndirectedGraphNode*> newq;
vector<UndirectedGraphNode*> vec;
q.push(node);
UndirectedGraphNode* newroot = new UndirectedGraphNode(node->label);
newq.push(newroot);
vec.push_back(newroot);
bool flag = 0;
while (!q.empty()) {
UndirectedGraphNode* top = q.front();
q.pop();
UndirectedGraphNode* newtop = newq.front();
newq.pop();
for (int i = 0; i < top->neighbors.size(); i++) {
for (int j = 0; j < vec.size(); j++) {
if (vec[j]->label == top->neighbors[i]->label) {
newtop->neighbors.push_back(vec[j]);
flag = 1;
break;
}
}
if (flag == 0) {
UndirectedGraphNode* next = new UndirectedGraphNode(top->neighbors[i]->label);
q.push(top->neighbors[i]);
vec.push_back(next);
newtop->neighbors.push_back(next);
newq.push(next);
}
flag = 0;
}
}
return newroot;
}
Task Scheduler
1.问题描述
CPU调度任务,给出任务列表和间隔n,每一个任务需要一个CPU周期,同一个任务之间必须间隔n个空隙,求最少需要多少个CPU周期。例子如下:
输入: tasks = ['A','A','A','B','B','B'], n = 2, 输出: 8
解释:A -> B -> idle -> A -> B -> idle -> A -> B.
2.思路
对所有任务数进行统计,则按照上面的例子tasks = ['A','A','A','B','B','B'],得到一个统计结果是<A, 3>,<B, 3>,按照任务量最大的先做,n是2,先选一个A,接着是B,没有第三个可选,A和下一个A之间至少要有2个空隙,所以这里只能插入一个idle,需要的周期是3;下一轮<A, 2>,<B, 2>,再次选择A,B,idle,需要的周期累计为6,得到结果<A, 1>,<B, 1>;第三轮,选了A,B之后,发现所有任务都做完了,不需要再插入idle了,所以需要的周期是6+2为8;
按照对题目的理解,首先是按任务量顺序选n+1个后,此时已做的任务项对应的任务量会对应减1,当遍历队伍发现不足n+1个不同任务可选时,如果任务队伍中还有任务,那需要加入空闲时间段(idle),如果没有任务了,那就直接结束了。对所有任务的任务量重新排序,依旧是选择任务量大的先开始做。
我们这里再举一个例子验证思路。假设tasks = ['A','A','A','A','A','A', 'B','C','D','E','F','G'],此时可以得到一个统计数据是[6, 1, 1, 1, 1, 1, 1],此处省略了字符,因为不需要列出具体的任务顺序。给定n = 2,那么第一轮后剩下的是[5, 0, 0, 1, 1, 1, 1];此时要对任务进行重新排序,0不需要加入队伍中了,得到[5, 1, 1, 1, 1],此时周期数是3;第二轮后得到[4, 1, 1],周期数是6;第三轮后得到[3],周期数累计是9;第四轮,只有一个字符,选了A之后,没有其他能选的了,但是任务还没做完,所以只能加入idle,那么累计周期数是12,得到[2];接下来与上一轮类似,最后得到结果是16,看来这个思路是可以的。
到了写算法的问题,选择什么样的数据结构来存储任务数量?使用vector的话,实际上进行删除,排序,处理比较麻烦,所以这里选了可以自动排序,且可以简单pop出元素的priority_queue实现。算法思想:
I. 统计不同任务的数量,将数量加入优先队列pq中;
II. 当pq不为空时:
i. 循环n+1次,将pq的前n+1个字符pop出,加入临时数组temp中,记录压入temp数组的次数time;如果不足n+1个,则直接break;
ii. 对于临时数组temp中的元素,取出来,减1,如果还是大于0,压回pq中;
iii. 判断此时pq是否为空,如果为空,说明没有需要执行的任务了,那么此时累计结果res只需要加上time个周期;如果此时pq不为空,说明后面还有任务要执行,但是必须要空闲一段时间,那么,累计结果res就需要加上n+1,表明这一轮后需要的周期数。
3.代码
int leastInterval(vector<char>& tasks, int n) {
vector<int> counter;
int size = tasks.size();
if (size == 0)
return 0;
if (n == 0)
return size;
for (int i = 0; i < 26; i++)
counter.push_back(0);
for (int i = 0; i < size; i++) {
counter[tasks[i] - 'A'] ++;
}
priority_queue<int> pq;
for (int i = 0; i < 26; i++) {
if (counter[i] != 0)
pq.push(counter[i]);
}
int res = 0;
while (!pq.empty()) {
int time = 0;
vector<int> tmp;
for (int i = 0; i < (n+1); i++) {
if (!pq.empty()) {
tmp.push_back(pq.top());
pq.pop();
time++;
}
else
break;
}
for (int i = 0; i < tmp.size(); i++) {
if (--tmp[i]) {
pq.push(tmp[i]);
}
}
res += !pq.empty() ? (n+1) : time;
}
return res;
}