《程序员面试金典(第6版)》面试题 16.21. 交换和(哈希法,双指针,二分查找)

题目描述

给定两个整数数组,请交换一对数值(每个数组中取一个数值),使得两个数组所有元素的和相等。题目入口

  • 返回一个数组,第一个元素是第一个数组中要交换的元素,第二个元素是第二个数组中要交换的元素。若有多个答案,返回任意一个均可。若无满足条件的数值,返回空数组。

示例:

输入: array1 = [4, 1, 2, 1, 1, 2], array2 = [3, 6, 3, 3]
输出: [1, 3]

示例:

输入: array1 = [1, 2, 3], array2 = [4, 5, 6]
输出: []

提示:

  • 1 <= array1.length, array2.length <= 100000

解题思路与代码

  • 首先呢,这道题看起来是一道十分清爽的题。为什么呢?因为说的是人话,清醒易懂。题目的难度呢?中等偏简单。

  • 这道题的核心其实是让你找出一种比暴力破解时间复杂度更小的做法出来(暴力破解的时间复杂度为O(n^2))

  • 那么这道题就很有趣了。我自己采取的办法是哈希法,因为哈希法可以使查找的复杂度从O(n),去降低到O(1)。至于为什么,那你可能就要去学习或了解一下哈希的底层原理了。推荐看我的这篇文章,介绍的很详细:全面理解哈希,哈希的底层原理是如何实现的,哈希题型的做题思路与题目清单(不断更新)

  • 还有两种方法,一种是使用排序 + 双指针,一种是排序 + 二分法。这两种的时间复杂度与空间复杂度都还行,与第一种方法相比,这两种的空间复杂度要明显的优于第一种方法,而时间复杂度,明显的要比第一种差很多。

那么接下来,就让我来看看这几种方法,在实现上都有些什么区别吧~

方法一:哈希法

  • 我们首先需要计算出两个数组的和分别是多少。然后需要去判断,两个数组的差是否是奇数,如果是奇数,那是不可能交换成功的,所以需要返回空。

  • 紧接着,我们创建一个unordered_set<int> set,让set里面充满数组1的元素。其次,我们再创建一个变量diff,它记录着两个数组和的差除以2的值。这个数必定是一个整数。所以不用担心会除出小数来。

  • 这个数的目的就是就是为了再遍历数组2的时候,去数组1中去寻找,是否有一个数n1 = diff + n2,找到了,就返回n1,n2。

  • 如果最后都找不到,那只能返回空了。

具体的代码如下:

class Solution {
public:
    vector<int> findSwapValues(vector<int>& array1, vector<int>& array2) {
        int a1 = arraySum(array1);
        int a2 = arraySum(array2);
        if((a1 - a2) % 2 != 0) return {};
        int diff = (a1 - a2) / 2;
        unordered_set<int> set(array1.begin(),array1.end());
        for(auto& n2 : array2){
            int n1 = n2 + diff;
            if(set.count(n1)) return{n1,n2};
        }
        return {};
    }
    int arraySum(vector<int>& array){
        int size = 0;
        for(auto& a : array)
            size += a;
        return size;
    }
};
复杂度分析

时间复杂度

  • 这段代码的时间复杂度是 O(n)。原因是 arraySum 函数遍历了 array1 和 array2 各一次,每次遍历的时间复杂度是 O(n),然后在构建 unordered_set set1 时,又遍历了一次 array1,时间复杂度也是 O(n)。最后在遍历 array2 时,查找操作的平均时间复杂度是 O(1),所以总的遍历操作的时间复杂度是 O(n)。因此,整个代码的时间复杂度是 O(n)。

空间复杂度

  • 这段代码的空间复杂度也是 O(n)。原因是使用了一个 unordered_set 来存储 array1 的所有元素,unordered_set 的大小与 array1 的大小相等,所以空间复杂度是 O(n)。

在这里,n 是 array1 和 array2 的长度之和。如果你想要更精确地表示时间复杂度和空间复杂度,可以使用两个变量,如 n1 和 n2,分别代表 array1 和 array2 的长度。这样,时间复杂度就是 O(n1 + n2),空间复杂度就是 O(n1)。

方法二 排序 + 双指针法

  • 这种方法其实蛮巧妙的,但是需要提前排序。为什么呢?这是因为只有有序的数组,才能使用后面的双指针操作。所以一定要进行排序操作。

  • 和方法一的许多步骤一样,我们需要计算数组的和,需要看数组和差是否为偶数等。这里不一样的一点是,我们用双指针去同时遍历两个数组,用while循环

  • 之前我们创建一个变量 int diff = (a1 - a2) / 2; 这个就是算出两个数组之间差值的一半,我们只需要把多一半的部分与少一半的部分去交换就好了。

  • 在while循环中,双指针指向的都是两个数组的首元素,如果 array1[p1] - array2[p2] < diff 我们就 ++ p1 这是因为只有当 array1[p1] - array2[p2] = diff 正好满足了交换原则,可以交换 ,那小于,说明数组1中的元素小了,该让p1指针动。反之就是p2指针动。刚刚好,就直接返回。

