Maximum Gap

本文探讨了如何在线性时间和空间复杂度下找到未排序数组排序后连续元素的最大差值。提出了三种解决方案:排序比较、桶排序及基数排序,并给出了具体实现代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目164:Maximum Gap

题目描述:
Given an unsorted array, find the maximum difference between the successive elements in its sorted form.

Try to solve it in linear time/space.

Return 0 if the array contains less than 2 elements.

You may assume all elements in the array are non-negative integers and fit in the 32-bit signed integer range.
题目分析:
首先题意要看懂的,一个没有排序的数组,排序后找到连续的元素间最大的差值,时间复杂度和空间复杂度是线性的。

1. 排序再比较

直接上代码:

class Solution {
public:
    int maximumGap(vector<int>& nums) {
        int len = nums.size();
        if (len < 2)
            return 0;
        sort(nums.begin(), nums.end());
        int maxLen, i;
        maxLen = 0;
        for (i = 1; i < len; ++ i) {
            maxLen = max(maxLen, nums[i] - nums[i - 1]);
        }
        return maxLen;
    }
};

sort排序的时间复杂度是O(N log N),不符合要求。

2. 桶排序

思路2:
将n个数放在n + 1个桶内,肯定有一个是空桶,且最⼤差值出现在⼀一个非空桶的最⼤大值和下一 个非空桶的最小值之间。最大值x, 最⼩值y, 如果x == y,则最大差值是0;否则,将n个数放进(n + 1)个桶
每个桶⼤小是d = (x – y) / (n + 1) +1,向上取整。每个桶区间是[y + i * d, y + (i + 1) * d) (i=0,1,..n) ,注意是左闭右开的区间,最大的数先不放在桶里。

#include <vector>
/* INT_MAX、INT_MIN的头文件C中是limits.h */
#include <climits>
class Solution {
public:
    int maximumGap(vector<int>& nums) {
        int len = nums.size();
        if (len < 2)
            return 0;
        int maxi, mini, i;
        maxi = nums[0];
        mini = nums[0];
        for (auto num : nums) {
            maxi = max(num, maxi);
            mini = min(num, mini);
        }
        if (maxi == mini)
            return 0;
        /* 注意是向上取整,否则当maxi-mini < len + 1时出错 */
        int gap = (maxi - mini) / (len + 1) + 1;
        vector<int> maxV(len + 1, INT_MIN);
        vector<int> minV(len + 1, INT_MAX);
        for (i = 0; i < len; i ++) {
            if (nums[i] != maxi) {
                int bucketId = (int)((nums[i] - mini) / gap);
                maxV[bucketId] = max(maxV[bucketId], nums[i]);
                minV[bucketId] = min(minV[bucketId], nums[i]);
            }
        }
        int ret = 0;
        int curMax = maxV[0];
        for (i = 1; i < len + 1; i ++) {
            if (maxV[i] != INT_MIN) {
                ret = max(ret, minV[i] - curMax);
                curMax = maxV[i];
            }
        }
        ret = max(ret, maxi - curMax);
        return ret;
    }
};

如果不是向上取整,当maxi - mini < len + 1时,gap == 0,在求nums[i]落在哪个桶内时出现除以0的问题,leetcode上报错是runtime error,一般遇到的是内存访问出错,gcc编译OK,运行报错Floating point exception,谷歌了一下,有说gcc版本的问题,实际上是,除0了,i / 0 或是 i % 0 都会报浮点错误。数据类型也是需要注意的。

3. 基数排序

思路3:
基数排序应该是也可以的,参考的别人的代码,先直接上代码。

