【LeetCode刷题】:双指针篇(三数之和,四数之和)

发布于:2025-02-10 ⋅ 阅读:(30) ⋅ 点赞:(0)

一、三数之和

1. 题目解析

三数之和【点击跳转】
在这里插入图片描述
题目的意思是给了我们一个数组nums,让我们找出三个下标不相同的数nums[i],nums[j],nums[k],要求这三个数的和恰好等于,然后返回这三个数

注意: 返回的这个三元组不能是重复的
我们可以结合示例1的解释来看:

示例 1:

输入的数组是nums = [-1, 0, 1, 2, -1, -4]

解释:
第一种: n u m s [ 0 ] + n u m s [ 1 ] + n u m s [ 2 ] = ( − 1 ) + 0 + 1 = 0 第二种: n u m s [ 1 ] + n u m s [ 2 ] + n u m s [ 4 ] = 0 + 1 + ( − 1 ) = 0 第三种: n u m s [ 0 ] + n u m s [ 3 ] + n u m s [ 4 ] = ( − 1 ) + 2 + ( − 1 ) = 0 第一种: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 第一种: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, 0, 1] 和 [-1, -1, 2]

2. 讲解算法原理

解法一:排序 + 暴力枚举 + 容器去重
我们可以将所有符合要求的三元组给枚举出来,然后直接利用set容器去重,但是在示例中可能会有很多种像示例一中的这种情况,所以我们可以先给数组排个序,另外,排完序后我们得到的三元组里面的元素顺序也会得到固定,这样也能够更好的让我们利用容器去重。

时间复杂度为:
O ( N 3 ) O(N^3) O(N3)

这里就直接给代码了,最后的提交结果肯定是超时的。

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        set<vector<int>> hash;
        int n = nums.size();
        vector<vector<int>> ret;
        // 排序
        sort(nums.begin(), nums.end());
        // 暴力枚举
        for(int i = 0; i < n; i++)
        {
            for(int j = i + 1; j < n; j++)
            {
                for(int k = j + 1; k < n; k++)
                {
                    if(nums[i] + nums[j] + nums[k] == 0)
                    {
                        vector<int> tmp = {nums[i], nums[j], nums[k]};
                        // 容器去重  --> 排好序后元素位置固定, 相同的两个三元组第一个元素和最后一个元素肯定是相同的
                        if(hash.find(tmp) == hash.end())
                        {
                            ret.push_back(tmp);
                            hash.insert(tmp);
                        }
                    }
                }
            }
        }
        return ret;
    }
};

在这里插入图片描述

解法二:排序 + 双指针
在暴力枚举的基础上进行的优化,前面做过一道和为s的两个数,如果想到这题,那么三数之和这道题就很好优化。
我们可以先固定一个数,假设这个数的下标是i,那么我们就在数组[i + 1, n - 1](n表示这个数组的长度)这个区间做那个和为s的两个数,只不过这两个数的和是等于nums[i]的相反数。

注意:
1、跟和为s的两个数不同的是,这里不止有一种结果,所以利用双指针找到一组结果后,不要挺,需要缩小范围然后继续找,确保结果没有遗漏。
2、另外一个就是去重问题,首先想到的就是利用容器的特性去重。还有就是找到一种结果之后,可以让指针跳过重复的元素进行去重。
在这里插入图片描述
这里需要注意一下指针越界的问题,题目中给定的数组nums可能是 [0, 0, 0, 0, 0],这种情况下指针会越界,所以在判断重复的元素时,left和right指针的关系是left < right,i 的话则需要小于数组的长度。

3. 代码编写

