题解 | #数组分组#

数组分组

https://www.nowcoder.com/practice/9af744a3517440508dbeb297020aca86

题目链接

数组分组

题目描述

对于给定的 个整数,需要将其分为 两个数组,并满足以下条件:

  • 所有 5 的倍数元素均在 数组中。

  • 所有 3 的倍数(且非 5 的倍数)元素均在 数组中。

  • 其他元素可以任意分配到 中。

求解是否存在一种分配方案,使得 数组中各个元素之和等于 数组中各个元素之和。

解题思路

这个问题的核心在于如何分配那些既不是 3 的倍数也不是 5 的倍数的“自由元素”,以使得两个数组的和相等。

问题转化

我们可以将所有输入数字分为三类:

  1. G5: 5 的倍数。这些数必须放入数组 。我们计算它们的和

  2. G3: 3 的倍数(但非 5 的倍数)。这些数必须放入数组 。我们计算它们的和

  3. C: 其他所有数字(自由元素)。这些数可以任意分配。

令最终数组 的和为 ,数组 的和为 。我们的目标是判断是否存在一种分配方案使得

如果存在这样的方案,那么所有数字的总和 必须是偶数,因为 。因此,如果所有输入数字的总和 为奇数,则一定无解。

如果 为偶数,那么目标就是让 都等于 。 数组 的总和由两部分组成:固定的 和从自由元素集合 中选出的一部分元素的和(记为 )。 所以,我们的目标方程是:

移项可得:

至此,原问题被转化为一个经典的 子集和问题 (Subset Sum Problem)我们能否从自由元素集合 中,挑选出一个子集,使得子集中元素的和恰好等于目标值

求解子集和问题

子集和问题是 NP-完全问题,没有已知的多项式时间解法。对于本题的 范围,暴力搜索所有子集是不可行的。

一个常见的解决方法是 带记忆化的深度优先搜索 (DFS),这本质上是一种动态规划。

我们可以定义一个递归函数 can_find_sum(index, target),表示能否从 C 集合的第 index 个元素开始,凑出和为 target

can_find_sum(index, target) 中:

  • 不选择 C[index]:问题转化为 can_find_sum(index + 1, target)

  • 选择 C[index]:问题转化为 can_find_sum(index + 1, target - C[index])

只要这两种情况有一种能成功,就说明可以凑出 target

为了避免重复计算,我们使用一个哈希表或二维数组作为备忘录,存储 (index, target) 状态的计算结果。

算法流程

  1. 遍历所有输入数字,将它们分类,计算出 sum5sum3,并将所有自由元素存入列表 C

  2. 计算所有数字的总和 S。如果 S 是奇数,直接返回 false

  3. 计算子集和的目标值 Target = S / 2 - sum5

  4. 调用带记忆化的 DFS 函数 can_find_sum(0, Target) 来判断能否从 C 中凑出 Target

  5. 返回 DFS 的结果。

代码

#include <iostream>
#include <vector>
#include <numeric>
#include <map>

using namespace std;

vector<long long> C;
map<pair<int, long long>, bool> memo;

bool can_find_sum(int index, long long target) {
    // 基本情况:target 达到了 0,成功
    if (target == 0) {
        return true;
    }
    // 基本情况:数组遍历完或 target 变为负数(对于仅正数情况)
    if (index == C.size()) {
        return false;
    }
    // 检查备忘录
    if (memo.count({index, target})) {
        return memo[{index, target}];
    }

    // 方案一:不选择 C[index]
    if (can_find_sum(index + 1, target)) {
        return memo[{index, target}] = true;
    }

    // 方案二:选择 C[index]
    if (can_find_sum(index + 1, target - C[index])) {
        return memo[{index, target}] = true;
    }

    // 两种方案都不可行
    return memo[{index, target}] = false;
}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);

    int n;
    cin >> n;

    long long sum5 = 0;
    long long sum3 = 0;
    long long total_sum = 0;

    for (int i = 0; i < n; ++i) {
        long long val;
        cin >> val;
        total_sum += val;
        if (val % 5 == 0) {
            sum5 += val;
        } else if (val % 3 == 0) {
            sum3 += val;
        } else {
            C.push_back(val);
        }
    }

    if (total_sum % 2 != 0) {
        cout << "false" << endl;
        return 0;
    }

    long long target = total_sum / 2 - sum5;

    if (can_find_sum(0, target)) {
        cout << "true" << endl;
    } else {
        cout << "false" << endl;
    }

    return 0;
}
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;

public class Main {
    private static List<Long> C = new ArrayList<>();
    private static Map<String, Boolean> memo = new HashMap<>();

    private static boolean canFindSum(int index, long target) {
        if (target == 0) {
            return true;
        }
        if (index == C.size()) {
            return false;
        }
        
        String key = index + ":" + target;
        if (memo.containsKey(key)) {
            return memo.get(key);
        }

        // 不选择 C[index]
        if (canFindSum(index + 1, target)) {
            memo.put(key, true);
            return true;
        }

        // 选择 C[index]
        if (canFindSum(index + 1, target - C.get(index))) {
            memo.put(key, true);
            return true;
        }

        memo.put(key, false);
        return false;
    }

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        
        long sum5 = 0;
        long sum3 = 0;
        long totalSum = 0;

        for (int i = 0; i < n; i++) {
            long val = sc.nextLong();
            totalSum += val;
            if (val % 5 == 0) {
                sum5 += val;
            } else if (val % 3 == 0) {
                sum3 += val;
            } else {
                C.add(val);
            }
        }

        if (totalSum % 2 != 0) {
            System.out.println("false");
            return;
        }

        long target = totalSum / 2 - sum5;

        if (canFindSum(0, target)) {
            System.out.println("true");
        } else {
            System.out.println("false");
        }
    }
}
import sys

# 增加递归深度限制
sys.setrecursionlimit(2000)

n = int(input())
nums = list(map(int, input().split()))

sum5 = 0
sum3 = 0
C = []
total_sum = 0

for x in nums:
    total_sum += x
    if x % 5 == 0:
        sum5 += x
    elif x % 3 == 0:
        sum3 += x
    else:
        C.append(x)

memo = {}

def can_find_sum(index, target):
    if target == 0:
        return True
    if index == len(C):
        return False
    
    # 检查备忘录
    if (index, target) in memo:
        return memo[(index, target)]
    
    # 方案一:不选择 C[index]
    res1 = can_find_sum(index + 1, target)
    
    # 方案二:选择 C[index]
    res2 = can_find_sum(index + 1, target - C[index])
    
    # 存储结果并返回
    memo[(index, target)] = res1 or res2
    return memo[(index, target)]

if total_sum % 2 != 0:
    print("false")
else:
    target = total_sum // 2 - sum5
    if can_find_sum(0, target):
        print("true")
    else:
        print("false")

算法及复杂度

  • 算法:动态规划(通过带记忆化的深度优先搜索实现),子集和问题

  • 时间复杂度:令自由元素的数量为 ),目标和的值域范围为 。记忆化搜索的状态由 (index, target) 决定,index 个取值,target 的取值范围 依赖于输入数字的大小。因此,时间复杂度为 。在最坏情况下, 可能非常大,但对于某些数据集,此方法可能有效。

  • 空间复杂度:空间复杂度主要由递归深度和备忘录大小决定。递归深度最多为 。备忘录的大小与时间复杂度同阶,为

全部评论

相关推荐

秋招投简历提醒助手:因为实习生都跑去秋招了。不过现在实习是不是太晚了点
点赞 评论 收藏
分享
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客网在线编程
牛客网题解
牛客企业服务