【自除数】和【除自身以外数组的乘积(两次优化)】


前言

最近的OJ练习用到动态分配内存malloc函数更频繁了,现在已经能够基础地运用它解决算法问题了,不得不说,不能使用变长数组确实麻烦多了…

一、自除数

题目:
自除数,是指可以被它包含的每一位数除尽的数。
例如, 128 是一个自除数,因为 128 % 1 == 0 ,128 % 2 == 0 ,128 % 8 == 0 。还有,自除数不允许包含 0 。
给定上边界和下边界数字,输出一个列表,列表的元素是边界(含边界)内所有的自除数。

OJ链接

思路:在之前的水仙花数(自幂数)和递归练习中,已经使用过了分离出每一位数的方法,就是再循环中模10取最后一位再除10,直到这个数为0终止循环。

代码如下:

#include <stdio.h>
#include <stdlib.h>
int isSelfDividing(int num)
{
	int tmp = num;
	while (tmp > 0)
	{
		int digit = tmp % 10;
		if (digit == 0 || num % digit != 0)//被除数不能为0  注意两条判断语句的顺序(前为真,后不执行)
		{
			return 0;
		}
		tmp /= 10;
	}
	return 1;
}
int* selfDividingNumbers(int left, int right, int* returnSize)
{
	int* result = (int*)malloc((right - left + 1) * sizeof(int));//分配足够大的数组
	int count = 0;
	for (int i = left; i <= right; i++)
	{
		if (isSelfDividing(i))
		{
			result[count++] = i;
		}
	}
	*returnSize = count;
	return result;
}
int main()
{
	int top = 0;
	int under = 0;
	scanf("%d%d", &top, &under);
	printf("\n");
	int size = 0;
	int* result = selfDividingNumbers(top, under, &size);
	int i = 0;
	for (i = 0; i < size; i++)	//size返回了数组元素个数
	{
		printf("%d ", result[i]);
	}
	printf("\n");
	free(result);
	return 0;
}

(1)在这段代码中,先调用了一个函数selfDividingNumbers遍历数字的上边界到下边界,此时就使用了动态分配内存为result数组分配了内存,这样,result就是一个指向整形数组的指针,*returnSize=count用于记录数组元素个数。
(2)在判断是否为自除数时调用isSelfDividing函数对每个数字判断,在isSelfDividing要注意的是,由于自除数不包含0,在**if(digit == 0 || num % digit != 0)**这段代码的两个条件中,他们的顺序不能交换,因为在逻辑或中,若第一个表达式为真,则第二个表达式不执行,如果调换顺序,就会导致除数为0,程序就会出错。

二、除自身以外数组的乘积(两次优化)

题目:
给你一个长度为 n 的整数数组 nums ,其中 n > 1 ,返回输出数组 output ,
其中 output[i] 等于 nums 中除nums[i] 之外其余各元素的乘积。
题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。

OJ链接

1. 暴力解法

时间复杂度:O(n^2),空间复杂度:O(n)
代码如下:

#include <stdio.h>
#include <stdlib.h>
int* productExceptSelf(int* nums, int numsSize, int* returnSize)
{
	int i = 0;
	int* result = (int*)malloc(sizeof(int) * numsSize);
	for (i = 0; i < numsSize; i++)
	{
		int product = 1;
		for (int j = 0; j < numsSize; j++)  //时间复杂度O(n^2)超出限制
		{
			if (i != j)
			{
				product *= nums[j];
			}
		}
		result[i] = product;
	}
	*returnSize = numsSize;
	return result;
}
int main()
{
	int n = 0;
	scanf("%d", &n);
	int arr[1000] = { 0 };
	for (int i = 0; i < n; i++)
	{
		scanf("%d", &arr[i]);
	}
	int size = 0;
	int* result = productExceptSelf(arr, n, &size);
	for (int i = 0; i < size; i++)
	{
		printf("%d ", result[i]);
	}
	free(result);
	return 0;
}

