【一次过】Lintcode 548.两数组的交 II

本文介绍了一种使用HashMap实现的寻找两个整数数组交集的方法,并针对不同情况提出了优化方案,如数组已排序时采用双指针法等。

 

计算两个数组的交

样例

nums1 = [1, 2, 2, 1], nums2 = [2, 2], 返回 [2, 2].

挑战

  • What if the given array is already sorted? How would you optimize your algorithm?
  • What if nums1's size is small compared to num2's size? Which algorithm is better?
  • What if elements of nums2 are stored on disk, and the memory is limited such that you cannot load all elements into the memory at once?

解题思路:

    我们换一种数据结构,用HashMap<int,int>存储,遍历数组1中所有元素的时,key为数组元素,value为该数组元素出现的次数。遍历数组2中元素时,如果HashMap中元素的value值大于0,则value--,并将该元素放入结果数组中,否则continue。最后返回结果数组。

public class Solution {
    /**
     * @param nums1: an integer array
     * @param nums2: an integer array
     * @return: an integer array
     */
    public int[] intersection(int[] nums1, int[] nums2) {
        // write your code here
        Map<Integer, Integer> map = new HashMap<>();
        for(int i : nums1){
            if(map.get(i) == null)
                map.put(i, 1);
            else
                map.put(i, map.get(i)+1);
        }
        
        List<Integer> list = new ArrayList<>();
        
        for(int i : nums2){
            if(map.get(i) != null && map.get(i) > 0){
                list.add(i);
                map.put(i, map.get(i)-1);
            }
        }
        
        int[] res = new int[list.size()];
        for(int i=0; i<res.length; i++)
            res[i] = list.get(i);
        
        return res;
    }
}

挑战:

如果数组全为有序数组,则可以采用双指针法,设置p1与p2分别遍历指向nums1和nums2,当*p1==*p2则保存进结果数组,否则让小的指针++,直到有一方数组遍历完毕结束,代码就不放了。

 

 