C++代码:

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> ret;
        int n = nums.size();
        // 排序
        sort(nums.begin(), nums.end());
        // 利用双指针解决问题
        for(int i = 0; i < n;  )
        {
            // 小优化
            if(nums[i] > 0)  break;
            // 定义左右双指针
            int left = i + 1, right = n - 1, target = -nums[i];
            while(left < right)
            {
                // 求和
                int sum = nums[left] + nums[right];
                if(sum < target) left++;
                else if(sum > target)  right--;
                else
                {
                    ret.push_back({nums[i], nums[left], nums[right]});
                    // 继续移动指针, 确保不漏
                    left++, right--;
                    // 去重left 和 right + 防止越界
                    while(left < right && nums[left] == nums[left - 1])  left++;
                    while(left < right && nums[right] == nums[right + 1])  right--;
                }
            }
            // 去重固定的数 i + 防止越界
            i++;
            while(i < n && nums[i] == nums[i - 1])  i++;
        }
        // 返回结果
        return ret;
    }
};

在这里插入图片描述

C语言代码:

// 比较函数,用于qsort排序
int compare(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}

int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
    if (nums == NULL || numsSize < 3 || returnSize == NULL || returnColumnSizes == NULL) {
        *returnSize = 0;
        *returnColumnSizes = NULL;
        return NULL;
    }

    qsort(nums, numsSize, sizeof(int), compare);

    *returnSize = 0;
    int capacity = numsSize / 3; // 合理的初始大小
    int** ret = (int**)malloc(capacity * sizeof(int*));
    *returnColumnSizes = (int*)malloc(capacity * sizeof(int));

    for (int i = 0; i < numsSize - 2; ) {
        if (nums[i] > 0) break;

        int left = i + 1, right = numsSize - 1, target = -nums[i];
        while (left < right) {
            int sum = nums[left] + nums[right];
            if (sum < target) {
                left++;
            } else if (sum > target) {
                right--;
            } else {
                if (*returnSize >= capacity) {
                    capacity *= 2; // 动态扩展
                    int** temp = (int**)realloc(ret, capacity * sizeof(int*));
                    if (temp == NULL) {
                        free(ret);
                        free(*returnColumnSizes);
                        *returnSize = 0;
                        *returnColumnSizes = NULL;
                        return NULL;
                    }
                    ret = temp;
                    *returnColumnSizes = (int*)realloc(*returnColumnSizes, capacity * sizeof(int));
                }

                ret[*returnSize] = (int*)malloc(3 * sizeof(int));
                ret[*returnSize][0] = nums[i];
                ret[*returnSize][1] = nums[left];
                ret[*returnSize][2] = nums[right];
                (*returnColumnSizes)[*returnSize] = 3;
                (*returnSize)++;

                left++, right--;
                while (left < right && nums[left] == nums[left - 1]) left++;
                while (left < right && nums[right] == nums[right + 1]) right--;
            }
        }

        i++;
        while (i < numsSize && nums[i] == nums[i - 1]) i++;
    }

    ret = (int**)realloc(ret, *returnSize * sizeof(int*));
    *returnColumnSizes = (int*)realloc(*returnColumnSizes, *returnSize * sizeof(int));
    return ret;
}

在这里插入图片描述
Python代码:

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        nums.sort()  # 对数组进行排序
        n = len(nums)
        ret = []  # 用于存储结果的列表

        for i in range(n):
            # 小优化:如果当前数字大于0,则后续数字也大于0,不可能组成和为0的三元组
            if nums[i] > 0:
                break

            # 去重:如果当前数字与前一个数字相同,跳过
            if i > 0 and nums[i] == nums[i - 1]:
                continue

            # 定义左右双指针
            left, right = i + 1, n - 1
            target = -nums[i]

            while left < right:
                total = nums[left] + nums[right]

                if total < target:
                    left += 1
                elif total > target:
                    right -= 1
                else:
                    # 找到一个满足条件的三元组
                    ret.append([nums[i], nums[left], nums[right]])

                    # 移动指针并去重
                    left += 1
                    while left < right and nums[left] == nums[left - 1]:
                        left += 1

                    right -= 1
                    while left < right and nums[right] == nums[right + 1]:
                        right -= 1

        return ret