具体的代码如下

class Solution {
public:
    vector<int> findSwapValues(vector<int>& array1, vector<int>& array2) {
        sort(array1.begin(),array1.end());
        sort(array2.begin(),array2.end());
        int a1 = arraySum(array1);
        int a2 = arraySum(array2);
        if((a1 - a2) % 2 != 0) return {};
        int diff = (a1 - a2) / 2;
        int p1 = 0;
        int p2 = 0;
        while(p1 < array1.size() && p2 < array2.size()){
            if(array1[p1] - array2[p2] < diff) ++p1;
            else if(array1[p1] - array2[p2] > diff) ++p2;
            else return {array1[p1],array2[p2]};
        }
        return {};
    }
    int arraySum(vector<int>& array){
        int size = 0;
        for(int& num : array)
            size += num;
        return size;
    }
};

在这里插入图片描述

复杂度分析

时间复杂度:

  • 首先使用了sort()函数进行排序,使用sort()函数的时间复杂度为O(nlogn),while循环遍历的时间复杂度为O(n)
  • 总的时间复杂度为O(nlogn)

空间复杂度:

  • 由于没有使用任何的数据结构,那么空间复杂度为O(1)

方法三: 排序 + 二分法

这种方法与方法二差不多。唯一的差别就在于上面的是使用while循环 + 双指针进行遍历双数组。 这里是使用for循环 + while循环(二分法)去执行遍历数组。

具体是这样的,这样的遍历方式有点像暴力破解了,只不过是内层的for循环,改成了用二分法去查找指定元素罢了。第一层for循环是去把数组1中的每一个元素拿出来,然后 减去一个diff 看能不能在数组2中找到这个元素。

在数组二中就用二分法去查找。二分法其实很简单,具体的看代码实现把,这里实在是不想多费笔墨了。

具体的代码实现如下:

class Solution {
public:
    vector<int> findSwapValues(vector<int>& array1, vector<int>& array2) {
        int a1 = 0;
        int a2 = 0;
        sort(array1.begin(),array1.end());
        sort(array2.begin(),array2.end());
        for(int& num : array1) a1 += num;
        for(int& num : array2) a2 += num;
        if((a1 - a2) % 2 != 0) return {};
        int diff = (a2 - a1) / 2;
        for(int i = 0; i < array1.size(); ++i){
            int left = 0;
            int right = array2.size() - 1;
            int target = array1[i] + diff;
            int mid;
            while(left <= right){
                mid = (left + right) / 2;
                if(target > array2[mid]) left = mid + 1;
                else if(target < array2[mid]) right = mid -1;
                else return {array1[i],array2[mid]};
            }
        }
        return {};
    }
};

在这里插入图片描述

复杂度分析:

时间复杂度:

  • 时间复杂度是 O(n log n)。其中,排序 array2 的时间复杂度为 O(n log n),然后你在 array1 中遍历每个元素,对于每个元素,都进行了一次二分查找,二分查找的时间复杂度为 O(log n),因此,总的时间复杂度为 O(n log n)。

空间复杂度:

  • 空间复杂度是 O(1)。在这个解决方案中,我们没有使用额外的数据结构,所以空间复杂度为 O(1)。

这个方法的效率与双指针方法相比,时间复杂度是相同的,都是 O(n log n)。然而,实际运行速度可能会有所不同,这取决于具体的输入数据和语言实现。在某些情况下,二分查找可能比双指针方法快,尤其是在两个数组大小差距很大的情况下。所以,这两种方法都是值得考虑的。

总结

这道题目是一个典型的算法题,主要考察了解决问题的能力、编程技巧和对算法时间复杂度的把控。通过这道题目,你可以学习和掌握不同的算法方法,如暴力解法、双指针技术、排序与二分查找、哈希法等,并深入理解它们的优缺点。

这道题的意义在于:

  • 培养解决问题的能力:这道题需要找到两个数组中各取一个元素交换后,使得两个数组的元素之和相等。在解决这个问题的过程中,你需要学会如何根据题目要求分析问题,寻找解决方案。

  • 提高编程技巧:通过实现不同的解决方案,你可以提高自己的编程技巧,例如使用双指针技术、排序和二分查找,哈希法等方法。

  • 熟练掌握时间复杂度和空间复杂度:对于不同的解决方案,你需要分析它们的时间复杂度和空间复杂度,并在实际编程中选择最优的方案。

  • 锻炼创新思维:在寻找解决方案的过程中,你可以尝试不同的思路,挑战自己的思维能力,培养创新意识。

通过这道题,你可以锻炼自己在算法和编程领域的能力,对于提高解决问题的能力、编程技巧和深入理解算法具有很好的帮助。

最后的最后,如果你觉得我的这篇文章写的不错的话,请给我一个赞与收藏,关注我,我会继续给大家带来更多更优质的干货内容

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

阿宋同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值