【华为OD】数字游戏

【华为OD】数字游戏

题目描述

小明玩一个游戏。系统发 1+n 张牌,每张牌上有一个整数。第一张给小明,后 n 张按照发牌顺序排成连续的一行。

需要小明判断,后 n 张牌中,是否存在连续的若干张牌,其和可以整除小明手中牌上的数字。

输入描述

输入数据有多组,每组输入数据有两行,输入到文件结尾结束。

  • 第一行有两个整数 n 和 m,空格隔开。m 代表发给小明牌上的数字。
  • 第二行有 n 个数,代表后续发的 n 张牌上的数字,以空格隔开。

输出描述

对每组输入,如果存在满足条件的连续若干张牌,则输出1;否则,输出0。

备注

  • 1 ≤ n ≤ 1000
  • 1 ≤ 牌上的整数 ≤ 400000
  • 输入的数组,不多于 1000
  • 用例确保输入都正确,不需要考虑非法情况。

示例

示例一

输入:

复制代码
6 7
2 12 6 3 5 5

输出:

复制代码
1

示例二

输入:

复制代码
10 11
1 1 1 1 1 1 1 1 1 1

输出:

复制代码
0

说明:

两组输入。

  • 第一组小明牌的数字为 7,再发了6张牌。第1、2两张牌数字和为 14,可以整除7,输出 1
  • 第二组小明牌的数字为 11,再发了10张牌,这10张牌数字和为10,无法整除 11,输出0。

解题思路

这是一个子数组和整除问题,需要判断是否存在连续子数组的和能被给定数字整除。

核心思想:

  1. 枚举所有可能的连续子数组
  2. 计算每个子数组的和
  3. 判断是否能被目标数字整除

我将提供两种解法:暴力枚举法前缀和优化法

解法一:暴力枚举法

枚举所有可能的连续子数组,计算每个子数组的和并判断是否能被目标数字整除。

Java实现

java 复制代码
import java.util.*;

public class Solution1 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        while (sc.hasNextLine()) {
            String line = sc.nextLine().trim();
            if (line.isEmpty()) break;
            
            String[] parts = line.split(" ");
            int n = Integer.parseInt(parts[0]);
            int m = Integer.parseInt(parts[1]);
            
            if (!sc.hasNextLine()) break;
            String[] numStrs = sc.nextLine().split(" ");
            int[] nums = new int[n];
            for (int i = 0; i < n; i++) {
                nums[i] = Integer.parseInt(numStrs[i]);
            }
            
            boolean found = false;
            
            // 枚举所有可能的连续子数组
            for (int i = 0; i < n && !found; i++) {
                int sum = 0;
                for (int j = i; j < n; j++) {
                    sum += nums[j];
                    if (sum % m == 0) {
                        found = true;
                        break;
                    }
                }
            }
            
            System.out.println(found ? 1 : 0);
        }
        
        sc.close();
    }
}

Python实现

python 复制代码
import sys

def solve_brute_force():
    lines = []
    for line in sys.stdin:
        lines.append(line.strip())
    
    i = 0
    while i < len(lines):
        if not lines[i]:
            break
            
        parts = lines[i].split()
        n, m = int(parts[0]), int(parts[1])
        
        if i + 1 >= len(lines):
            break
            
        nums = list(map(int, lines[i + 1].split()))
        
        found = False
        
        # 枚举所有可能的连续子数组
        for start in range(n):
            current_sum = 0
            for end in range(start, n):
                current_sum += nums[end]
                if current_sum % m == 0:
                    found = True
                    break
            if found:
                break
        
        print(1 if found else 0)
        i += 2

solve_brute_force()

C++实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <sstream>
using namespace std;

int main() {
    string line;
    
    while (getline(cin, line)) {
        if (line.empty()) break;
        
        istringstream iss(line);
        int n, m;
        iss >> n >> m;
        
        if (!getline(cin, line)) break;
        
        istringstream numStream(line);
        vector<int> nums(n);
        for (int i = 0; i < n; i++) {
            numStream >> nums[i];
        }
        
        bool found = false;
        
        // 枚举所有可能的连续子数组
        for (int i = 0; i < n && !found; i++) {
            int sum = 0;
            for (int j = i; j < n; j++) {
                sum += nums[j];
                if (sum % m == 0) {
                    found = true;
                    break;
                }
            }
        }
        
        cout << (found ? 1 : 0) << endl;
    }
    
    return 0;
}

