刷题日记分享:寻找最小不可组成和与辨识假币的巧妙策略
2024-01-24 04:46:58
引言
算法练习是程序员的必经之路,犹如运动员的训练场。在日复一日的刷题中,我们磨砺思维,提升技巧,探究算法的奥妙。今天,我将分享两道来自牛客试题广场的有趣题目及题解:
- 求正数数组的最小不可组成和
- 有假币
求正数数组的最小不可组成和
给定一个正整数数组 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;
}
}
结语
刷题是提升算法技能的有效途径,两道题各有千秋,它们分别考验了程序员的数学基础和逻辑思维能力。希望大家能从中有所收获,在算法的世界里不断进步。