程序员老鬼

面试官:你前面一位面试者是清华毕业的,为什么我要录取你

今天要聊聊一个特别有意思的话题——面试官的奇葩问题。
没想到在产品岗位的面试中,竟然碰到了一道让面试者差点“怼”回去的问题。
有个网友分享过,她在面试某公司时,被问了这么一句话:“你前面一位面试者是清华毕业的,为什么我要录取你而不是他?”一听就能感受到那股挑衅的味道。简直像是在说:“你跟清华出来的比,能行吗?”

Image

我能理解那位网友的心情,听到这种问题,谁不心态失衡呢?

有时候学历虽然可以作为一个敲门砖,但并不代表就能解决所有问题。每个人的潜力和实际能力都不同,面试官是不是该看得更全面些?

算法题:分割等和子集

今天我们来聊聊一个看似简单,但其实非常考验编程思维和算法能力的问题:分割等和子集。你可能在面试或者算法竞赛中遇到过这类题目,题目大概是这样的:

给定一个整数数组,能否将其划分为两个子集,使得这两个子集的和相等?

说起来,这个问题貌似很简单,其实背后涉及到动态规划、背包问题等经典算法,难度并不低。今天,我就用Java来带大家深入分析一下。

问题的核心思想其实是:如果我们想要将一个数组划分成两个和相等的子集,数组所有元素的和必须是偶数。因为如果总和是奇数,根本不可能平均分配到两个子集中。

解决方案:

首先,我们来思考一下问题的解法。我们可以这样来划分问题:

  1. 计算数组元素的总和。
  2. 如果总和是奇数,直接返回false,因为无法平分。
  3. 如果总和是偶数,那么我们可以将问题转化为:从数组中找一个子集,使得这个子集的和等于总和的一半。换句话说,我们只要找出一个子集,它的和为sum/2,那么剩下的元素必然能组成另一个和为sum/2的子集。

此时,问题变成了一个经典的“背包问题”:我们需要判断是否存在一个子集,其元素和为sum/2。这个问题可以使用动态规划来解决。

动态规划思路:

我们定义一个布尔型的数组dp,其中dp[i]表示是否可以从数组中找到一个子集,使得该子集的和为i。初始时,dp[0]为true,因为和为0的子集是空集。然后,我们遍历数组的每个元素,更新dp数组,看看能否通过当前元素组合出某个和。

下面是这个算法的代码实现:

public class PartitionSubset {
    public boolean canPartition(int[] nums) {
        int sum = 0;
        for (int num : nums) {
            sum += num;
        }

                // 如果总和是奇数,直接返回false
        if (sum % 2 != 0) {
            return false;
        }

                int target = sum / 2;
        boolean[] dp = new boolean[target + 1];
        dp[0] = true; // 和为0是空集

                // 遍历每个元素
        for (int num : nums) {
            // 从后往前更新dp数组
            for (int j = target; j >= num; j--) {
                dp[j] = dp[j] || dp[j - num];
            }
        }

                return dp[target];
    }

    public static void main(String[] args) {
        PartitionSubset ps = new PartitionSubset();
        int[] nums = {1, 5, 11, 5};
        System.out.println(ps.canPartition(nums));  // 输出 true
    }
}

代码解析:

  1. 计算总和:我们首先遍历数组,计算出所有元素的总和。然后判断总和是否为奇数,如果是奇数,直接返回false,因为不可能分成两个和相等的子集。

  2. 动态规划数组:我们定义一个dp数组,它的长度是sum / 2 + 1。dp[i]表示是否可以通过数组中的一些元素组成和为i的子集。

  3. 更新dp数组:对于每一个元素num,我们从后向前遍历dp数组,更新每个dp[j]。如果当前dp[j - num]为true,说明我们可以通过当前的元素num来组合出和为j的子集,因此更新dp[j]为true。

  4. 返回结果:最后,如果dp[target]为true,说明存在一个子集的和为sum / 2,那么就可以将数组分成两个和相等的子集,返回true;否则返回false

时间复杂度与空间复杂度:

  • 时间复杂度:O(n * sum),其中n是数组的长度,sum是数组元素的总和。我们遍历数组和更新dp数组需要O(n * sum)的时间。
  • 空间复杂度:O(sum),因为我们只需要一个大小为sum / 2 + 1的布尔型数组。

小结:

这个题目通过动态规划解决了一个经典的“背包问题”。尽管乍看上去像是一个简单的分割问题,但背后其实涉及到的知识点非常重要,尤其是在大厂面试或者算法竞赛中,经常会见到类似的题目。

当你面对这类问题时,首先要理清问题的本质——如果总和是奇数,直接返回false,然后把问题转化为“找和为sum / 2的子集”来进行求解。通过动态规划,我们可以有效地解决这个问题,而不需要穷举所有子集。

另外,做这种题目时,建议你不仅要理解算法的过程,还要能够自己手写出代码,理解每一步是如何进行状态转移的。

-END-

ok,今天先说到这,老规矩,给大家分享一份不错的副业资料,感兴趣的同学找我领取。

Image

以上,就是今天的分享了,看完文章记得右下角给何老师点赞,也欢迎在评论区写下你的留言。