在这里插入图片描述

二、四数之和

1. 题目解析

四数之和【点击跳转】
在这里插入图片描述
和三数之和的题目意思一模一样,只不过是由三个数的和等于0变成了数组中四个数相加要等于目标值target,符合要求的四元组不能重复,结果不能有遗漏。

2. 讲解算法原理

你在逗我呢?三数之和都会了,四数之和还不会?不准往下看了,赶紧给我写代码去OvO。

解法一:排序 + 暴力枚举 + 容器去重
我们可以将所有符合要求的四元组给枚举出来,然后直接利用set容器去重,但是在示例中可能会有很多种像示例一中的这种情况,所以我们可以先给数组排个序,另外,排完序后我们得到的四元组里面的元素顺序也会得到固定,这样也能够更好的让我们利用容器去重。

时间复杂度为:
O ( N 4 ) O(N^4) O(N4)

这里就直接给代码了,最后的提交结果肯定是超时的。

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        set<vector<int>> hash;
        int n = nums.size();
        vector<vector<int>> ret;
        // 排序
        sort(nums.begin(), nums.end());
        // 暴力枚举
        for(int i = 0; i < n; i++)
        {
            for(int j = i + 1; j < n; j++)
            {
                for(int k = j + 1; k < n; k++)
                {
                    for(int y = k + 1; y < n; y++)
                    {
                        if((long long)nums[i] + nums[j] + nums[k] + nums[y] == target)
                        {
                            vector<int> tmp = {nums[i], nums[j], nums[k], nums[y]};
                            // 容器去重  --> 排好序后元素位置固定, 相同的两个三元组第一个元素和最后一个元素肯定是相同的
                            if(hash.find(tmp) == hash.end())
                            {
                                ret.push_back(tmp);
                                hash.insert(tmp);
                            }
                        }
                    }
                }
            }
        }
        return ret;
    }
};

在这里插入图片描述

解法二:排序 + 双指针
其实就和三数之和的算法原理是一模一样的。只不过是由固定一个数变成了固定两个数,先固定一个数,然后对后面的数使用三数求和即可。

在暴力枚举的基础上进行的优化,前面做过一道和为s的两个数,如果想到这题,那么四数之和这道题就很好优化。
我们可以先固定一个数,假设这个数的下标是i,那么我们就在数组[i + 1, n - 1](n表示这个数组的长度)这个区间做那个和为s的两个数,只不过这两个数的和是等于nums[i]的相反数。

注意:
1、跟和为s的两个数不同的是,这里不止有一种结果,所以利用双指针找到一组结果后,不要挺,需要缩小范围然后继续找,确保结果没有遗漏。
2、另外一个就是去重问题,首先想到的就是利用容器的特性去重。还有就是找到一种结果之后,可以让指针跳过重复的元素进行去重。

3. 代码编写

C++代码:

class Solution {
public:
    vector<vector<int>> fourSum(vector<int>& nums, int target) {
        vector<vector<int>> ret;
        // 排序
        sort(nums.begin(), nums.end());
        int n = nums.size();
        // if(n < 4)  return ret;
        for(int i = 0; i < n; ){  // 固定第一个数
            for(int j = i + 1; j < n; ){  // 固定第二个数
                // 左右双指针
                int left = j + 1, right = n - 1;
                long long rnum = (long long)target - nums[i] - nums[j];
                while(left < right){
                    if(nums[left] + nums[right] > rnum)  right--;
                    else if(nums[left] + nums[right] < rnum)  left++;
                    else{
                        // 尾插
                        ret.push_back({nums[i], nums[j], nums[left++], nums[right--]});
                        // 对 left 和 right 进行去重处理
                        while(left < right && nums[left] == nums[left - 1])  left++;
                        while(left < right && nums[right] == nums[right + 1])  right--;
                    }
                }
                ++j;
                // 对 j 进行去重处理
                while(j < n && nums[j] == nums[j - 1])  j++;
            }
            ++i;
            // 对 i 进行去重处理
            while(i < n && nums[i] == nums[i - 1])  i++;
        }
        return ret;
    }
};

