LeetCode 15.三数之和(C语言)

题目描述:

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。

示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

提示:

  • 3 <= nums.length <= 3000
  • -10^5 <= nums[i] <= 10^5

题解

我们做这道题,首先要将这些元素进行排序,我们可以用C语言自带的排序函数qsort进行排序。

排序之后,在数组中找到这三个数,我们会首先想到用三重循环,

    for(int i = 0; i < numsSize - 2; i++)
    {
        for(int j = i + 1; j < numsSize - 1; j++)
        {
            for(int k = j + 1; k < numsSize; k++)
            {
                if(nums[i] + nums[j] + nums[k] == 0)
                {
                    录入;
                }
            }
        }
    }

但我们发现仅仅三重循环我们连样例都过不了,这是因为题目中要求返回的三元组是不能彼此重复的,如示例一排序后结果为

-4  -1  -1  0  1  2而三元组中的-1  0  1这个结果会在 i = 1 j = 3 k = 4 和 i = 2 j = 3 k = 4这两次循环中取到,而之所以会出现这种情况,是因为 i 重复取了两个相同的值,解决方法就是当循环变量碰到与上一次相同的值时就要跳过这次循环

    for(int i = 0; i < numsSize - 2; i++)
    {
        if(i > 0 && nums[i] == nums[i - 1])
            continue;
        for(int j = i + 1; j < numsSize - 1; j++)
        {
            if(j > i + 1 && nums[j] == nums[j - 1])
                continue;
            for(int k = j + 1; k < numsSize; k++)
            {
                if(k > j + 1 && nums[k] == nums[k - 1])
                    continue;
                if(nums[i] + nums[j] + nums[k] == 0)
                {
                    录入
                }
            }
        }
    }

当然我们在跳过循环时要注意变量的范围,例如变量 i 就要取 i 和 i - 1范围的交集,i - 1 > 0,i >1,否则就会造成数组越界。

但写成这样还是过不了的,O(n^3)的时间复杂度到最后的几个数很多的样例会超时。所以我们要优化算法。

比如 nums[i] 和 nums[j] 都是从小往大取值,那么满足条件的 nums[k] 一定是一个较大的数,那么我们在第三重循环 k 就可以从最后一个数往前遍历

 再有我们可以检查nums[k]的取值范围,

根据 nums[i] + nums[j] + nums[k] = 0 有nums[k] = -(nums[i] + nums[j]),当 k 从后往前遍历时,nums[k] 的值逐渐减小,当 nums[k] 的值 <= -(nums[i] + nums[j])时 我们检查三数之和,

当三数之和等于零时,我们记录结果,小于零,有nums[j] < -(nums[i] + nums[k]),将 j 从小到大遍历,直到nums[i] + nums[j] + nums[k] >= 0,当三数之和等于零时,记录结果,

否则重复上面步骤直到 j == k

这时我们让 i++,j = i + 1,k = numsSize - 1 如此循环直到 i == numsSize - 1,i 后只有一个数不足以满足条件为止。

for(int i = 0; i < numsSize - 2; i++)
{
    if(i > 0 && nums[i] == nums[i - 1])
        continue;
    int j = i + 1;
    for(int k = numsSize - 1; k > j; k--)
    {
        while(k > j && (nums[k] > -(nums[i] + nums[j]) || k < numsSize - 1 && nums[k] == nums[k + 1]))
            k--;
        while(j < k && (nums[i] + nums[j] + nums[k] < 0 || j > i + 1 && nums[j] == nums[j - 1]))
            j++;
        if(j == k)
            break;
        if(nums[i] + nums[j] + nums[k] == 0)
        {
            记录
        }
    }
}

第一个while中的条件除了要确保 k 在有效范围中 还包含两方面,一是让nums[k]不断减小直到nums[k] 的值 <= -(nums[i] + nums[j],二是nums[k] 与上一次取值相同时继续向前遍历

第二个while也是类似。

数据的录入

给出的函数头我们可以看出来我们要将数据存入一个二维数组中,该数组的最大列数就是三元组的最大数,这里是3000/3=1000

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

**returnColumnSizes代表着列数组(存储每行有几列的数组)的地址

为*returnColumnSizes重新申请一块数组空间,大小为1000(最多1000列)

*returnColumnSizes = (int*)malloc(sizeof(int) * 1000);

将*returnSize初始化为零,代表当前的二维数组有零个三元组,

*returnSize = 0;

每当有三元组录入时我们可以再用二维数组的列指针申请一块三个 int 大小的空间

array[*returnSize] = malloc(sizeof(int) * 3);

将三元依次读入

 array[*returnSize][0] = nums[i];  array[*returnSize][1] = nums[j];  array[*returnSize][2] = nums[k];

同时设置列数组该行有三列

(*returnColumnSizes)[(*returnSize)] = 3;

行数加一 (*returnSize)++;

当然这样写在这道题中内存是不够用的,所以我们要用 realloc 函数,设置一个base基准,

下面为完整代码

/**
 * Return an array of arrays of size *returnSize.
 * The sizes of the arrays are returned as *returnColumnSizes array.
 * Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
 */
int cmp(void *a, void *b)
{
    return *(int*)a - *(int*)b;
}

int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
    qsort(nums, numsSize, sizeof(int), cmp);
    int base = 10;
    int **array = (int**)malloc(sizeof(int*) * base);
    *returnSize = 0;
    *returnColumnSizes = (int*)malloc(sizeof(int) * base);
for(int i = 0; i < numsSize - 2; i++)
{
    if(i > 0 && nums[i] == nums[i - 1])
        continue;
    int j = i + 1;
    for(int k = numsSize - 1; k > j; k--)
    {
        while(k > j && (nums[k] > -(nums[i] + nums[j]) || k < numsSize - 1 && nums[k] == nums[k + 1]))
            k--;
        while(j < k && (nums[i] + nums[j] + nums[k] < 0 || j > i + 1 && nums[j] == nums[j - 1]))
            j++;
        if(j == k)
            break;
        if(nums[i] + nums[j] + nums[k] == 0)
        {
            array[*returnSize] = malloc(sizeof(int) * 3);
            array[*returnSize][0] = nums[i];
            array[*returnSize][1] = nums[j];
            array[*returnSize][2] = nums[k];
            (*returnColumnSizes)[(*returnSize)] = 3;
            (*returnSize)++;
            if(*returnSize == base)
            {
                base *= 2;
                array = (int**)realloc(array, sizeof(int*) * base);
                *returnColumnSizes = (int*)realloc(*returnColumnSizes, sizeof(int) * base);
            }
        }
    }
}
    return array;
}

  • 24
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值