<think>嗯,我现在需要解决LintCode 406题,也就是和大于S的最小子数问题。题目是说给定一个由n个正整数成的数和一个正整数s,找出该数中满足其和≥s的最小长度子数,如果无解就返回-1。我得用C++来实现这个解法。首先,我需要仔细理解问题,然后思考可能的解题方法。 首先,题目要求的是子数的和至少为s,并且这个子数的长度要尽可能小。子数是数中连续的元素成的,所以不能打乱顺序。我需要找到一个连续的子数,其和大于等于s,同时长度最短。 我记得之前学过的滑动窗口(双指针)方法可能适用这种情况。因为滑动窗口通常用于处理连续子数的和的问题,尤其是当数元素都是正数的时候。这种情况下,滑动窗口的时间复杂度可以做到O(n),这应该是比较高效的。 让我再仔细想想滑动窗口的思路。滑动窗口通常维护一个窗口,用左右两个指针,右指针不断向右移动,直到窗口内的和满足条件,然后尝试移动左指针尽可能缩小窗口,同时记录最小的窗口长度。这样,每次右指针移动后,左指针可能移动多次,直到窗口和再次小于s,然后继续移动右指针。整个过程只需要遍历数两次,所以时间复杂度是O(n)。 比如,假设数是[2,3,1,2,4,3],s=7。初始时左右指针都在0位置,sum=0。然后右指针不断移动,sum增加,直到sum≥7。比如当右指针移动到3的位置(元素是2,此时sum=2+3+1+2=8),这时候满足条件。然后左指针开始移动,看看能不能缩小窗口。左指针移动到1,sum变为3+1+2=6,这时候不满足,于是右指针继续移动。当右指针移动到4的位置(元素4),sum=3+1+2+4=10≥7。左指针再移动,此时左指针在1的位置,右指针在4,窗口长度是4(元素3,1,2,4),长度是4。然后继续移动左指针到2,sum=1+2+4=7,满足条件,此时窗口长度是3。接着继续移动左指针到3,sum=2+4=6,不满足,所以右指针继续移动到5(元素3),sum=2+4+3=9≥7。左指针移动到4,sum=4+3=7,此时窗口长度是2。所以最终最小的长度是2。 这个例子中,滑动窗口方法确实有效。那为什么滑动窗口适用于这个问题呢?因为数中的元素都是正整数,当窗口扩大时,sum增加,缩小窗口时sum减少。因此,一旦窗口内的sum≥s,我们可以安全地移动左指针,缩小窗口,而不会错过更小的可能解。如果数中有负数,那么这种方法可能不适用,因为当窗口缩小时sum可能反而增加,导致无法正确判断。 所以解题步骤大概是这样的: 初始化左指针left=0,右指针right=0,当前窗口的和sum=0,最小长度min_len设为无穷大。然后循环遍历数: 1. 将右指针的元素加到sum中,然后右指针右移。 2. 当sum≥s时,进入内层循环,尝试移动左指针缩小窗口: a. 计算当前窗口长度right - left,更新min_len。 b. 减去左指针指向的元素,左指针右移。 c. 重复直到sum < s。 3. 循环结束后,如果min_len还是无穷大,说明没有符合条件的子数,返回-1,否则返回min_len。 那这样的算法时间复杂度是O(n),每个元素最多被左右指针各访问一次。 接下来,我需要考虑边界情况,比如当数中有一个元素正好等于s的情况,此时应该返回1。或者整个数的和刚好等于s,此时返回数长度。或者所有元素的和都不足s,返回-1。 比如测试样例中的输入样例,可能需要测试不同的情况。例如,题目中的样例,输入数为[2,3,1,2,4,3],s=7,输出应该是2,对应子数[4,3]。 接下来,我需要用C++实现这个滑动窗口的方法。步骤大致如下: - 初始化left=0,sum=0,min_len为一个很大的值,比如INT_MAX。 - 遍历right从0到n-1: sum += nums[right] while (sum >= s) { min_len = min(min_len, right - left +1) sum -= nums[left] left++ } - 最后,如果min_len还是INT_MAX,返回-1,否则返回min_len。 这样应该可以解决问题。例如,当sum >=s时,不断缩小窗口,并记录每次可能的长度,取最小值。 但需要确保数元素都是正整数,否则可能无法正确工作。题目中说明数是正整数,所以没问题。 那现在编写代码的时候需要注意一些细节,比如数为空的情况,或者当数中没有满足条件的子数时返回-1。 另外,需要测试一些情况: 测试用例1: 输入:nums = [2,3,1,2,4,3], s=7 预期输出:2,因为子数[4,3]的和是7,长度2。 测试用例2: 输入:nums = [1,4,4], s=4 预期输出:1,因为元素4单独存在。 测试用例3: 输入:nums = [1,2,3], s=10 预期输出:-1,总和是6不够。 现在,编写C++代码: 根据上述思路,大致代码结构是: int minimumSize(vector<int> &nums, int s) { int n = nums.size(); if(n ==0) return -1; int left =0; int sum=0; int min_len = INT_MAX; for(int right=0; right <n; right++){ sum += nums[right]; while(sum >=s){ min_len = min(min_len, right - left +1); sum -= nums[left]; left++; } } return (min_len == INT_MAX)? -1 : min_len; } 但是,这可能需要进一步检查是否正确。例如,当数中有多个元素满足时,是否正确取到最小长度? 比如测试用例1中的过程: right=0,sum=2,不满足s=7。 right=1,sum=5,仍不满足。 right=2,sum=6,不满足。 right=3,sum=8,此时进入循环: min_len=4-0+1=4(窗口是0~3,长度4)。 sum-=nums[0]=2,sum=6,left=1。 此时sum=6<7,退出循环。 继续right=4,sum=6+4=10≥7: 进入循环,计算长度4-1+1=4?不,此时right是4,left是1。长度是4-1+1=4。 然后sum-=nums[1]=3,sum=7,left=2。再次循环,此时sum=7≥7: 计算长度4-2+1=3,min_len更新为3。 sum-=nums[2]=1,sum=6,left=3。退出循环。 继续right=5,sum=6+3=9≥7: 进入循环,计算长度5-3+1=3。此时min_len是之前记录的3和现在的3,保持不变。 sum-=nums[3]=2,sum=7,left=4。再次循环,sum=7≥7: 计算长度5-4+1=2,此时min_len更新为2。 sum-=nums[4]=4,sum=3,left=5。退出循环。 循环结束,返回2。正确。 所以,这个算法应该能正确计算。 但是,有没有可能漏掉某些情况?比如,当窗口内的和刚好等于s时,是否可能更早地出现更小的窗口?比如在移动左指针的过程中,可能找到更小的窗口。例如,假设有一个窗口的和超过s,但之后左指针移动后,sum减少,但可能还存在更小的窗口。 比如,假设数是[1,2,3,4,5],s=9。总和1+2+3+4=10≥9,窗口长度4。然后sum减去1,得到9,此时窗口是2+3+4=9,长度3,这时会被计算到。之后sum减2得到7,退出循环。此时右指针继续移动,sum加5得到12,进入循环,计算长度4(窗口是3+4+5=12)。然后sum减3得到9,长度3。所以最终最小长度是3。而实际存在更小的窗口,比如4+5=9,长度2。这说明我的算法有问题吗? 哦,这似乎有问题。因为在上述情况下,当右指针到5的位置时,sum是3+4+5=12。此时进入循环,计算窗口长度3(3,4,5),此时min_len更新为3。然后sum减去3得到9,此时sum=9≥s=9。于是再次进入循环,此时窗口是4+5=9,长度是2。这时候min_len会被更新为2。所以算法是正确的吗? 是的,这个时候会进入循环两次: 第一次sum=12≥9,计算right=4(索引从0开始?假设数是0-based,原数是[1,2,3,4,5],索引0~4。当右指针到4时,sum=1+2+3+4+5=15?或者原题中的例子可能不同? 或者原问题中的数是正整数,而上面这个例子中,假设s=9。初始时,假设数是[1,2,3,4,5],s=9。那正确的解应该是子数[4,5],长度2。 现在用算法模拟: 初始化left=0,sum=0,min_len=INT_MAX. right=0: sum=1. sum <9 →不处理。 right=1: sum=3. 仍不。 right=2: sum=6. 仍不。 right=3: sum=10≥9. 进入循环: 计算窗口长度3-0+1=4。min_len=4. sum -=1 →sum=9. left=1. 再次检查sum >=9?是的,再次循环: 计算窗口长度3-1+1=3 →min_len=3. sum -=2 →sum=7. left=2. sum <9 →退出。 继续right=4: sum +=5 →7+5=12≥9. 进入循环: 窗口长度4-2+1=3 →min_len现在是3和之前的3,一样。 sum -=3 →sum=9. left=3. 再次检查sum >=9 →是的。 窗口长度4-3+1=2 →min_len更新为2. sum -=4 →sum=5. left=4. 退出循环。 循环结束。返回min_len=2,正确。 这说明算法在这种情况下也能正确计算。因此,该算法是正确的。 那这说明滑动窗口方法是可行的,且能正确找到最小长度的子数。 所以,现在我需要将这个思路转化为C++代码,并且处理可能的边界条件。 另一个测试案例:当数中有多个元素等于s的情况。例如,nums=[5,1,3,5], s=5。这时最小长度是1,因为有一个元素5。当滑动窗口遍历到第一个元素时,sum=5≥5,窗口长度1,此时min_len=1。然后sum减5,left=1。继续处理其他元素,但结果已经是1,所以正确。 因此,算法是正确的。 综上,解题思路是使用滑动窗口法,时间复杂度O(n),空间复杂度O(1)。 现在,我需要根据这个思路写出C++代码,并确保正确。</think>### LintCode 406. 和大于S的最小子数 解题思路 #### 问题分析 题目要求在给定**正整数数**中找到**和≥s**的**最短连续子数**的长度。若不存在则返回-1。由于数元素均为正数,可以使用**滑动窗口(双指针)**方法高效解决,时间复杂度为$O(n)$。 #### 算法思路 1. **初始化**:左指针`left=0`,当前窗口和`sum=0`,最小长度`min_len`设为极大值(如`INT_MAX`)。 2. **滑动窗口**: - 右指针`right`遍历数,将元素累加到`sum`。 - 当`sum≥s`时,尝试缩小窗口(左指针右移)以找到更短的有效子数: - 更新`min_len`为当前窗口长度`right-left+1`。 - 减去左指针元素并右移左指针,直到`sum<s`。 3. **结果判断**:若`min_len`未被更新,返回-1,否则返回`min_len`。 #### 代码实现 ```cpp #include <vector> #include <climits> // 用于INT_MAX using namespace std; int minimumSize(vector<int> &nums, int s) { int n = nums.size(); if (n == 0) return -1; int left = 0; int sum = 0; int min_len = INT_MAX; for (int right = 0; right < n; right++) { sum += nums[right]; // 当sum >= s时,尝试缩小窗口 while (sum >= s) { min_len = min(min_len, right - left + 1); sum -= nums[left]; left++; } } return (min_len == INT_MAX) ? -1 : min_len; } ``` #### 复杂度分析 - **时间复杂度**:$O(n)$,每个元素最多被左右指针各访问一次。 - **空间复杂度**:$O(1)$,仅需常数级额外空间。 #### 示例验证 - **输入**:`nums = [2,3,1,2,4,3]`, `s = 7` - **过程**:窗口`[4,3]`和为7,长度2。 - **输出**:2。 - **输入**:`nums = [1,4,4]`, `s = 4` - **输出**:1(直接取单个元素4)。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值