🍭 大家好这里是KK爱Coding ,一枚热爱算法的程序员
✨ 本系列打算持续跟新科大讯飞近期的春秋招笔试题汇总~
💻 ACM银牌🥈| 多次AK大厂笔试 | 编程一对一辅导
👏 感谢大家的订阅➕ 和 喜欢💗
📧 KK这边最近正在收集近一年互联网各厂的笔试题汇总,如果有需要的小伙伴可以关注后私信一下 KK领取,会在飞书进行同步的跟新,🌿5月1日之前限时免费领取哦,后续会由ACM银牌团队持续维护~。
文章目录
- 💌 01.硬币最少组合问题
- 问题描述
- 输入格式
- 输出格式
- 样例输入
- 样例输出
- 数据范围
- 题解
- 参考代码
- 🗳 02.单词拆分问题
- 问题描述
- 输入格式
- 输出格式
- 样例输入
- 样例输出
- 数据范围
- 题解
- 参考代码
- 🎀 03.K小姐的幸运评论
- 题目描述
- 输入格式
- 输出格式
- 样例输入
- 样例输出
- 数据范围
- 题解
- 参考代码
- 写在最后
- 📧 KK这边最近正在收集近一年互联网各厂的笔试题汇总,如果有需要的小伙伴可以关注后私信一下 KK领取,会在飞书进行同步的跟新。
💌 01.硬币最少组合问题
问题描述
K小姐有一个爱好就是收集各种硬币。有一天,她在整理自己的硬币收藏时,突然想到了一个有趣的问题。如果给定一个总金额 a m o u n t amount amount,能够用手上现有的硬币面值组合成该金额,需要的硬币数量最少是多少枚呢?
现在已知K小姐有 5 5 5 种不同面值的硬币,分别为 1 1 1、 2 2 2、 5 5 5、 10 10 10、 20 20 20、 50 50 50、 100 100 100 元,每种面值的硬币数量都是无限的。请你帮助K小姐设计一个程序,快速计算出给定总金额所需的最少硬币组合。
输入格式
输入仅包含一个正整数 a m o u n t amount amount,表示要求组合的目标总金额,单位为元。
输出格式
输出最少硬币组合,从大到小按面值顺序输出,硬币面值间用空格分隔。若无法用现有面值组合出目标总金额,则输出 − 1 -1 −1。
样例输入
8
样例输出
5 2 1
数据范围
1 ≤ a m o u n t ≤ 1 0 5 1 \leq amount \leq 10^5 1≤amount≤105
题解
这是一个典型的动态规划问题。可以定义一个一维数组 d p dp dp,其中 d p [ i ] dp[i] dp[i] 表示组合成金额 i i i 所需的最少硬币数量。
先将 d p dp dp 数组初始化为一个较大的值,表示暂时无法组合出该金额。然后对于每一种面值的硬币,遍历 a m o u n t amount amount,尝试更新 d p [ j ] dp[j] dp[j] 的值。具体来说,对于面值为 v a l val val 的硬币,可以将金额 j j j 拆分为 v a l val val 和 j − v a l j-val j−val 两部分,即 d p [ j ] = d p [ j − v a l ] + 1 dp[j] = dp[j-val] + 1 dp[j]=dp[j−val]+1,表示组合成金额 j j j 的最少硬币数等于组合成金额 j − v a l j-val j−val 的最少硬币数再加上当前这枚面值为 v a l val val 的硬币。
最后,再从 d p [ a m o u n t ] dp[amount] dp[amount] 开始回溯,找出凑成总金额的硬币组合方案。具体做法是,从 a m o u n t amount amount 开始,如果 d p [ j ] = d p [ l a s t ] − 1 dp[j] = dp[last] - 1 dp[j]=dp[last]−1,即找到了上一个状态,就将两个状态的差值 l a s t − j last - j last−j 加入答案数组中,然后令 l a s t = j last = j last=j,直到 j j j 减小到 0 0 0 为止。因为要求输出的硬币面值要从大到小排列,所以最后再对答案数组逆序输出即可。
参考代码
- Python
import sysn = int(sys.stdin.readline().strip())
coins = [1, 2, 5, 10, 20, 50, 100]
INF = float('inf')dp = [INF] * (n + 1)
dp[0] = 0for val in coins:for j in range(val, n + 1):dp[j] = min(dp[j], dp[j - val] + 1)if dp[n] == INF or n == 0:print(-1)exit(0)result = []
last, j = n, n
while j >= 0:if dp[j] == dp[last] - 1:result.append(last - j)last = jj -= 1print(*reversed(result))
- Java
import java.util.*;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int[] coins = {1, 2, 5, 10, 20, 50, 100};int INF = Integer.MAX_VALUE;int[] dp = new int[n + 1];Arrays.fill(dp, INF);dp[0] = 0;for (int val : coins) {for (int j = val; j <= n; j++) {dp[j] = Math.min(dp[j], dp[j - val] + 1);}}if (dp[n] == INF || n == 0) {System.out.println(-1);return;}List<Integer> result = new ArrayList<>();int last = n, j = n;while (j >= 0) {if (dp[j] == dp[last] - 1) {result.add(last - j);last = j;}j--;}Collections.reverse(result);for (int num : result) {System.out.print(num + " ");}}
}
- Cpp
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;const int INF = 0x3f3f3f3f;int main() {int n;cin >> n;vector<int> coins = {1, 2, 5, 10, 20, 50, 100};vector<int> dp(n + 1, INF);dp[0] = 0;for (int val : coins) {for (int j = val; j <= n; j++) {dp[j] = min(dp[j], dp[j - val] + 1);}}if (dp[n] == INF || n == 0) {cout << -1 << endl;return 0;}vector<int> result;int last = n, j = n;while (j >= 0) {if (dp[j] == dp[last] - 1) {result.push_back(last - j);last = j;}j--;}reverse(result.begin(), result.end());for (int num : result) {cout << num << " ";}return 0;
}
🗳 02.单词拆分问题
问题描述
LYA最近在学习一门新的外语,他发现有些外来词在词典中并没有收录。为了方便学习和记忆,他想将这些生词拆分成词典中已有的单词。
现在给定一个词典,其中每个单词都有一个正整数权重。对于一个待拆分的单词,可以将其拆分成词典中出现的单词,且拆分得到的单词序列的权重之和最大。
例如,词典中有 i i i、 f l y t e k flytek flytek、 i n l y inly inly 三个单词,权重分别为 3 3 3、 1 1 1、 5 5 5。对于单词 i l y t e k ilytek ilytek,可以拆分成 i / f l y t e k i/flytek i/flytek 或 i l y / t e k ily/tek ily/tek,对应的权重之和分别为 4 4 4 和 8 8 8,因此应该拆分为 i l y / t e k ily/tek ily/tek。
假设词典中最多有 100 100 100 个单词,每个单词的长度不超过 50 50 50 个字符。请你帮助LYA设计一个程序,能够根据词典将输入的单词拆分成权重之和最大的形式。
输入格式
第一行包含整数 N N N,表示词典中单词的个数。
接下来 N N N 行,每行包含一个单词和对应的权重值,以空格分隔。
最后一行输入一个待拆分的单词。
输出格式
如果可以将输入的单词拆分,则输出拆分后的结果,单词之间用 / / / 隔开。如果无法拆分,则原样输出待拆分的单词。
样例输入
7
ba 1
cef 2
cefs 3
s 2
dok 6
sdok 9
ok 3
bacefsdok
样例输出
/ba/cef/sdok
数据范围
1 ≤ N ≤ 100 1 \leq N \leq 100 1≤N≤100
1 ≤ 1 \leq 1≤ 单词长度 ≤ 50 \leq 50 ≤50
题解
本题可以使用动态规划来解决。设 d p [ i ] dp[i] dp[i] 表示将前 i i i 个字符拆分得到的最大权重和, f o r e [ i ] fore[i] fore[i] 表示前 i i i 个字符拆分得到最大权重和时,最后一个单词的前一个字符的下标。
我们可以从前往后枚举每个字符作为当前拆分的结束位置,然后枚举上一个单词的结束位置,判断中间这一段是否能在词典中找到。如果能找到,就更新 d p [ i ] dp[i] dp[i] 和 f o r e [ i ] fore[i] fore[i]。
最后,如果 d p [ n ] dp[n] dp[n] 的值没有被更新过,说明无法拆分,原样输出单词。否则,从 f o r e [ n ] fore[n] fore[n] 开始,依次向前跳转,就能得到拆分的单词序列。
参考代码
- Python
def main():n = int(input())words = {}for _ in range(n):word, weight = input().split()words[word] = int(weight)s = input()m = len(s)dp = [-float('inf')] * (m + 1)fore = [-1] * (m + 1)dp[0] = 0for i in range(1, m + 1):for j in range(i):word = s[j:i]if word in words and dp[j] + words[word] > dp[i]:dp[i] = dp[j] + words[word]fore[i] = jif dp[m] == -float('inf'):print(s)else:res = []i = mwhile i > 0:res.append(s[fore[i]:i])i = fore[i]print('/' + '/'.join(res[::-1]))if __name__ == "__main__":main()
- Java
import java.io.*;
import java.util.*;public class Main {public static void main(String[] args) throws IOException {BufferedReader br = new BufferedReader(new InputStreamReader(System.in));int n = Integer.parseInt(br.readLine());Map<String, Integer> words = new HashMap<>();for (int i = 0; i < n; i++) {String[] input = br.readLine().split(" ");words.put(input[0], Integer.parseInt(input[1]));}String s = br.readLine();int m = s.length();int[] dp = new int[m + 1];Arrays.fill(dp, Integer.MIN_VALUE);int[] fore = new int[m + 1];dp[0] = 0;for (int i = 1; i <= m; i++) {for (int j = 0; j < i; j++) {String word = s.substring(j, i);if (words.containsKey(word) && dp[j] + words.get(word) > dp[i]) {dp[i] = dp[j] + words.get(word);fore[i] = j;}}}if (dp[m] == Integer.MIN_VALUE) {System.out.println(s);} else {List<String> res = new ArrayList<>();int i = m;while (i > 0) {res.add(s.substring(fore[i], i));i = fore[i];}Collections.reverse(res);System.out.println("/" + String.join("/", res));}}
}
- Cpp
#include <iostream>
#include <unordered_map>
#include <vector>
#include <climits>
#include <algorithm>
using namespace std;int main() {int n;cin >> n;unordered_map<string, int> words;for (int i = 0; i < n; i++) {string word;int weight;cin >> word >> weight;words[word] = weight;}string s;cin >> s;int m = s.length();vector<int> dp(m + 1, INT_MIN);vector<int> fore(m + 1);dp[0] = 0;for (int i = 1; i <= m; i++) {for (int j = 0; j < i; j++) {string word = s.substr(j, i - j);if (words.count(word) && dp[j] + words[word] > dp[i]) {dp[i] = dp[j] + words[word];fore[i] = j;}}}if (dp[m] == INT_MIN) {cout << s << endl;} else {vector<string> res;int i = m;while (i > 0) {res.push_back(s.substr(fore[i], i - fore[i]));i = fore[i];}reverse(res.begin(), res.end());cout << "/" << res[0];for (int i = 1; i < res.size(); i++) {cout << "/" << res[i];}cout << endl;}return 0;
}
🎀 03.K小姐的幸运评论
题目描述
K小姐是一位著名的博主,她在社交平台上发布了一篇文章。文章发布后,粉丝们纷纷在评论区留下了自己的观点和看法。为了鼓励大家积极评论,K小姐决定从评论者中选出一位"最幸运评论者",送出神秘大奖。
评论者的选取规则如下:
- 每个评论都有一个唯一的编号,从 0 0 0 开始递增。
- 每个评论下面都有点赞和点踩功能,定义每条评论的净赞数量为该评论点赞数减去点踩数。净赞数量可以是负数。
- 在编号连续的评论段中,存在一个净赞数量之和最大的区间,记为区间 [ l , r ] [l,r] [l,r]。
- 在区间 [ l , r ] [l,r] [l,r] 内净赞数最多的评论者即为"最幸运评论者"。如果有并列,则取编号最大的评论者。
现在请你帮助K小姐设计一个程序,处理评论数据,找出这位幸运的粉丝。
输入格式
第一行包含一个整数 n n n,表示评论的总数。
第二行包含 n n n 个整数,每个整数表示一条评论的净赞数量。
输出格式
输出共 1 1 1 行,包含 5 5 5 个整数 l , r , m a x v , p , v l,r,maxv,p,v l,r,maxv,p,v,分别表示最大净赞区间的左右端点编号、区间内净赞总量、"最幸运评论者"的编号、该评论者的净赞数量。
样例输入
10
-99 -58 -52 7 89 48 43 -10 94 69
样例输出
3 9 340 8 94
数据范围
1 ≤ n ≤ 1 0 5 1 \leq n \leq 10^5 1≤n≤105
− 1 0 4 ≤ -10^4 \leq −104≤ 净赞数量 ≤ 1 0 4 \leq 10^4 ≤104
题解
本题是一道经典的最大子段和问题,但是在此基础上还需要找出最大子段中的最大值及其位置。
我们可以使用动态规划来解决最大子段和问题。定义 d p [ i ] dp[i] dp[i] 表示以位置 i i i 结尾的最大子段和, d p [ i ] dp[i] dp[i] 的状态转移方程为:
d p [ i ] = { n u m s [ i ] , if d p [ i − 1 ] ≤ 0 d p [ i − 1 ] + n u m s [ i ] , otherwise dp[i]=\begin{cases} nums[i] & ,\text{if } dp[i-1] \leq 0 \\ dp[i-1]+nums[i] &,\text{otherwise} \end{cases} dp[i]={nums[i]dp[i−1]+nums[i],if dp[i−1]≤0,otherwise
同时我们需要记录区间的左右端点、区间内的最大值及其位置。
- 当 d p [ i − 1 ] ≤ 0 dp[i-1] \leq 0 dp[i−1]≤0 时,说明前面的子段和为负,舍弃前面的部分,从位置 i i i 开始重新计算区间。
- 当 d p [ i − 1 ] > 0 dp[i-1]>0 dp[i−1]>0 时,前面的子段和为正,可以继续延伸当前区间。
- 记录最大子段和 m a x S u m maxSum maxSum,以及对应的左右端点 l , r l,r l,r。
- 用单独的变量 m a x N u m , p o s maxNum,pos maxNum,pos 记录区间内的最大值和对应下标。
最后,输出 l , r , m a x S u m , p o s , m a x N u m l,r,maxSum,pos,maxNum l,r,maxSum,pos,maxNum 即可。
时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)。其中 n n n 为数组长度。
参考代码
- Python
n = int(input())
nums = list(map(int, input().split()))l = r = 0
maxSum = float('-inf')
dp = [0] * n
dp[0] = nums[0]
maxNum = nums[0]
pos = 0for i in range(1, n):if dp[i-1] > 0:dp[i] = dp[i-1] + nums[i]else:dp[i] = nums[i]l = iif dp[i] > maxSum:maxSum = dp[i]r = iif nums[i] > maxNum:maxNum = nums[i]pos = iprint(l, r, maxSum, pos, maxNum)
- Java
import java.util.Scanner;public class Main {public static void main(String[] args) {Scanner sc = new Scanner(System.in);int n = sc.nextInt();int[] nums = new int[n];for (int i = 0; i < n; i++) {nums[i] = sc.nextInt();}int l = 0, r = 0;int maxSum = Integer.MIN_VALUE; int[] dp = new int[n];dp[0] = nums[0];int maxNum = nums[0];int pos = 0;for (int i = 1; i < n; i++) {if (dp[i-1] > 0) {dp[i] = dp[i-1] + nums[i];} else {dp[i] = nums[i];l = i;}if (dp[i] > maxSum) {maxSum = dp[i];r = i;}if (nums[i] > maxNum) {maxNum = nums[i];pos = i;}}System.out.print(l + " " + r + " " + maxSum + " " + pos + " " + maxNum);}
}
- Cpp
#include <iostream>
#include <vector>
using namespace std;int main() {int n;cin >> n;vector<int> nums(n);for (int i = 0; i < n; i++) {cin >> nums[i];}int l = 0, r = 0;int maxSum = INT_MIN;vector<int> dp(n);dp[0] = nums[0];int maxNum = nums[0];int pos = 0;for (int i = 1; i < n; i++) {if (dp[i-1] > 0) {dp[i] = dp[i-1] + nums[i];} else {dp[i] = nums[i];l = i;}if (dp[i] > maxSum) {maxSum = dp[i];r = i;}if (nums[i] > maxNum) {maxNum = nums[i];pos = i;}}cout << l << " " << r << " " << maxSum << " " << pos << " " << maxNum << endl;return 0;
}
写在最后
📧 KK这边最近正在收集近一年互联网各厂的笔试题汇总,如果有需要的小伙伴可以关注后私信一下 KK领取,会在飞书进行同步的跟新。