目录
- 前言
- 问题介绍
- 解决方案
- 代码编写
- java语言版本
- c语言版本
- c++语言版本
- 思考感悟
- 写在最后
前言
当前所有算法都使用测试用例运行过,但是不保证100%的测试用例,如果存在问题务必联系批评指正~
在此感谢左大神让我对算法有了新的感悟认识!
问题介绍
原问题
给定一个数组arr,其中该arr[i] = j表示区域i直接连接的区域是区域j,如下图所示:
其中arr[3] = 1,arr[6] = 1,arr[2] = 1,其中arr[1] = 0代表 1是首都
现在要求:
1、不使用新数组
2、时间复杂度在O(n)
3、将arr[i] = j代表的意义转换成 到1的距离为i的节点数有几个,如上图 arr[1] = 3
已知:路径图不会成环并且都最终通往首都
解决方案
原问题:
1、不是用新数组,那么就只能使用arr这个入参提供的空间
2、时间复杂度控制在O(n)也就是只能最多使用一层循环,避免循环嵌套
3、解决该问题:arr需要经历2个周期转换,第一个周期转换为:将arr[i] = j 代表 i指向j 转换为 arr[i] = j 代表 i到首都的距离为j
第二个周期转换为: arr[i] = j 代表 i到首都的距离为j 转换为 arr[i] = j 代表 到首都的距离为i的节点数有几个
其中 转换周期1的方法:
从arr[0]开始遍历,取出arr[0],代表下一个节点j,arr[0]是起点,所以先让arr[0]为0,之后看arr[j],将arr[j]的下一个节点暂存后,再将arr[j]设置为0,代表j节点的上一个节点是节点0,如此往复一定会查到首都节点,此时将首都设置为0,开始往回走,每回去一个节点该节点的数值更新为上一个节点-1,直到回到起点为止。形象一点如下:
去过程:原2节点现在指向节点0,当遇到节点1时发现1是首都,次时往回走,看回过程
回过程:当从1回到2的时候,arr[1]为0,所以arr[2] = arr[1] - 1,最终这条路径变成负数路径
转换周期二:距离数组转统计数组
到这里arr = [-2, 0, -1, -1, -2, -2, -1]
现在开始转换成统计数组,首先不能使用多余的空间,所以我们还是需要在该数组上做转换
arr[0] = -2 说明0节点到首都的距离为2,此时判断arr[2] 是否大于0,如果小于0,说明当前位置并没有被统计过,所以暂存,改为0,然后+1。如果大于0 ,说明已经统计过,此时直接+1即可,被统计过的arr[0]此时已经统计过,但是暂时没有人距离为0(首都没遍历到),所以arr[0]变为0
一轮交换转换之后统计数组即可出现。
代码编写
java语言版本
原问题:
/**
* 二轮测试:从arr[i] = j 代表数值i的结点指向j含义转化为 arr[i]代表到首都的距离为i的结点数量
* 时间复杂度O(N),空间复杂度O(1)
* @param arr
* @return
*/
public static int[] path2NumsCp1(int[] arr) {
if (arr == null) {
return null;
}else if (arr.length == 0) {
return new int[]{0};
}
// 在原arr的基础上直接计算每一个点到首都的距离
int cap = convertPath2Length(arr);
arr = convertLength2Nums(arr, cap);
return arr;
}
/**
* 将代表path的arr转换成距离首都的距离(用负数表达)
* @param arr
*/
private static int convertPath2Length(int[] arr) {
// 记录一下首都的index
int capIndex = -1;
for (int i = 0; i < arr.length; i++) {
// 首先判断i是否已经计算过
if (arr[i] < 0 || i == capIndex) {
continue;
}
// 记录一下起点,标记起点为-1
int cur = i;
// next代表下一跳
int next = arr[cur];
arr[cur] = -1;
while (arr[next] >= 0 && next != capIndex) {
// 先判断是否是首都
if (arr[next] == next) {
// 首都直接返回,并且首都到首都的距离是0
arr[next] = 0;
capIndex = next;
break;
}
// 需要跳跃
int temValue = arr[next];
// 记录回去的路
arr[next] = cur;
cur = next;
next = temValue;
}
// 到这里next可能代表首都,也可能arr[next]已经计算过,cur代表着上一个节点的index
// 所以从这里开始往回更新距离了
while (arr[cur] != -1) {
// 暂存一下下一个的位置
int tem = arr[cur];
// 上一个距离-1
arr[cur] = arr[next] - 1;
// 更新游标
next = cur;
cur = tem;
}
// 最后再更新一下arr[cur]
arr[cur] = arr[next] - 1;
}
return capIndex;
}
/**
* 从长度到统计数组的转换
* @param arr
* @return
*/
private static int[] convertLength2Nums(int[] arr, int cap) {
for (int i = 0; i < arr.length; i ++) {
if (arr[i] >= 0 && i != cap) {
// 大于0的说明统计过了
continue;
}
// 到这里的都要进行统计
// 首先拿出来arr[i],取相反数才是真的len
int cur = arr[i];
arr[i] = 0;
boolean flag = false;
while (cur <= 0) {
// 当前位置距离首都的距离
int len = -cur;
if (arr[len] >= 0) {
arr[len] ++;
flag = true;
break;
}else {
// 说明了需要循环替换
int next = arr[len];
// 替换
arr[len] = 0;
arr[len] ++;
// 下一个游标
cur = next;
}
}
if (!flag) {
arr[cur] ++;
}
}
return arr;
}
public static void main(String[] args) {
CommonUtils.printArr(path2NumsCp1(new int[]{9,1,4,9,0,4,8,9,0,1}));
}
c语言版本
正在学习中
c++语言版本
正在学习中
思考感悟
这道题很考验大脑的寄存器,,,
首先记录数组的变化非常绕,不让使用多余空间时,就要在一个数组上面进行交换和更新,其中需要注意的两个点:
1、用负数处理非常巧妙的将数组当做两个数组来用
2、0这个位置和0这个值非常特殊,因为其代表首都也代表距离,容易搞混,所以需要有一个变量专门标注首都以备后面判断使用。
总之需要好好体会数组的转换,并且如果限制了空间,可以使用正负数的方式将数组进行界线分割,其次,回溯的过程也很巧妙,用于路径数组变成长度数组时使用。
写在最后
方案和代码仅提供学习和思考使用,切勿随意滥用!如有错误和不合理的地方,务必批评指正~
如果需要git源码可邮件给2260755767@qq.com
再次感谢左大神对我算法的指点迷津!