在这里插入图片描述

C语言代码:

// 比较函数,用于qsort排序
int compare(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}

int** fourSum(int* nums, int numsSize, int target, int* returnSize, int** returnColumnSizes) {
    // 排序
    qsort(nums, numsSize, sizeof(int), compare);

    // 初始化结果数组
    *returnSize = 0;
    int capacity = 100;  // 初始容量,可根据需要调整
    int** ret = (int**)malloc(capacity * sizeof(int*));
    *returnColumnSizes = (int*)malloc(capacity * sizeof(int));
    for (int i = 0; i < numsSize; ) {
        for (int j = i + 1; j < numsSize; ) {
            int left = j + 1, right = numsSize - 1;
            long long rnum = (long long)target - nums[i] - nums[j];
            while (left < right) {
                long long sum = (long long)nums[left] + nums[right];
                if (sum > rnum) {
                    right--;
                } else if (sum < rnum) {
                    left++;
                } else {
                    // 动态分配内存存储结果
                    if (*returnSize >= capacity) {
                        capacity *= 2;
                        ret = (int**)realloc(ret, capacity * sizeof(int*));
                        *returnColumnSizes = (int*)realloc(*returnColumnSizes, capacity * sizeof(int));
                    }
                    ret[*returnSize] = (int*)malloc(4 * sizeof(int));
                    ret[*returnSize][0] = nums[i];
                    ret[*returnSize][1] = nums[j];
                    ret[*returnSize][2] = nums[left];
                    ret[*returnSize][3] = nums[right];
                    (*returnColumnSizes)[*returnSize] = 4;
                    (*returnSize)++;
                    // 去重
                    left++;
                    while (left < right && nums[left] == nums[left - 1]) left++;
                    right--;
                    while (left < right && nums[right] == nums[right + 1]) right--;
                }
            }
            // 去重固定第二个数
            j++;
            while (j < numsSize && nums[j] == nums[j - 1]) j++;
        }
        // 去重固定第一个数
        i++;
        while (i < numsSize && nums[i] == nums[i - 1]) i++;
    }
    // 重新分配内存以匹配实际结果数量
    ret = (int**)realloc(ret, *returnSize * sizeof(int*));
    *returnColumnSizes = (int*)realloc(*returnColumnSizes, *returnSize * sizeof(int));
    return ret;
}

在这里插入图片描述

Python代码:

class Solution:
    def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
        nums.sort()  # 对数组进行排序
        n = len(nums)
        ret = []  # 用于存储结果的列表
        for i in range(n):  # 固定第一个数
            # 去重处理:如果当前数字与前一个数字相同,跳过
            if i > 0 and nums[i] == nums[i - 1]:
                continue
            for j in range(i + 1, n):  # 固定第二个数
                # 去重处理:如果当前数字与前一个数字相同,跳过
                if j > i + 1 and nums[j] == nums[j - 1]:
                    continue
                left, right = j + 1, n - 1  # 左右双指针
                rnum = target - nums[i] - nums[j]  # 剩余的目标值
                while left < right:
                    total = nums[left] + nums[right]
                    if total > rnum:
                        right -= 1
                    elif total < rnum:
                        left += 1
                    else:
                        # 找到一个满足条件的四元组
                        ret.append([nums[i], nums[j], nums[left], nums[right]])
                        # 去重处理
                        while left < right and nums[left] == nums[left + 1]:
                            left += 1
                        while left < right and nums[right] == nums[right - 1]:
                            right -= 1
                        left += 1
                        right -= 1
        return ret

在这里插入图片描述


网站公告

今日签到

点亮在社区的每一天
去签到