15. 三数之和为0给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k

15. 三数之和

难度中等5530收藏分享切换为英文接收动态反馈

给你一个整数数组 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
  • -105 <= nums[i] <= 105

这次还算可以 时间在c语言里面领先百分之96 内存领先百分之93

先简要介绍一下我的思路,我的思路是一个渐变的过程,他在不断修改,不断进步

但有些时候要思考一个问题看上去合理的行为不一定能快,根据测试集的变化看似变快的思路其实会降低时间,但这个思路我们要有。至于到底是怎么回事,请耐下心接着看

//我的第一想法是穷举,但似乎时间复杂度会有点大
//看一些大佬的题解以后做出如下总结(同时在做题的时候补充了一下自己的想法 做出了一些改进)
//我自认为我的剪枝应该做到了极致
//1.大部分用的都是排序+双指针
//排序是去重的一个前提,只有整个数组是有序数组
//我们才能跳过相同数据,排序我用的是堆排序
//2.双指针有多重优化 方式:
//(错误存疑)在有序数组的前提下,第一个first——left跳过相同
//在有序数组的前提下,如果i,i+1,i+2个和大于0可以结束
//在有序数组的前提下,如果第i个与n-1,n-2和小于0说明i不够大 i要++,用continue减少不必要的循环
//在有序数组的前提下,找到合适的解后要用for循环跳过等价解


//在有序数组的前提下,找到解不合适需要跳过相同解 如果过大 third跳过相同解 反之second跳过相同解

这个是不是看上去特别合理,但是因为测试集没有那种解不对且连着出现好几相同的需要跳过的,所以while循环过多的判断反而让时间耗费变多,所以我把那两句话注释掉了(就是else if mid++ else right--里面的哪两个句子)
//3.当我做完陷入自我满足时我又想到一个新方法我只需要一个o(n)就可以避免如此复杂的逻辑
//去掉nums数组里面的相同数据似乎会节省很多时间,但是还可能出现一些问题(我猜测会出问题)
//万一出问题比如某些特解例子会因为我把相同数据去的太多了比如-1 -1 2 这种例子直接丢失了
//因为我把-1直接搞没了,回头思考我是不是剪枝剪疯了 我觉得对于第一个数据不应该跳过相同数据
//吓得我回头标注(错误存疑),问题在于如果向右去重会把-1,-1,2抹去,而向左去重如果在第二个位置遇到-1 我跳过不会省略-1,-1,2这种情况,因为第一个-1时已经把-1,-1,2找到存起来了

//在有序数组的前提下,第一个first——left必须向左去重才能避免-1,-1,2的问题

/**
 * 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().
 */
#define size 10
void Swap(int *num, int i, int j)
{
    int temp;
    temp = num[i];
    num[i] = num[j];
    num[j] = temp;
}
// 最大堆调整
void Heapify(int *num, int len, int k)
{
    if (k < len)
    {
        int root = k;           // 根结点
        int lchild = 2*k + 1;   // 左孩子结点
        int rchild = 2*k + 2;   // 右孩子结点
        // 查找左右孩子结点中的最大结点
        if (lchild < len && num[root] < num[lchild])
        {
            root = lchild; 
        }
        if (rchild < len && num[root] < num[rchild])
        {
            root = rchild;
        }
        // 交换最大结点到根结点
        if (root != k)
        {
            Swap(num, root, k);
            // 每次交换都可能影响到对应孩子结点子树的顺序
            // 所以需要对交换后的孩子结点子树进行最大堆调整
            Heapify(num, len, root);
        }
    }
}

// 创建最大堆
void CreateHeap(int *num, int len)
{

    // 最后一个结点下标
    int last = len - 1;   
    // 最后一个结点的父结点下标      
    int parent = (last - 1) / 2;
    // 从最后一个结点的父结点到根结点,依次进行最大堆调整
    for (int i = parent; i >= 0; i--)
    {
        Heapify(num, len, i);
    }
}

// 堆排序
void HeapSort(int *num, int len)
{
    // 创建最大堆并进行最大堆调整
    CreateHeap(num, len);
    printf("最大堆调整!\n");
    // 依次取出根结点(最大值)
    for (int i = len - 1; i >= 0; i--)
    {
        // 将最大堆的根结点(最大值)换到最后一个结点
        Swap(num, i, 0);
        // 交换后二叉树的根结点发生了改变,故还需对根结点做最大堆调整(已交换的末尾结点不参与调整)
        // 而此时根结点小于所有父结点,因而在调整时只需考虑最大孩子的分支即可
        Heapify(num, i, 0); 
    }   
}
int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){
    int base=300;                                           //数组的初始长度,可更改
    int** a=(int**)malloc(sizeof(int*)*base);
    *returnColumnSizes=(int*)malloc(sizeof(int)*base);
    *returnSize=0;                                          //初始化
    int i,j,k=0,mid,right,temp;
    HeapSort(nums,numsSize);
    for(i=0;i<numsSize-2;i++){
        if(i>0&&nums[i]==nums[i-1]){continue;}              //对i向左去重,(向右会忽略-1,-1,2)
        if(nums[i]+nums[i+1]+nums[i+2]>0)//在有序数组的前提下,如果i,i+1,i+2个和大于0可以结束
        {
            break;
        }
        if(nums[i]+nums[numsSize-1]+nums[numsSize-2]<0)
        {
            continue;
        }
        mid=i+1;right=numsSize-1;                           //定义双指针
        while(mid<right){
            if(nums[i]+nums[mid]+nums[right]==0){
                a[*returnSize]=(int*)malloc(sizeof(int)*3); //每一个数组大小都为3
                (*returnColumnSizes)[*returnSize]=3;        //给申请的空间赋值
                a[*returnSize][0]=nums[i];
                a[*returnSize][1]=nums[mid++];
                a[*returnSize][2]=nums[right--];
                (*returnSize)++;                            //行自增		
                while(nums[mid]==nums[mid-1]&&mid<right){mid++;}
                while(nums[right]==nums[right+1]&&mid<right){right--;}    
                if(*returnSize==base){
                    base*=2;
                    a=(int**)realloc(a,sizeof(int*)*base);
                    *returnColumnSizes=(int*)realloc(*returnColumnSizes,sizeof(int)*base);
                }//如果二维数组的大小达到初始设定的行数,则进行空间扩容
            }//找到目标数组
            else if(nums[i]+nums[mid]+nums[right]<0)
            {
                mid++;
                //while(nums[mid]==nums[mid-1]&&mid<right){mid++;}
            }
            else
            {    
                
                right--;
                //while(nums[right]==nums[right+1]&&mid<right){right--;} 
            }  
        }
    }//i遍历最小的数,mid从i+1开始,寻找中间的数,right为右指针
    return a;
}

 


版权声明:本文为2201_76045733原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。