这段代码就是在每个数中遍历除它本身的数的乘积,嵌套循环解决,时间复杂度太高,不推荐。

2. 第一次优化:前缀乘积 * 后缀乘积

时间复杂度O(n),空间复杂度O(n)
代码如下(主函数同上):

int* productExceptSelf(int* nums, int numsSize, int* returnSize)
{
	int* proleft = (int*)malloc(sizeof(int) * numsSize);
	int* proright = (int*)malloc(sizeof(int) * numsSize);
	//前缀乘积
	int i = 0;
	proleft[0] = 1;
	for (i = 1; i < numsSize; i++)
	{
		proleft[i] = proleft[i - 1] * nums[i - 1];//将每位i的前缀乘积依次放入数组 proleft[]={1,1,2,6}
	}
	//后缀乘积
	proright[numsSize - 1] = 1;
	for (i = numsSize - 1 - 1; i >= 0; i--)
	{
		proright[i] = proright[i + 1] * nums[i + 1];//proright[]={24,12,4,1}
	}
	//前缀乘积*后缀乘积=除自身各元素乘积
	int* output = (int*)malloc(sizeof(int) * numsSize);
	for (i = 0; i < numsSize; i++)
	{
		output[i] = proleft[i] * proright[i];
	}
	*returnSize = numsSize;
	return output;
}

优化后的productExceptSelf函数中,是为两个指针分配了内存,一个存放前缀乘积,一个存放后缀乘积,最后分配一个指针将它们乘起来存放。这样做将时间复杂度降低到了O(n),用了三个循环分别求前缀乘积、后缀乘积和最终结果,由于不能使用变长数组,开辟了三个新的空间。空间复杂度为O(n)。
而其实在计算后缀乘积时,可以通过一个变量来记录累积的乘积,而不需要额外的数组 proright。这样可以减少空间复杂度,并且只需进行一次遍历
这样,就有了第二次优化。

3. 第二次优化:遍历两次数组以计算前缀和后缀乘积

时间复杂度O(n),空间复杂度O(n)
代码如下:

int* productExceptSelf(int* nums, int numsSize, int* returnSize)
{
	int* output = (int*)malloc(sizeof(int) * numsSize);
	//计算前缀乘积,并且直接放在output空间
	int i = 0;
	int tmp = 1;
	output[0] = 1;
	for (i = 1; i < numsSize; i++)
	{
		tmp *= nums[i - 1];  //tmp用于保存前缀乘积
		output[i] = tmp;	//output[]={1,1,2,6}
	}
	//直接乘上后缀乘积 输入乘积结果
	tmp = 1;
	for (i = numsSize - 1; i >= 0; i--)
	{
		output[i] *= tmp;  //后缀乘积最后一项为1
		tmp *= nums[i];	//tmp用于保存后缀乘积
	}
	*returnSize = numsSize;
	return output;
}

在这段代码中,我们只分配了一个内存给output指针,我们可以用output[]来访问数组,在计算前缀乘积时,直接将其结果放到output[]数组当中,而后用一个变量记录后缀的乘积,并直接在output[]数组中将结果求出。这次优化后,我们只用两次循环,开辟了一个新的空间就解决了问题。虽然空间复杂度还是O(n),但是更加节省,并且减少了不必要的内存分配和释放。


总结

若要动态分配一个包含整数的数组,可以使用以下语法:

int* array = (int*)malloc(sizeof(int) * size);

在这个例子中,malloc 函数为 size 个整数类型的元素动态分配了内存空间,并将分配的内存指针赋给了 array。
动态分配内存后,我们可以使用 array 指针通过索引来访问和修改数组元素,例如 array[0],array[1] 等。
需要记住,使用完动态分配的内存后,应该通过调用 free 函数来释放这块内存空间,以避免内存泄漏。

总结起来,动态分配内存时,实际上是为指针分配了一个特定大小的内存空间。该指针可以被视为一个数组,在使用完毕后应该释放该内存空间。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值