​leetcode算法题第18题:四数之和​

本文详细介绍了LeetCode第18题的解决方案——四数之和。通过排序数组并使用双指针技巧,有效解决寻找满足特定条件的不重复四元组问题。该方法首先对数组进行升序排序,然后利用两层循环结构配合双指针移动,确保找到所有可能的组合。在遍历过程中,通过比较当前和目标值的关系调整指针位置,避免重复元素的出现。这种方法对于理解和实践排序及双指针算法有很好的启示作用。

leetcode算法题第18题:四数之和


题目:

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

1. 0 <= a, b, c, d < n
2. a、b、c 和 d 互不相同
3. nums[a] + nums[b] + nums[c] + nums[d] == target

你可以按 任意顺序 返回答案 。

 


解题思路:排序+双指针

和三数之和一样,四数之和只是嵌套了两重循环,然后又用双指针不断更新四数之和的大小,具体做法 如下:

1.给数组排序,从小到大排序

2.先套用第一层循环i从0遍历到倒数第三个数,要先判断如果三个最小数都大于tagat,则返回空集合,如果三个最大的数都小于tagat,则i向右移动一位

3.再套用第二层循环j从i+1遍历到倒数第二个数,也要先判断三个最小值大于tagat,则结束第二层循环,执行第一层循环,如果三个最大值小于tagat,则j直接向右移动一位

4.在两重循环中,建立双指针left和right,left的初始位置为j+1向右遍历,right的位置则从最后一位向左遍历,每次判断四个数的和与tagat的大小,如果相等则添加进设定好的空列表,且此时left要向右移动一位,right则要向左移动一位;如果此时四个数的和大于tagat,则right向左移动一位;小于tagat,则left向右移动一位。直到left和right重叠相等。

注意:因为题目中不能出现重复的选项,所以每次移动遍历时,如果后一个数和前一个数相等,则要直接跳过个数,遍历下一个,直到不重复为止


代码:

 

 

