返回

刷题日记分享:寻找最小不可组成和与辨识假币的巧妙策略

后端

引言

算法练习是程序员的必经之路,犹如运动员的训练场。在日复一日的刷题中,我们磨砺思维,提升技巧,探究算法的奥妙。今天,我将分享两道来自牛客试题广场的有趣题目及题解:

  • 求正数数组的最小不可组成和
  • 有假币

求正数数组的最小不可组成和

给定一个正整数数组 nums,求出所有可能组成和中不能组成的最小正整数。

示例:
给定 nums = [1, 2, 3, 5],最小不可组成和为 4

题解:

我们首先需要知道一个规律:如果数组中的所有正整数都是互质的,那么最小不可组成和就等于数组中所有正整数的和加一。

因此,我们可以先将数组中的所有正整数分解质因数,然后找出所有质因数的最小公倍数。如果最小公倍数不为 1,那么数组中存在非互质的正整数,此时最小不可组成和就不能用上面的公式计算。我们需要用其他方法来求解。

一种比较简单的方法是:我们可以将数组中的所有正整数从小到大排列,然后从第一个正整数开始,依次尝试将每个正整数加到前面的正整数的和中,直到和大于等于数组中所有正整数的和为止。此时,和减去数组中所有正整数的和就是最小不可组成和。

import java.util.Arrays;

public class MinUnreachableSum {

    public static void main(String[] args) {
        int[] nums = {1, 2, 3, 5};
        System.out.println(minUnreachableSum(nums)); // 4
    }

    public static int minUnreachableSum(int[] nums) {
        // 将数组中的所有正整数分解质因数
        int[] primeFactors = new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            primeFactors[i] = nums[i];
            for (int j = 2; j <= nums[i] / 2; j++) {
                while (primeFactors[i] % j == 0) {
                    primeFactors[i] /= j;
                }
            }
        }

        // 求出所有质因数的最小公倍数
        int lcm = 1;
        for (int primeFactor : primeFactors) {
            lcm = lcm * primeFactor;
        }

        // 如果最小公倍数不为 1,那么数组中存在非互质的正整数,此时最小不可组成和就不能用上面的公式计算
        if (lcm != 1) {
            // 将数组中的所有正整数从小到大排列
            Arrays.sort(nums);

            // 从第一个正整数开始,依次尝试将每个正整数加到前面的正整数的和中,直到和大于等于数组中所有正整数的和为止
            int sum = 0;
            for (int num : nums) {
                sum += num;
                if (sum >= lcm) {
                    return sum - lcm + 1;
                }
            }
        }

        // 如果最小公倍数为 1,那么数组中的所有正整数都是互质的,此时最小不可组成和就等于数组中所有正整数的和加一
        return lcm + 1;
    }
}

有假币

给定一组硬币,其中有一个假币,假币比其他硬币轻或重。你有天平和无限次的称重机会,如何找出假币并确定它是轻还是重?

示例:
给定 10 枚硬币,其中一枚是假币,你可以通过称重来找出假币并确定它是轻还是重。

题解:

我们可以将 10 枚硬币分成两组,每组 5 枚硬币。然后将两组硬币分别放在天平的两边。如果天平平衡,那么假币一定在没有被称重的 5 枚硬币中。如果天平不平衡,那么假币就在被称重的 10 枚硬币中。

接下来,我们可以将被称重的 10 枚硬币分成两组,每组 3 枚硬币。然后将两组硬币分别放在天平的两边。如果天平平衡,那么假币一定在没有被称重的 2 枚硬币中。如果天平不平衡,那么假币就在被称重的 6 枚硬币中。

以此类推,我们可以不断地将硬币分组称重,直到找到假币。

public class FakeCoin {

    public static void main(String[] args) {
        int[] coins = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int fakeCoin = findFakeCoin(coins);
        System.out.println(fakeCoin); // 6
        System.out.println(isFakeCoinLighter(coins, fakeCoin)); // true
    }

    public static int findFakeCoin(int[] coins) {
        int left = 0;
        int right = coins.length - 1;
        while (left <= right) {
            int mid = (left + right) / 2;
            int[] leftCoins = Arrays.copyOfRange(coins, 0, mid);
            int[] rightCoins = Arrays.copyOfRange(coins, mid + 1, coins.length);
            int leftSum = sum(leftCoins);
            int rightSum = sum(rightCoins);
            if (leftSum == rightSum) {
                left = mid + 1;
            } else if (leftSum < rightSum) {
                right = mid - 1;
            } else {
                return coins[mid];
            }
        }
        return -1;
    }

    public static int sum(int[] coins) {
        int sum = 0;
        for (int coin : coins) {
            sum += coin;
        }
        return sum;
    }

    public static boolean isFakeCoinLighter(int[] coins, int fakeCoin) {
        int leftSum = 0;
        int rightSum = 0;
        for (int coin : coins) {
            if (coin != fakeCoin) {
                if (coin < fakeCoin) {
                    leftSum += coin;
                } else {
                    rightSum += coin;
                }
            }
        }
        return leftSum > rightSum;
    }
}

结语

刷题是提升算法技能的有效途径,两道题各有千秋,它们分别考验了程序员的数学基础和逻辑思维能力。希望大家能从中有所收获,在算法的世界里不断进步。