解法二:前缀和 + 同余定理优化法

使用前缀和和同余定理来优化算法。如果两个前缀和对m的余数相同,那么它们之间的子数组和必定能被m整除。

Java实现

java 复制代码
import java.util.*;

public class Solution2 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        while (sc.hasNextLine()) {
            String line = sc.nextLine().trim();
            if (line.isEmpty()) break;
            
            String[] parts = line.split(" ");
            int n = Integer.parseInt(parts[0]);
            int m = Integer.parseInt(parts[1]);
            
            if (!sc.hasNextLine()) break;
            String[] numStrs = sc.nextLine().split(" ");
            int[] nums = new int[n];
            for (int i = 0; i < n; i++) {
                nums[i] = Integer.parseInt(numStrs[i]);
            }
            
            boolean found = false;
            Set<Integer> remainders = new HashSet<>();
            remainders.add(0); // 前缀和为0的余数
            
            int prefixSum = 0;
            for (int i = 0; i < n; i++) {
                prefixSum += nums[i];
                int remainder = prefixSum % m;
                
                // 如果当前余数已经出现过,说明存在子数组和能被m整除
                if (remainders.contains(remainder)) {
                    found = true;
                    break;
                }
                
                remainders.add(remainder);
            }
            
            System.out.println(found ? 1 : 0);
        }
        
        sc.close();
    }
}

Python实现

python 复制代码
import sys

def solve_optimized():
    lines = []
    for line in sys.stdin:
        lines.append(line.strip())
    
    i = 0
    while i < len(lines):
        if not lines[i]:
            break
            
        parts = lines[i].split()
        n, m = int(parts[0]), int(parts[1])
        
        if i + 1 >= len(lines):
            break
            
        nums = list(map(int, lines[i + 1].split()))
        
        found = False
        remainders = {0}  # 前缀和为0的余数
        
        prefix_sum = 0
        for num in nums:
            prefix_sum += num
            remainder = prefix_sum % m
            
            # 如果当前余数已经出现过,说明存在子数组和能被m整除
            if remainder in remainders:
                found = True
                break
            
            remainders.add(remainder)
        
        print(1 if found else 0)
        i += 2

solve_optimized()

C++实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <unordered_set>
#include <sstream>
using namespace std;

int main() {
    string line;
    
    while (getline(cin, line)) {
        if (line.empty()) break;
        
        istringstream iss(line);
        int n, m;
        iss >> n >> m;
        
        if (!getline(cin, line)) break;
        
        istringstream numStream(line);
        vector<int> nums(n);
        for (int i = 0; i < n; i++) {
            numStream >> nums[i];
        }
        
        bool found = false;
        unordered_set<int> remainders;
        remainders.insert(0); // 前缀和为0的余数
        
        int prefixSum = 0;
        for (int i = 0; i < n; i++) {
            prefixSum += nums[i];
            int remainder = prefixSum % m;
            
            // 如果当前余数已经出现过,说明存在子数组和能被m整除
            if (remainders.find(remainder) != remainders.end()) {
                found = true;
                break;
            }
            
            remainders.insert(remainder);
        }
        
        cout << (found ? 1 : 0) << endl;
    }
    
    return 0;
}

解法三:简化版前缀和优化(推荐)

考虑到输入处理的复杂性,提供一个更简洁的版本:

Java实现

java 复制代码
import java.util.*;

public class Solution3 {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        
        while (sc.hasNext()) {
            int n = sc.nextInt();
            int m = sc.nextInt();
            
            int[] nums = new int[n];
            for (int i = 0; i < n; i++) {
                nums[i] = sc.nextInt();
            }
            
            boolean found = false;
            Set<Integer> remainders = new HashSet<>();
            remainders.add(0);
            
            int prefixSum = 0;
            for (int num : nums) {
                prefixSum += num;
                int remainder = prefixSum % m;
                
                if (remainders.contains(remainder)) {
                    found = true;
                    break;
                }
                
                remainders.add(remainder);
            }
            
            System.out.println(found ? 1 : 0);
        }
        
        sc.close();
    }
}

Python实现