include <iostream>
include <map>
include <vector>
using namespace std;
class Solution {
    public:
        int maximumGap(vector<int> &nums) {
            multimap<int,int> radix_map;
            int divider=10,index=1,max_index=10;
            while(index<=max_index){
                for(auto num:nums){
                    int temp = num,i=1;
                    while(i<index){
                        temp = num/divider;
                        i++;
                    }
                radix_map.insert(make_pair(temp,num));
                }
                nums.clear();
                for(auto num_pair:radix_map){
                    nums.push_back(num_pair.second);
                }
                radix_map.clear();
                index++;
            }
            int max_gap = 0;
            for(int i=1;i<nums.size();i++){
                if(nums[i]-nums[i-1]>max_gap){
                    max_gap = nums[i]-nums[i-1];
                }
            }
            return max_gap;
        }
};
<think>好的,我现在需要分析用户提供的这段Python代码。首先,用户希望我介绍这段代码的功能和结构。让我仔细看一下代码的结构和各个部分的作用。 首先,代码定义了一个名为Solution的类,里面包含了几个方法:quicksort、partition和maximumGap。看起来这是一个实现快速排序并结合排序结果计算最大间隔的问题。可能题目是LeetCode上的“Maximum Gap”问题,因为maximumGap这个方法名很符合那个题目的要求。 接下来,我注意到quicksort方法被用来对数组进行排序。用户在这里使用了递归实现的快速排序,并且还调用了partition方法。这里的快速排序似乎不是标准的实现,可能使用了三向切分(Dijkstra的三向切分法)来优化重复元素的处理。这一点需要仔细看partition方法的逻辑。 在quicksort方法中,首先处理递归终止条件,当左边界L大于等于右边界R时返回。然后有一个随机交换的操作,将nums[R]和一个随机位置(在L到R之间)的元素交换。这可能是在选择pivot时采用随机化策略,以避免最坏情况的时间复杂度。接着调用partition方法,返回a和b,然后递归处理左右两个子数组。这里的a和b可能是划分后的边界,这样中间的等于pivot的部分不需要再处理,从而优化了重复元素的情况。 接下来看partition方法。这个方法里有三个指针:less、more和pos。初始时,less指向L,more指向R,target是选中的pivot(nums[R])。然后pos从L开始遍历,直到pos遇到more。在循环中,如果当前元素大于target,就将其与more-1位置的元素交换,并移动more指针;如果等于target,pos右移;如果小于target,则交换到less的位置,并移动less和pos。最后,将pos位置的元素和more交换,可能将pivot放到正确的位置。最后返回的是less-1和more,这应该表示小于pivot的区域的右边界和大于pivot区域的左边界,中间是等于pivot的部分。这样,在递归调用quicksort时,只需要处理左边(L到a)和右边(b到R),中间的部分已经排好序了。 然后看maximumGap方法。这个方法首先处理长度小于2的情况,返回0。否则,调用quicksort对数组进行排序,然后遍历排序后的数组,计算相邻元素的最大差值。初始时,d被赋值为第一个差值,然后在循环中更新为最大的差值。最后返回d。 现在,我需要确认这段代码的正确性。首先,快速排序的实现是否正确,尤其是partition方法。标准的快速排序的partition通常返回一个索引,将数组分为左右两部分,但这里的partition返回两个值,这可能是因为三向切分,将数组分为小于、等于、大于三个部分。因此,递归调用的时候,左边处理L到a,右边处理b到R,中间的部分不需要处理,因为已经是等于pivot的元素,排好序了。这一点需要验证是否正确。 比如,在partition结束后,假设数组被分成三个部分:L到a是小于pivot的部分,a+1到b-1是等于pivot的部分,b到R是大于pivot的部分。因此,递归调用quicksort时,左边是L到a,右边是b到R。这样是否正确? 在quicksort中,调用partition后的递归部分是self.quicksort(nums,L,a)和self.quicksort(nums,b,R)。那么中间的a+1到b-1的部分不需要处理,因为它们已经是等于pivot的元素,排好了顺序。所以这里的递归划分是正确的。 但是,在partition方法中,最后返回的是less-1和more。需要仔细跟踪变量变化。例如,假设循环结束后,pos等于more。然后交换nums[pos]和nums[more],这里可能有问题,因为more原本是R,而pos此时可能等于more。比如,假设在循环结束时,pos=more,此时交换nums[pos]和nums[more](即自己交换),这可能没有必要,或者是否应该交换? 或者,可能循环结束时,pos的位置是处理完所有元素后的位置。例如,在循环中,当nums[pos]大于target时,交换到more-1,然后more减1,此时pos的位置不变,继续处理这个元素。直到nums[pos]不再大于target。当循环结束时,pos等于more,此时将nums[pos]与nums[more](也就是原来R的位置)交换。这时候可能将pivot(nums[R])放到正确的位置。比如,假设原来的pivot被存在R的位置,在循环结束后,将nums[pos](此时pos=more)与nums[R]交换,这样pivot就被放到正确的位置,即more的位置。这可能有问题吗?需要更仔细分析。 举个例子,比如初始情况下,nums[R]是pivot。在循环处理过程中,所有元素被分为小于、等于、大于三个区域。循环结束时,pos等于more。此时交换nums[pos]和nums[R],也就是将pivot放到more的位置。这样,在数组的末尾,pivot会被放到正确的位置吗? 或者可能,这里的partition逻辑与标准的三向切分不同。例如,标准的三向切分通常将数组分为小于pivot、等于pivot、大于pivot三个部分,而partition返回的是左右边界。而这里的处理可能在交换后,将pivot放到中间的位置。 这部分可能需要具体例子来测试。比如,假设数组是[3,1,2,3,4,5],pivot是最后一个元素5。那么在partition过程中会发生什么?或者可能这个例子不太合适,需要再想一个例子。 另一个需要注意的点是,在quicksort方法中,交换nums[R]和一个随机位置的元素,然后以nums[R]作为pivot。这是标准的随机化选择pivot的方法,可以避免最坏情况的时间复杂度。 现在,关于maximumGap方法,它是否正确?假设数组已经排序完成,那么相邻元素的最大差值是正确的。但需要注意,当数组长度为0或1时返回0,否则遍历数组计算最大差值。这部分逻辑是对的。 接下来,需要确认这段代码是否存在潜在的问题。例如,在递归深度较大的情况下,是否会导致栈溢出?用户在这里设置了sys.setrecursionlimit(30000000),这可能是因为Python的默认递归深度限制较小,当处理大规模数据时容易导致栈溢出。但设置这么大的递归深度可能并不安全,因为Python的栈深度是有限的,尤其是在处理最坏情况下的快速排序时,递归深度可能达到O(n),对于大规模数据来说这可能会导致栈溢出错误。但是,由于这里使用了随机化pivot选择,快速排序的期望递归深度是O(log n),所以可能对于大多数情况来说是可以处理的。但这也取决于Python的环境设置。 另一个可能的问题是,快速排序是否是原地排序的,是否会修改原始数组。是的,这里的quicksort是原地排序,直接修改nums数组。在maximumGap方法中,调用了self.quicksort(nums,0,len(nums)-1),这会直接对传入的nums进行排序。然而,在Python中,列表是可变对象,作为参数传递时是引用传递。因此,这可能会导致原始数组被修改。但在这个问题中,maximumGap可能不需要保留原数组的顺序,所以这样处理是没问题的。 现在,关于这段代码的效率,快速排序的平均时间复杂度是O(n log n),而三向切分在处理重复元素时更有效。但LeetCode中的Maximum Gap问题要求线性时间复杂度和线性空间复杂度,而基于比较的排序算法如快速排序的时间复杂度是O(n log n),无法满足线性时间的要求。因此,正确的解法应该使用基数排序或桶排序,这样可以在O(n)时间内完成排序,然后计算最大间隔。但用户的这段代码显然使用了快速排序,因此在最坏情况下的时间复杂度是O(n^2),无法通过LeetCode上的时间限制,特别是对于大规模测试用例。 例如,LeetCode的164题Maximum Gap的官方解法要求O(n)时间,而使用基于比较的排序的解法可能无法通过时间限制。因此,这段代码虽然在逻辑上是正确的,但在效率上可能不满足题目要求,导致超时。 另外,在maximumGap方法中,排序后的数组遍历计算最大差值是否正确?是的,因为排序后的数组相邻元素的最大差值必定存在于某个相邻元素之间。例如,排序后的数组是递增的,所以遍历一次即可找到最大差值。 总结:这段代码实现了快速排序的三向切分优化版本,并用排序后的数组计算最大相邻差值。然而,由于快速排序的时间复杂度问题,在处理大规模数据时可能效率不足,不符合题目要求的线性时间复杂度。但代码逻辑上是正确的,可以正确计算出排序后的最大间隔。</think>让我们逐步分析这段代码的结构和功能: ### 一、代码结构解析 ```python class Solution(object): # 系统设置递归深度(避免快速排序递归过深报错) import sys import random sys.setrecursionlimit(30000000) def quicksort(self, nums, L, R): # 快速排序主函数 if L >= R: return nums # 随机选择轴点并交换到末尾 nums[random.randint(L,R)], nums[R] = nums[R], nums[random.randint(L,R)] # 三向切分 a, b = self.partition(nums, L, R) # 递归处理左右子区间 self.quicksort(nums, L, a) self.quicksort(nums, b, R) return nums def partition(self, nums, L, R): # 三向切分核心算法 less = L # 小于区的右边界 more = R # 大于区的左边界 target = nums[R] # 轴点值 pos = L # 当前扫描指针 while pos < more: if nums[pos] > target: nums[pos], nums[more-1] = nums[more-1], nums[pos] more -= 1 elif nums[pos] == target: pos += 1 else: nums[pos], nums[less] = nums[less], nums[pos] less += 1 pos += 1 # 将轴点放入正确位置 nums[pos], nums[more] = nums[more], nums[pos] return less-1, more+1 def maximumGap(self, nums): # 主功能函数 if len(nums) < 2: return 0 self.quicksort(nums, 0, len(nums)-1) # 先排序 d = 0 for i in range(1, len(nums)): # 计算最大间隔 d = max(d, nums[i] - nums[i-1]) return d ``` ### 二、关键算法说明 #### 1. 三向切分快速排序 - **核心思想**:将数组划分为三个区域: - $[L, a]$:**小于**轴点 - $(a, b)$:**等于**轴点 - $[b, R]$:**大于**轴点 - **优势**:高效处理重复元素,时间复杂度保持$O(n \log n)$ #### 2. 最大间隔计算 - **排序后特性**:最大差值必定出现在相邻元素之间 - **时间复杂度**:$O(n)$线性扫描 ### 三、潜在问题分析 #### 1. 递归深度风险 虽然通过`sys.setrecursionlimit`设置了递归深度,但对于完全倒序的极端情况,仍可能触发栈溢出。 #### 2. 时间复杂度局限 - **理论瓶颈**:快速排序平均$O(n \log n)$,但题目要求线性时间解法(应使用桶排序) - **LeetCode 164**:该代码无法通过大规模测试用例 ### 四、改进建议 ```python # 桶排序实现(符合题目要求) def maximumGap(self, nums): if len(nums) < 2: return 0 max_num, min_num = max(nums), min(nums) bucket_size = max(1, (max_num - min_num) // (len(nums)-1)) bucket_num = (max_num - min_num) // bucket_size + 1 buckets = [[] for _ in range(bucket_num)] for num in nums: idx = (num - min_num) // bucket_size buckets[idx].append(num) prev_max = float('inf') max_gap = 0 for bucket in filter(lambda x: x, buckets): curr_min, curr_max = min(bucket), max(bucket) if prev_max != float('inf'): max_gap = max(max_gap, curr_min - prev_max) prev_max = curr_max return max_gap ``` ### 五、总结对比 | 特性 | 当前代码 | 改进方案 | |-------------|---------------|---------------| | 时间复杂度 | $O(n \log n)$ | $O(n)$ | | 空间复杂度 | $O(1)$ | $O(n)$ | | 通过LeetCode | ❌ | ✅ | | 适用场景 | 小规模数据 | 任意规模数据 | 建议在面试中遇到此类问题时,优先选择桶排序方案以满足题目要求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值