携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第31天,点击查看活动详情 题目描述
「力扣挑战赛」有 n 个比赛场馆(场馆编号从 0 开始),场馆之间的通道分布情况记录于二维数组 edges 中,edges[i]= [x, y] 表示第 i 条通道连接场馆 x 和场馆 y(即两个场馆相邻)。初始每个场馆中都有一定人数的志愿者(不同场馆人数可能不同),后续 m 天每天均会根据赛事热度进行志愿者人数调配。调配方案分为如下三种:
将编号为 idx 的场馆内的志愿者人数减半; 将编号为 idx 的场馆相邻的场馆的志愿者人数都加上编号为 idx 的场馆的志愿者人数; 将编号为 idx 的场馆相邻的场馆的志愿者人数都减去编号为 idx 的场馆的志愿者人数。 所有的调配信息记录于数组 plans 中,plans[i] = [num,idx] 表示第 i 天对编号 idx 的场馆执行了第 num 种调配方案。 在比赛结束后对调配方案进行复盘时,不慎将第 0 个场馆的最终志愿者人数丢失,只保留了初始所有场馆的志愿者总人数 totalNum ,以及记录了第 1 ~ n-1 个场馆的最终志愿者人数的一维数组 finalCnt。请你根据现有的信息求出初始每个场馆的志愿者人数,并按场馆编号顺序返回志愿者人数列表。
注意:
测试数据保证当某场馆进行第一种调配时,该场馆的志愿者人数一定为偶数; 测试数据保证当某场馆进行第三种调配时,该场馆的相邻场馆志愿者人数不为负数; 测试数据保证比赛开始时每个场馆的志愿者人数都不超过 10^9; 测试数据保证给定的场馆间的道路分布情况中不会出现自环、重边的情况。 示例 1:
输入: finalCnt = [1,16], totalNum = 21, edges = [[0,1],[1,2]], plans = [[2,1],[1,0],[3,0]]
输出:[5,7,9]
解释:
示例 2 :
输入: finalCnt = [4,13,4,3,8], totalNum = 54, edges = [[0,3],[1,3],[4,3],[2,3],[2,5]], plans = [[1,1],[3,3],[2,5],[1,0]]
输出:[10,16,9,4,7,8]
提示:
- 2 <= n <= 5*10^4
- 1 <= edges.length <= min((n * (n - 1)) / 2, 5*10^4)
- 0 <= edges[i][0], edges[i][1] < n
- 1 <= plans.length <= 10
- 1 <= plans[i][0] <=3
- 0 <= plans[i][1] < n
- finalCnt.length = n-1
- 0 <= finalCnt[i] < 10^9
- 0 <= totalNum < 5*10^13
解题思路
给定一个数组finalCnt为除了第0个场馆之外的所有场馆的最终志愿者人数,totalNum为初始志愿者总数,edges为每条通道场馆相邻的场馆,plans为每一天每个场馆选择的方案。要求初始每个场馆的人数。
首先确定每个场馆的志愿者人数有三个方案
- 当前场馆的志愿者人数减少一半;
- 当前场馆相邻的所有场馆志愿者人数增加当前场馆的人数;
- 当前场馆相邻的所有场馆志愿者人数减少当前场馆的人数;
所以需要先获取到场馆的相邻场馆,使用一个map的容器来封装场馆间的相邻场馆,命名为neighbors。
然后根据plans和finalCnt倒推初始的场馆志愿者人数,这里因为不确0号场馆的最终志愿者人数,所以可以用回溯的方法来求得最初的志愿者人数。
确定本题的回溯三要素
路径
这里的路径其实就只有一个值,即当前0号场馆选择的人数
可选择路径
0号场馆最终志愿者人数的范围[0,finalCnt[0]*plans.size]
终止条件
当选择的方案与当前场馆人数不匹配时,直接进行下次回溯;如果已经到达第一天的方案选择且初始志愿者人数之和等于totalNum时,返回当前每个场馆的志愿者人数。
优化思路
超出时间限制,先判断能否进行剪枝,或者缩小场馆0的取值范围;
可以将场馆0的最终志愿者数量设置为变量x,然后每次计算都代入计算x,就不计算具体值,最终的初始志愿者数量可以通过解一元一次方程解答;
所以优化后的代码不从遍历所有0号场馆可能的值入手,而是直接采用假设变量法,即假定它是一个变量。则操作这个场馆及相邻场馆的志愿者人数分为两部分,一部分是已知的常量,另一部分是未知数系数。最后通过一元一次方程求解得到未知数。然后获得最终志愿者人数。
代码实现
public static int[] volunteerDeployment(int[] finalCnt, long totalNum, int[][] edges, int[][] plans) {
// 封装相邻场馆map
Map<Integer, HashSet<Integer>> neighbors = new HashMap<>();
for (int[] edge : edges) {
neighbors.putIfAbsent(edge[0], new HashSet<>());
neighbors.get(edge[0]).add(edge[1]);
neighbors.putIfAbsent(edge[1], new HashSet<>());
neighbors.get(edge[1]).add(edge[0]);
}
// 所有场馆当前的人数
long[] res = new long[finalCnt.length + 1];
for (int i=0;i< finalCnt.length;i++) {
res[i+1] = finalCnt[i];
}
// 每个场馆的变量系数
double[] variable = new double[finalCnt.length + 1];
// 第0个场馆,系数初始为1
variable[0] = 1.0;
for (int i = plans.length-1;i>= 0;i--) {
int[] plan = plans[i];
int item = plan[1];
if (plan[0] == 1) {
// 本身加一半
res[item] *= 2;
// 变量也要做相应的变动
variable[item] *= 2;
} else if (plan[0] == 2) {
// 相邻的所有元素减当前场馆元素
for (Integer integer : neighbors.getOrDefault(item, new HashSet<>())) {
res[integer] -= res[item];
// 变量也要做相应的变动
variable[integer] -= variable[item];
}
} else {
// 相邻的所有元素加当前场馆元素
for (Integer integer : neighbors.getOrDefault(item, new HashSet<>())) {
res[integer] += res[item];
// 变量也要做相应的变动
variable[integer] += variable[item];
}
}
}
// 求解一元一次方程
long sum = 0;
double variableSum = 0;
for (int i =0;i < res.length; i ++) {
sum += res[i];
variableSum += variable[i];
}
double x = (double)(totalNum-sum)/variableSum;
int[] returnRes = new int[res.length];
// 封装结果
for (int i =0;i < res.length; i ++) {
returnRes[i] = (int)(res[i] + x*variable[i]);
}
return returnRes;
}