python 复制代码
def solve_simple():
    try:
        while True:
            line = input().split()
            n, m = int(line[0]), int(line[1])
            
            nums = list(map(int, input().split()))
            
            found = False
            remainders = {0}
            
            prefix_sum = 0
            for num in nums:
                prefix_sum += num
                remainder = prefix_sum % m
                
                if remainder in remainders:
                    found = True
                    break
                
                remainders.add(remainder)
            
            print(1 if found else 0)
            
    except EOFError:
        pass

solve_simple()

C++实现

cpp 复制代码
#include <iostream>
#include <vector>
#include <unordered_set>
using namespace std;

int main() {
    int n, m;
    
    while (cin >> n >> m) {
        vector<int> nums(n);
        for (int i = 0; i < n; i++) {
            cin >> nums[i];
        }
        
        bool found = false;
        unordered_set<int> remainders;
        remainders.insert(0);
        
        int prefixSum = 0;
        for (int num : nums) {
            prefixSum += num;
            int remainder = prefixSum % m;
            
            if (remainders.find(remainder) != remainders.end()) {
                found = true;
                break;
            }
            
            remainders.insert(remainder);
        }
        
        cout << (found ? 1 : 0) << endl;
    }
    
    return 0;
}

算法复杂度分析

解法一:暴力枚举法

  • 时间复杂度:O(N²),需要枚举所有可能的子数组
  • 空间复杂度:O(1)

解法二:前缀和 + 同余定理优化法

  • 时间复杂度:O(N),只需要一次遍历
  • 空间复杂度:O(min(N, M)),存储余数的集合

算法原理详解

同余定理的应用

关键洞察:如果存在两个前缀和 prefixSum[i]prefixSum[j] (i < j),使得:

复制代码
prefixSum[i] ≡ prefixSum[j] (mod m)

那么子数组 nums[i+1...j] 的和就能被 m 整除,因为:

复制代码
sum(nums[i+1...j]) = prefixSum[j] - prefixSum[i] ≡ 0 (mod m)

特别地,如果某个前缀和本身就能被 m 整除(余数为0),那么从开头到该位置的子数组和就能被 m 整除。

示例分析

示例一分析

数组:[2, 12, 6, 3, 5, 5],m = 7

前缀和计算过程:

  • 位置0:prefixSum = 2,余数 = 2 % 7 = 2
  • 位置1:prefixSum = 2 + 12 = 14,余数 = 14 % 7 = 0

由于余数为0,说明前两个元素的和(14)能被7整除,输出1。

示例二分析

数组:[1, 1, 1, 1, 1, 1, 1, 1, 1, 1],m = 11

所有前缀和的余数都是1到10之间的数,没有重复的余数,且没有余数为0的情况,所以不存在能被11整除的子数组和,输出0。

总结

两种解法各有特点:

  1. 暴力枚举法:思路直观,容易理解,但时间复杂度较高
  2. 前缀和 + 同余定理优化法:利用数学性质优化算法,时间复杂度降为O(N),是最优解法

对于这道题目,由于 n ≤ 1000,两种方法都能通过,但前缀和 + 同余定理优化法更加高效,特别适合处理大规模数据。

关键技巧是理解同余定理在子数组和问题中的应用:如果两个前缀和的余数相同,那么它们之间的子数组和必定能被目标数整除。这个性质大大简化了问题的求解过程。

相关推荐
pzx_0012 小时前
【LeetCode】392.判断子序列
算法·leetcode·职场和发展
沐怡旸2 小时前
【算法--链表】146.LRU缓存--通俗讲解
算法·面试
京东零售技术2 小时前
查收你的技术成长礼包
后端·算法·架构
老纪的技术唠嗑局2 小时前
向量检索技术优化步骤详解——游戏公司智能客服与推荐系统落地OceanBase
游戏
fangzelin53 小时前
算法-滑动窗口
数据结构·算法
zcz16071278214 小时前
LVS + Keepalived 高可用负载均衡集群
java·开发语言·算法
rit84324994 小时前
人工鱼群算法AFSA优化支持向量机SVM,提高故障分类精度
算法·支持向量机·分类
德迅云安全杨德俊4 小时前
游戏盾:构筑网络安全防线,抵御DDoS攻击的解决方案
网络·安全·游戏·ddos