<think>嗯,用户想用C语言编写一个高效的算法来计算两个等长升序数组合并后的中位数。首先,我得确定中位数的定义,合并后的数组如果是偶数个元素,中位数是中间两个数的平均值,奇数的话就是中间那个数。但用户提到两个等长数组,合并后的长度是2n,所以中位数应该是第n和n+1个元素的平均数?或者可能目里要求的是更简化的方式? 然后,用户需要高效的时间和空间复杂度。直接合并再排序的话,时间复杂度是O(n log n),比如用引用[4]的方法,合并后排序,但这样显然不够高效,尤其是当数组已经有序的情况下。应该用更高效的方法,比如双指针合并,这样时间复杂度是O(n),但空间复杂度也是O(n)。不过用户可能希望更优的空间复杂度,比如O(1)。 不过,可能还有更优的方法。比如,二分查找的方法。因为两个数组都是升序的,可以不需要实际合并数组,而是通过比较中位数的位置来逐步缩小范围。比如,类似求两个有序数组的中位数的算法,时间复杂度可以降到O(log n),空间复杂度O(1)。例如,参考LeetCode上的那道,虽然那里是两个可能不同长度的数组,但这里的等长可能可以简化算法。 那具体怎么做呢?对于两个等长的数组,假设长度为n,合并后的中位数是第n大的数。或者如果是合并后总长度是2n,那么中位数是第n和n+1的平均?或者根据目定义,可能直接取第n大的数?需要明确用户的具体需求。 比如,假设合并后的中位数是第n大的元素,那么如何在不合并的情况下找到这个元素?可以用二分法每次排除不可能的部分。例如,比较两个数组的中位数,根据大小决定舍弃哪一部分。 不过,这种情况下,算法可能需要处理边界条件较多,但时间复杂度是O(log n)。这可能比合并数组的O(n)时间更好,尤其是在n较大的时候。 所以,用户的问可能有两种解法:一种是双指针合并,时间O(n),空间O(n);另一种是二分查找,时间O(log n),空间O(1)。用户希望高效的时间和空间,所以可能更倾向于后者。 然后,我需要用C语言实现这个算法。例如,对于双指针方法,可以创建一个数组,逐个比较两个数组的元素,按顺序放入新数组,直到达到中位数的位置。这样时间复杂度O(n),空间O(n)。但如果是更高效的,比如二分法,需要详细设计步骤。 比如,在二分法中,可以比较两个数组的中位数,假设a的中位数是mid1,b的是mid2。如果mid1 < mid2,那么中位数一定在a的后半部分和b的前半部分之间。这样递归或循环下去,直到找到中位数。 不过具体实现可能比较复杂。比如,参考LeetCode的解法,可能需要处理奇偶长度,但这里两个数组是等长的,总长度是偶数,所以中位数是两个中间数的平均值。所以需要找到第n和n+1大的数。 或者,可能目要求的是合并后的数组的中位数,但合并后的数组是有序的,所以中位数可以直接由两个数组的中位数位置得到,不需要实际合并。例如,使用两个指针遍历两个数组,直到找到中间的两个数。 比如,用类似归并排序中的合并步骤,同时计数,当达到第n个元素时,记录这两个数,计算平均值。这样的时间复杂度是O(n),空间复杂度O(1)(如果不需要实际存储合并后的数组的话)。 但用户要求高效的时间和空间,可能更希望O(log n)的时间。这时候需要考虑二分法。 举个例子,假设数组a和b的长度都是n。要找合并后的中位数,即第n和n+1个数,然后取平均。或者可能目中的中位数定义为合并后的中间位置的值,不管奇偶,但通常如果是偶数,可能需要平均。需要明确。 假设需要找到第n和n+1大的数,取平均。那么,可以使用两个指针i和j,初始指向a和b的起始位置。然后比较a[i]和b[j],较小的那个指针后移,同时计数,直到到达n次。这样就能找到第n和n+1的元素。但这种方法的时间复杂度是O(n),虽然空间是O(1)。 但有没有更高效的方法?比如二分法。 或者,可能使用类似于寻找两个有序数组的中位数的算法,这里因为数组长度相等,可以简化步骤。例如,对于两个数组a和b,各自取中位数ma和mb。如果ma < mb,则中位数位于a的后半部分和b的前半部分;否则位于a的前半和b的后半。这样每次排除一半的元素,直到找到中位数。 例如,参考这个思路: 1. 找到两个数组的中位数ma和mb。 2. 比较ma和mb: - 如果ma == mb,则这就是中位数。 - 如果ma < mb,则中位数位于a的后半部分和b的前半部分。 - 否则,位于a的前半和b的后半。 3. 递归地在剩下的部分继续查找,直到找到中间的值。 不过,这样的实现需要考虑奇偶长度,但这里数组长度相等,可能简化处理。 或者,使用分治法,每次比较两个数组的中间元素,将搜索范围缩小一半,直到找到中位数。这样的时间复杂度是O(log n),因为每次递归处理的数据量减半。 举个例子,假设数组a和b各有n个元素,合并后的中位数是第n大的元素。例如,当n=3时,合并后的数组长度是6,中位数是第3和第4元素的平均。但根据目定义,可能需要具体分析。 可能用户的问中,中位数的定义是合并后的中间位置的数,如果是总长度是偶数,可能需要取平均。例如,两个长度为n的数组合并后总长度是2n,中位数是第n和n+1的平均值。 那么,如何高效地找到这两个数而不合并数组? 可以考虑同时遍历两个数组,用两个指针i和j,初始为0。然后比较a[i]和b[j],较小的数前进,同时记录当前的步数,直到走到第n-1和n步的位置,得到两个数。例如,当总步数达到n次时,当前数和下一个数即为所需的两个数。这样时间复杂度是O(n),空间O(1)。 但用户可能希望更高效,比如O(log n)的时间。这时候需要考虑二分法。 另一种方法是,利用数组的有序性,寻找两个数组中的分割点,使得左边的元素都小于右边的元素,并且分割点左右两边的元素数量相等。例如,类似LeetCode上的解法,但针对等长的情况进行调整。 具体来说,假设每个数组的长度为n,我们需要找到分割点i和j,使得i + j = n,并且a[i-1] <= b[j]和b[j-1] <= a[i]。这样,中位数就是(max(a[i-1], b[j-1]) + min(a[i], b[j]))/2。 这种方法的时间复杂度是O(log n),因为使用二分查找来确定i的位置。 具体实现步骤如下: 1. 确定数组a和b的长度n。 2. 使用二分查找在数组a中寻找合适的分割点i,使得对应的j = n - i,满足条件。 3. 调整i的范围,直到找到正确的分割点。 4. 计算中位数。 这样可以在O(log n)时间内完成,空间复杂度O(1)。 现在,我需要用C语言实现这个算法。需要注意的是处理边界条件,比如i=0或i=n的情况,这时候对应的j可能超出数组范围,需要特殊处理。 例如,当i=0时,数组a的所有元素都在右半部分,左半部分的最大值是b[j-1]。当j=0时,类似的情况。 此外,需要确保分割后的左边元素全部小于右边的元素,即a[i-1] <= b[j]和b[j-1] <= a[i]。 然后,找到正确的i的位置,可以通过二分查找来调整i的值。 实现的大致步骤: - 初始化low = 0,high = n。 - 进行二分查找,计算i = (low + high)/2,j = n - i。 - 检查a[i-1] <= b[j]和b[j-1] <= a[i],如果满足条件,则找到分割点。 - 否则,调整low或high的值继续查找。 找到分割点后,计算左边最大值和右边最小值,取平均即为中位数。 但需要注意数组的边界情况,例如当i=0时,a中没有左半元素,左边最大来自b[j-1];当j=0时同理。 这样的算法时间复杂度是O(log n),满足高效的要求。 现在,编写C代码实现这个逻辑。需要注意数组的索引是否正确,以及处理奇偶长度的情况,但在这个问中,两个数组等长,总长度为偶数,所以中位数是平均值。 例如,合并后的数组长度为2n,中位数是第n和n+1小的数的平均。找到这两个数,可以通过上述的分割点方法,找到左边最大的两个数,或者右边最小的两个数,然后取平均。 总结,用户的问需要高效算法,应该使用二分法,时间复杂度O(log n),空间O(1)。而双指针方法虽然容易实现,但时间复杂度是O(n),可能不够高效。因此,选择二分法实现。 现在,具体代码的编写: 首先,处理数组长度n,假设数组a和b的长度都是n。如果n=0,返回0。否则,进行二分查找。 在二分查找中,确定i的范围,并在每次循环中计算i和j,然后比较边界条件。 例如,初始化low=0,high=n。循环直到找到正确的i。 在每次迭代中: i = (low + high)/2; j = n - i; 检查是否a[i-1] <= b[j]和b[j-1] <= a[i]。如果满足,则分割点正确。 否则,如果a[i-1] > b[j],说明i太大,需要减小i,即调整high = i-1。 否则,如果b[j-1] > a[i],说明i太小,需要增大i,即调整low = i+1。 循环直到找到正确的i,或者low超过high。 处理边界条件,例如i=0或i=n的情况: 当i=0时,j=n,此时a的所有元素都在右边,左边最大是b[j-1]=b[n-1]。需要检查是否b[n-1] <= a[0]。 同样,当i=n时,j=0,左边最大是a[n-1],需要检查是否a[n-1] <= b[0]。 找到正确的i和j后,计算max_left和min_right,取平均。 现在,编写代码时需要注意数组的索引是否正确,因为C语言中数组0开始。例如,当i=0时,a的左半部分为空,所以左边最大值是b[j-1],而j此时为n,所以j-1 =n-1。 此外,当i=0时,检查b[j-1] <= a[i],即b[n-1] <=a[0]。如果成立,则满足条件。 同样,当i=n时,检查a[i-1] <= b[j],即a[n-1] <=b[0]。 现在,编写代码: double findMedianSortedArrays(int* a, int* b, int n) { if (n == 0) { return 0.0; // 处理空数组的情况 } int low = 0, high = n; int i, j; int max_left, min_right; while (low <= high) { i = (low + high) / 2; j = n - i; // 处理i=0的情况,即a数组全在右半部分 if (i == 0) { if (j == n || b[j-1] <= a[0]) { // 找到分割点 break; } else { low = i + 1; continue; } } // 处理i=n的情况,即a数组全在左半部分 else if (i == n) { if (j == 0 || a[n-1] <= b[0]) { break; } else { high = i - 1; continue; } } // 正常情况检查条件 if (a[i-1] > b[j]) { high = i - 1; } else if (b[j-1] > a[i]) { low = i + 1; } else { // 满足条件,退出循环 break; } } // 计算max_left和min_right if (i == 0) { max_left = b[j-1]; } else if (j == 0) { max_left = a[i-1]; } else { max_left = (a[i-1] > b[j-1]) ? a[i-1] : b[j-1]; } if (i == n) { min_right = b[j]; } else if (j == n) { min_right = a[i]; } else { min_right = (a[i] < b[j]) ? a[i] : b[j]; } return (max_left + min_right) / 2.0; } 不过,这里假设数组的索引是正确的。例如,当i=0时,j=n,此时检查b[j-1]是否<=a[0]。但此时j=n,所以b[j]不存在,所以当i=0时,必须保证j<=n,而j=n的情况下,b数组的左半部分是整个数组,右半部分为空。因此,当i=0时,分割后的左边最大是b[n-1],右边最小是a[0],需要满足b[n-1] <=a[0]。 同样,当i=n时,分割后的左边最大是a[n-1],右边最小是b[0],需要满足a[n-1] <=b[0]。 这个算法的时间复杂度是O(log n),空间复杂度O(1),满足用户的要求。 测试这个算法需要不同的情况,例如: 案例1: a = [1,3,5], b = [2,4,6], n=3. 合并后的数组是[1,2,3,4,5,6],中位数是(3+4)/2=3.5. 根据算法,分割点i在a中的位置可能是2(因为a[2]=5,分割后左边是[1,3], 右边是5;b中j=1,分割左边是[2],右边是[4,6]。此时左边最大是max(3,2)=3,右边最小是min(5,4)=4,平均3.5。正确。 案例2: a=[1,2], b=[3,4], n=2. 合并后的中位数是(2+3)/2=2.5. 算法中的分割点i可能在1,j=1.左边最大是max(a[0],b[0])=max(1,3)=3?或者可能分割点需要重新计算。 可能需要调整代码中的逻辑。或者可能我的代码有错误。需要详细分析。 可能我的算法实现中存在错误,需要进一步调试。例如,在案例2中,数组a=[1,2], b=[3,4], n=2。此时,分割点i应该是多少? 根据算法,初始low=0, high=2. 第一次循环: i = (0+2)/2=1,j=2-1=1. 检查a[i-1] =a[0]=1 <= b[j]=b[1]=4,成立。 检查b[j-1]=b[0]=3 <=a[i]=a[1]=2?不成立。所以条件不满足,需要调整。因为b[j-1]=3 > a[i]=2,所以应该增加i,即low=1+1=2. 此时low=2, high=2,进入循环: i=(2+2)/2=2,j=2-2=0. 检查i=2是否等于n=2,是。此时检查a[n-1]=a[1]=2 <=b[0]=3,成立。所以分割点i=2,j=0. 计算max_left:i=2,j=0。因为j=0,所以max_left =a[i-1]=a[1]=2. min_right:i=2,超出数组长度,所以取b[j]=b[0]=3. 所以中位数是(2+3)/2=2.5,正确。 所以代码在这种情况下正确。 另一个测试案例:a=[1,3], b=[2,4], n=2.合并后的数组是[1,2,3,4],中位数(2+3)/2=2.5. 算法中的分割点i=1, j=1. a[i-1]=a[0]=1 <=b[j]=4,成立。b[j-1]=2 <=a[1]=3,成立。所以分割点正确。max_left=max(1,2)=2,min_right=min(3,4)=3,平均2.5,正确。 这说明代码正确。 因此,这个算法是可行的。</think>### 分步解答 要高效计算两个等长升序数组的中位数,可以采用**二分查找法**,时间复杂度为$O(\log n)$,空间复杂度为$O(1)$。核心思想是通过比较两个数组的中位点,逐步缩小搜索范围,无需实际合并数组。 #### 算法步骤 1. **初始化**:定义两个数组的搜索范围`low=0`和`high=n`(n为数组长度)。 2. **二分查找**:计算分割点`i`和`j`(满足`i + j = n`),比较`a[i-1]`与`b[j]`、`b[j-1]`与`a[i]`,调整搜索范围: - 若`a[i-1] > b[j]`,说明`i`过大,需减小`i`(`high = i-1`)。 - 若`b[j-1] > a[i]`,说明`i`过小,需增大`i`(`low = i+1`)。 3. **计算中位数**:找到分割点后,左半部分最大值为`max_left = max(a[i-1], b[j-1])`,右半部分最小值为`min_right = min(a[i], b[j])`,中位数为二者的平均值。 #### C语言实现 ```c double findMedianSortedArrays(int* a, int* b, int n) { if (n == 0) return 0.0; int low = 0, high = n; int i, j, max_left, min_right; while (low <= high) { i = (low + high) / 2; j = n - i; // 处理边界条件 if (i > 0 && j < n && a[i-1] > b[j]) { high = i - 1; } else if (j > 0 && i < n && b[j-1] > a[i]) { low = i + 1; } else { // 找到合适的分割点 if (i == 0) max_left = b[j-1]; else if (j == 0) max_left = a[i-1]; else max_left = (a[i-1] > b[j-1]) ? a[i-1] : b[j-1]; if (i == n) min_right = b[j]; else if (j == n) min_right = a[i]; else min_right = (a[i] < b[j]) ? a[i] : b[j]; return (max_left + min_right) / 2.0; } } return 0.0; } ``` #### 示例说明 假设输入为`a = [1, 3, 5]`和`b = [2, 4, 6]`,合并后的有序数组为`[1, 2, 3, 4, 5, 6]`,中位数为$(3+4)/2 = 3.5$。通过上述算法,分割点`i=2`(a的分割为`[1,3 | 5]`),`j=1`(b的分割为`[2 | 4,6]`),计算得到`max_left = 3`和`min_right = 4`,最终结果为$3.5$。 #### 复杂度分析 - **时间复杂度**:每次循环将搜索范围缩小一半,因此为$O(\log n)$。 - **空间复杂度**:仅使用固定变量,为$O(1)$[^4]。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值