机试编程(六)
附注:此篇blog为根据论坛中回忆版真题从LeetCode、牛客网、HDU中找的类似题型,需要非常重视。
目录:
- 最低加油次数【LeetCode 871】
- 加油站【LeetCode 134】
- 一周中的第几天【LeetCode 1185】
- 日期之间隔几天【LeetCode 1360】
- 一年中的第几天【LeetCode 1154】
- 课程表III【LeetCode 630】
- 将整数按权重排序【LeetCode 1387】
- 删除排序数组中的重复项 II【LeetCode 80】
- 数组中的第K个最大元素【LeetCode 215】
- 合并K个排序链表【LeetCode 23】
- 最多能完成排序的块【LeetCode 769】
- 出界的路径数【LeetCode 576】
- 欧拉回路【浙江大学】
- 逃生【HDU 4857】
- 拼凑硬币【腾讯】
- 硬币兑换【京东】
- 硬币表示【程序员面试金典_ 编程题】
- 零钱兑换【LeetCode 322】
- 零钱兑换II【LeetCode 518】
- 排列硬币【LeetCode 441】
一、应用实例:
1、题目描述:汽车从起点出发驶向目的地,该目的地位于出发位置东面 target英里处。沿途有加油站,每个station[i]代表一个加油站,它位于出发位置东面station[i][0]英里处,并且有station[i][1]升汽油。假设汽车油箱的容量是无限的,其中最初有startFuel升燃料。它每行驶 1 英里就会用掉 1 升汽油。当汽车到达加油站时,它可能停下来加油,将所有汽油从加油站转移到汽车中。为了到达目的地,汽车所必要的最低加油次数是多少?如果无法到达目的地,则返回 -1 。注意:如果汽车到达加油站时剩余燃料为 0,它仍然可以在那里加油。如果汽车到达目的地时剩余燃料为 0,仍然认为它已经到达目的地。【LeetCode 871】
示例 1:
输入:target = 1, startFuel = 1, stations = []
输出:0
解释:我们可以在不加油的情况下到达目的地。
示例 2:
输入:target = 100, startFuel = 1, stations = [[10,100]]
输出:-1
解释:我们无法抵达目的地,甚至无法到达第一个加油站。
示例 3:
输入:target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]]
输出:2
解释:我们出发时有 10 升燃料。我们开车来到距起点 10 英里处的加油站,消耗 10 升燃料。将汽油从 0 升加到 60 升。然后,我们从 10 英里处的加油站开到 60 英里处的加油站(消耗 50 升燃料),并将汽油从 10 升加到 50 升。然后我们开车抵达目的地。我们沿途在1两个加油站停靠,所以返回 2 。
提示:
1 <= target, startFuel, stations[i][1] <= 10^9
0 <= stations.length <= 500
0 < stations[0][0] < stations[1][0] < ... < stations[stations.length-1][0] < target
示例代码:
class Solution {
public:
/**
target:目的地距离出发地target英里(1英里耗费1升油)
startFuel:初始邮箱容量值
stations[i]:一个加油站,离出发地stations[i][0]英里,加油站中有stations[i][1]升汽油
求最少加油次数
//能到下一个加油站就把其前面加油站的油桶带上,到不了就选前面的油桶中最多油的
*/
int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {
if(startFuel >= target){
return 0;
}
if(stations.size() == 0){
return -1;
}
priority_queue<int> myQueue;
int count = 0;
int currFuel = startFuel;//当前油量
int currDist = 0;//当前距离
for(int i = 0; i < stations.size(); i++){
if(currDist + currFuel >= target){
break;
}
while(currFuel < stations[i][0] - currDist){
if(myQueue.empty()){
return -1;
}
currFuel += myQueue.top();
count++;
myQueue.pop();
}
if(currFuel >= stations[i][0] - currDist){
myQueue.push(stations[i][1]);
currFuel -= (stations[i][0] - currDist);
currDist = stations[i][0];
}
}
int left = target - currDist - currFuel;
while(left > 0){
if(myQueue.empty()){
return -1;
}
left -= myQueue.top();
myQueue.pop();
count++;
}
return count;
}
};
2、题目描述:在一条环路上有N个加油站,其中第i个加油站有汽油gas[i]升。你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1个加油站需要消耗汽油cost[i]升。你从其中的一个加油站出发,开始时油箱为空。如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。【LeetCode 134】
说明:如果题目有解,该答案即为唯一答案。输入数组均为非空数组,且长度相同。输入数组中的元素均为非负数。
示例1:
输入: gas = [1,2,3,4,5],cost = [3,4,5,1,2]
输出: 3
解释:从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油。开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油。开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油。开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油。开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油。开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。因此,3 可为起始索引。
示例 2:
输入: gas = [2,3,4],cost = [3,4,3]
输出: -1
解释:你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油。开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油。开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油。你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。因此,无论怎样,你都不可能绕环路行驶一周。
示例代码:
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int gasSum = 0, costSum = 0;
for(int i = 0; i < gas.size(); i++){
gasSum += gas[i];
costSum += cost[i];
}
if(gasSum < costSum){
return -1;
}
int start = 0, currGas = 0;
for(int i = 0; i < gas.size(); i++){
currGas += gas[i] - cost[i];
if(currGas < 0){
start = i + 1;
currGas = 0;
}
}
return start;
}
};
3、题目描述:给你一个日期,请你设计一个算法来判断它是对应一周中的哪一天。输入为三个整数:day、month 和year,分别表示日、月、年。您返回的结果必须是这几个值中的一个?{"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"}。【LeetCode 1185】
输入:day = 31, month = 8, year = 2019
输出:"Saturday"
示例代码:
class Solution {
public:
string array[7] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
int monthDay[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
int JudgeYear(int year){
if(year % 400 == 0 || (year % 4 == 0 && year % 100 != 0)){
return 366;
}
return 365;
}
string dayOfTheWeek(int day, int month, int year) {
//1年1月1日是星期1
int sumDay = 0;
for(int i = 1; i < year; i++){
sumDay += JudgeYear(i);
}
if(JudgeYear(year) == 366){
monthDay[2]++;
}
for(int i = 1; i < month; i++){
sumDay += monthDay[i];
}
sumDay += day;
return array[sumDay % 7];
}
};
4、题目描述:请你编写一个程序来计算两个日期之间隔了多少天。日期以字符串形式给出,格式为 YYYY-MM-DD,如示例所示。【LeetCode 1360】
示例 1:
输入:date1 = "2019-06-29", date2 = "2019-06-30"
输出:1
示例 2:
输入:date1 = "2020-01-15", date2 = "2019-12-31"
输出:15
示例代码:
class Solution {
public:
int monthDay[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
int JudgeLeapYear(int year){
if(year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)){
return 366;
}
return 365;
}
int StringToInt(string s){
int result = 0;
for(int i = 0; i < s.size(); i++){
result = result * 10 + s[i] - '0';
}
return result;
}
int daysBetweenDates(string date1, string date2) {
string big = date1, small = date2;
if(date1.substr(0, 4) < date2.substr(0, 4)
|| date1.substr(0, 4) == date2.substr(0, 4) && date1.substr(5, 2) < date2.substr(5, 2)
|| date1.substr(0, 4) == date2.substr(0, 4) && date1.substr(5, 2) == date2.substr(5, 2) && date1.substr(8) < date2.substr(8)){
big = date2;
small = date1;
}
int year1 = StringToInt(big.substr(0, 4));
int year2 = StringToInt(small.substr(0, 4));
int month1 = StringToInt(big.substr(5, 2));
int month2 = StringToInt(small.substr(5, 2));
int day1 = StringToInt(big.substr(8));
int day2 = StringToInt(small.substr(8));
int sumDay = 0;
for(int i = year2; i < year1; i++){
sumDay += JudgeLeapYear(i);
}
int index;
if(JudgeLeapYear(year2) == 366){
index = 1;
}else{
index = 0;
}
for(int i = 1; i < month2; i++){
sumDay -= monthDay[index][i];
}
sumDay -= day2;
if(JudgeLeapYear(year1) == 366){
index = 1;
}else{
index = 0;
}
for(int i = 1; i < month1; i++){
sumDay += monthDay[index][i];
}
sumDay += day1;
return sumDay;
}
};
5、题目描述:给你一个按 YYYY-MM-DD 格式表示日期的字符串date,请你计算并返回该日期是当年的第几天。通常情况下,我们认为 1 月 1 日是每年的第 1 天,1 月 2 日是每年的第 2 天,依此类推。每个月的天数与现行公元纪年法(格里高利历)一致。【LeetCode 1154】
示例 1:
输入:date = "2019-01-09"
输出:9
示例 2:
输入:date = "2019-02-10"
输出:41
示例代码:
class Solution {
public:
int monthDay[2][13] = {
{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
{0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
};
bool JudgeLeapYear(int year){
if(year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)){
return true;
}
return false;
}
int StringToInt(string s){
int result = 0;
for(int i = 0; i < s.size(); i++){
result = result * 10 + s[i] - '0';
}
return result;
}
int dayOfYear(string date){
int year = StringToInt(date.substr(0, 4));
int month = StringToInt(date.substr(5, 2));
int day = StringToInt(date.substr(8));
int index = 0;
if(JudgeLeapYear(year)){
index = 1;
}
int sumDay = 0;
for(int i = 1; i < month; i++){
sumDay += monthDay[index][i];
}
sumDay += day;
return sumDay;
}
};
6、题目描述:这里有 n 门不同的在线课程,他们按从 1 到 n编号。每一门课程有一定的持续上课时间(课程时间)t 以及关闭时间第 d 天。一门课要持续学习 t 天直到第 d 天时要完成,你将会从第 1 天开始。给出 n 个在线课程用 (t, d) 对表示。你的任务是找出最多可以修几门课。【LeetCode 630】
示例:
输入: [[100, 200], [200, 1300], [1000, 1250], [2000, 3200]]
输出: 3
解释: 这里一共有 4 门课程, 但是你最多可以修 3 门:首先, 修第一门课时, 它要耗费 100 天,你会在第 100 天完成, 在第 101 天准备下门课。第二, 修第三门课时, 它会耗费 1000 天,所以你将在第 1100 天的时候完成它, 以及在第 1101 天开始准备下门课程。第三, 修第二门课时, 它会耗时 200 天,所以你将会在第 1300 天时完成它。第四门课现在不能修,因为你将会在第 3300 天完成它,这已经超出了关闭日期。
提示:整数 1 <= d, t, n <= 10,000 。你不能同时修两门课程。
示例代码:
class Solution {
public:
static bool CompareAsc(const vector<int> &c1, const vector<int> &c2){
if(c1[1] == c2[1]){
return c1[0] < c2[0];
}
return c1[1] < c2[1];
}
int scheduleCourse(vector<vector<int>>& courses){
if(courses.size() == 0){
return 0;
}
sort(courses.begin(), courses.end(), CompareAsc);
priority_queue<int> myQueue;
int currTime = 0;
for(int i = 0; i < courses.size(); i++){
if(currTime + courses[i][0] <= courses[i][1]){
myQueue.push(courses[i][0]);
currTime += courses[i][0];
}else{
if(!myQueue.empty() && courses[i][0] < myQueue.top()){
currTime -= (myQueue.top() - courses[i][0]);
myQueue.pop();
myQueue.push(courses[i][0]);
}
}
}
return myQueue.size();
}
};
7、题目描述:我们将整数x的权重定义为按照下述规则将x变成1所需要的步数:
如果x是偶数,那么x = x / 2;如果x是奇数,那么x = 3 * x + 1
比方说,x=3 的权重为 7 。因为 3 需要 7 步变成 1 (3 --> 10 --> 5 --> 16 --> 8 --> 4 --> 2 --> 1)。
给你三个整数lo,hi 和k。你的任务是将区间[lo, hi]之间的整数按照它们的权重升序排序,如果大于等于 2 个整数有相同的权重,那么按照数字自身的数值升序排序。请你返回区间[lo, hi]之间的整数按权重排序后的第k个数。【LeetCode 1387】
注意,题目保证对于任意整数x(lo <= x <= hi),它变成1 所需要的步数是一个 32 位有符号整数。
示例 1:
输入:lo = 12, hi = 15, k = 2
输出:13
解释:12 的权重为 9(12 --> 6 --> 3 --> 10 --> 5 --> 16 --> 8 --> 4 --> 2 --> 1),13 的权重为 9,14 的权重为 17,15 的权重为 17,区间内的数按权重排序以后的结果为 [12,13,14,15] 。对于 k = 2 ,答案是第二个整数也就是 13 。注意,12 和 13 有相同的权重,所以我们按照它们本身升序排序。14 和 15 同理。
示例 2:
输入:lo = 1, hi = 1, k = 1
输出:1
示例 3:
输入:lo = 7, hi = 11, k = 4
输出:7
解释:区间内整数 [7, 8, 9, 10, 11] 对应的权重为 [16, 3, 19, 6, 14] 。
按权重排序后得到的结果为 [8, 10, 11, 7, 9] 。
排序后数组中第 4 个数字为 7 。
1 <= lo <= hi <= 1000
1 <= k <= hi - lo + 1
示例代码:
class Solution {
public:
struct Node{
int val;
int weight;
Node(int v, int w):val(v), weight(w){};
};
static bool CompareAsc(const Node &n1, const Node &n2){
if(n1.weight == n2.weight){
return n1.val < n2.val;
}
return n1.weight < n2.weight;
}
int getKth(int lo, int hi, int k) {
vector<Node> nodeList;
for(int i = lo; i <= hi; i++){
int weight = 0, tmp = i;
while(tmp != 1){
if(tmp % 2 == 0){
tmp /= 2;
}else{
tmp = 3 * tmp + 1;
}
weight++;
}
nodeList.push_back(Node(i, weight));
}
sort(nodeList.begin(), nodeList.end(), CompareAsc);
return nodeList[k - 1].val;
}
};
8、题目描述:给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。【LeetCode 80】
示例1:给定 nums = [1,1,1,2,2,3],函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。你不需要考虑数组中超出新长度后面的元素。
示例2:给定 nums = [0,0,1,1,1,1,2,3,3],函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为0, 0, 1, 1, 2, 3, 3 。你不需要考虑数组中超出新长度后面的元素。
说明:为什么返回数值是整数,但输出的答案是数组呢?请注意,输入数组是以“引用”方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。
你可以想象内部操作如下:
// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);
// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中该长度范围内的所有元素。
for (int i = 0; i < len; i++) {
print(nums[i]);
}
示例代码1:
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size() <= 2){
return nums.size();
}
int count = 1;
for(int i = 0; i < nums.size() - 1; ){
if(nums[i] == nums[i + 1]){
count++;
if(count > 2){
nums.erase(nums.begin() + i);
count--;
}else{
i++;
}
}else{
count = 1;
i++;
}
}
return nums.size();
}
};
示例代码2:
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size() <= 2){
return nums.size();
}
int index = 0;
for(int i = 0; i < nums.size(); i++){
if(index < 2 || nums[i] != nums[index - 2]){
nums[index++] = nums[i];
}
}
return index;
}
};
9、题目描述:在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。【LeetCode 215】
示例1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
说明:你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。
示例代码1:
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
sort(nums.begin(), nums.end());
return nums[nums.size() - k];
}
};
示例代码2:
class Solution {
public:
void QuickSort(vector<int> &nums, int low, int high){
if(low < high){
int pivot = Partition(nums, low, high);
QuickSort(nums, low, pivot - 1);
QuickSort(nums, pivot + 1, high);
}
}
int Partition(vector<int> &nums, int low, int high){
int pivot = nums[low];
while(low < high){
while(low < high && nums[high] >= pivot){
high--;
}
nums[low] = nums[high];
while(low < high && nums[low] <= pivot){
low++;
}
nums[high] = nums[low];
}
nums[low] = pivot;
return low;
}
int findKthLargest(vector<int>& nums, int k) {
QuickSort(nums, 0, nums.size() - 1);
return nums[nums.size() - k];
}
};
10、题目描述:合并k个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。【LeetCode 23】
示例:
输入:[1->4->5,1->3->4,2->6]
输出: 1->1->2->3->4->4->5->6
示例代码1:(96ms,11MB)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeKLists(vector<ListNode*>& lists) {
priority_queue<int, vector<int>, greater<int> > myQueue;
for(int i = 0; i < lists.size(); i++){
while(lists[i]){
myQueue.push(lists[i]->val);
lists[i] = lists[i]->next;
}
}
ListNode *result = new ListNode(-1);
ListNode *p = result;
while(!myQueue.empty()){
p->next = new ListNode(myQueue.top());
myQueue.pop();
p = p->next;
}
return result->next;
}
};
示例代码2:(336ms,10.3MB)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode *list1, ListNode *list2){
if(list1 == NULL){
return list2;
}
if(list2 == NULL){
return list1;
}
ListNode *head = NULL;
if(list1->val < list2->val){
head = list1;
head->next = mergeTwoLists(list1->next, list2);
}else{
head = list2;
head->next = mergeTwoLists(list1, list2->next);
}
return head;
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.size() == 0){
return NULL;
}else if(lists.size() == 1){
return lists[0];
}else if(lists.size() == 2){
return mergeTwoLists(lists[0], lists[1]);
}
for(int i = 1; i < lists.size(); i++){
lists[i] = mergeTwoLists(lists[i - 1], lists[i]);
}
return lists[lists.size() - 1];
}
};
示例代码3:(108ms,28.7MB)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode *list1, ListNode *list2){
if(list1 == NULL){
return list2;
}
if(list2 == NULL){
return list1;
}
ListNode *head = NULL;
if(list1->val < list2->val){
head = list1;
head->next = mergeTwoLists(list1->next, list2);
}else{
head = list2;
head->next = mergeTwoLists(list1, list2->next);
}
return head;
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
if(lists.size() == 0){
return NULL;
}else if(lists.size() == 1){
return lists[0];
}else if(lists.size() == 2){
return mergeTwoLists(lists[0], lists[1]);
}
vector<ListNode *> leftList;
for(int i = 0; i < lists.size() / 2; i++){
leftList.push_back(lists[i]);
}
vector<ListNode *> rightList;
for(int i = lists.size() / 2; i < lists.size(); i++){
rightList.push_back(lists[i]);
}
return mergeTwoLists(mergeKLists(leftList), mergeKLists(rightList));
}
};
11、题目描述:数组arr是[0, 1, ..., arr.length - 1]的一种排列,我们将这个数组分割成几个“块”,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。我们最多能将数组分成多少块?【LeetCode 769】
示例 1:
输入: arr = [4,3,2,1,0]
输出: 1
解释:将数组分成2块或者更多块,都无法得到所需的结果。
例如,分成 [4, 3], [2, 1, 0] 的结果是 [3, 4, 0, 1, 2],这不是有序的数组。
示例 2:
输入: arr = [1,0,2,3,4]
输出: 4
解释:我们可以把它分成两块,例如 [1, 0], [2, 3, 4]。然而,分成 [1, 0], [2], [3], [4] 可以得到最多的块数。
注意:arr 的长度在 [1, 10] 之间。arr[i]是 [0, 1, ..., arr.length - 1]的一种排列。
示例代码:
class Solution {
public:
//区间最大值等于数组索引位一个块
int maxChunksToSorted(vector<int>& arr) {
int count = 0;
int maxValue = arr[0];
for(int i = 0; i < arr.size(); i++){
maxValue = max(maxValue, arr[i]);
if(i == maxValue){
count++;
}
}
return count;
}
};
12、题目描述:给定一个 m × n 的网格和一个球。球的起始坐标为(i,j),你可以将球移到相邻的单元格内,或者往上、下、左、右四个方向上移动使球穿过网格边界。但是,你最多可以移动N次。找出可以将球移出边界的路径数量。答案可能非常大,返回 结果 mod 10^9+ 7 的值。球一旦出界,就不能再被移动回网格内。网格的长度和高度在 [1,50] 的范围内。N 在 [0,50] 的范围内。【LeetCode 576】
输入: m = 2, n = 2, N = 2, i = 0, j = 0
输出: 6
输入: m = 1, n = 3, N = 3, i = 0, j = 1
输出: 12
示例代码:
class Solution {
public:
static const int MAX_N = 51;
static const int MOD = 1000000007;
int dp[MAX_N][MAX_N][MAX_N];//dp[i][j][k]表示从坐标(i,j)出发,在运行k步时最多的路径数
int findPathsTmp(int m, int n, int N, int i, int j){
if(N < 0){
return 0;
}
if(i + N < m && i - N >= 0 && j + N < n && j - N >= 0){
return dp[i][j][N] = 0;
}
if(i == m || j == n || i == -1 || j == -1){
return 1;
}
if(dp[i][j][N] != -1){
return dp[i][j][N];
}
int result = 0;
//上、下、左、右
result = (result + findPathsTmp(m, n, N - 1, i - 1, j)) % MOD;
result = (result + findPathsTmp(m, n, N - 1, i + 1, j)) % MOD;
result = (result + findPathsTmp(m, n, N - 1, i, j - 1)) % MOD;
result = (result + findPathsTmp(m, n, N - 1, i, j + 1)) % MOD;
return dp[i][j][N] = result;
}
int findPaths(int m, int n, int N, int i, int j) {
if(N == 0){
return 0;
}
memset(dp, -1, sizeof(dp));
findPathsTmp(m, n, N, i, j);
return dp[i][j][N];
}
};
13、题目描述:欧拉回路是指不令笔离开纸面,可画过图中每条边仅一次,且可以回到起点的一条回路。现给定一个图,问是否存在欧拉回路?【浙江大学】
- 输入格式:测试输入包含若干测试用例。每个测试用例的第1行给出两个正整数,分别是节点数N ( 1 < N < 1000 )和边数M;随后的M行对应M条边,每行给出一对正整数,分别是该条边直接连通的两个节点的编号(节点从1到N编号)。当N为0时输入结束。
- 输出格式:每个测试用例的输出占一行,若欧拉回路存在则输出1,否则输出0。
- 样例输入:
- 3 3
- 1 2
- 1 3
- 2 3
- 3 2
- 1 2
- 2 3
- 0
- 样例输出:
- 1
- 0
示例代码:
#include <iostream>
using namespace std;
const int MAX_N = 1001;
int father[MAX_N];
int height[MAX_N];
int degree[MAX_N];
int use[MAX_N];
int FindFather(int x){
while(x != father[x]){
x = father[x];
}
return father[x];
}
void Union(int x, int y){
x = FindFather(x);
y = FindFather(y);
if(x != y){
if(height[x] > height[y]){
father[y] = x;
}else if(height[x] < height[y]){
father[x] = y;
}else{
father[x] = y;
height[y]++;
}
}
}
int main(){
int n, m;
while(cin >> n){
if(n == 0){
break;
}
cin >> m;
for(int i = 1; i <= n; i++){
father[i] = i;
degree[i] = 0;
height[i] = 0;
use[i] = 0;
}
int from, to;
for(int i = 0; i < m; i++){
cin >> from >> to;
degree[to]++;
degree[from]++;
use[from] = 1;
use[to] = 1;
Union(from, to);
}
int sumUnion = 0, degreeFlag = 1;
for(int i = 1; i <= n; i++){
if(degree[i] % 2 != 0){
degreeFlag = 0;
break;
}
if(use[i] == 1 && FindFather(i) == i){
sumUnion++;
if(sumUnion > 1){
break;
}
}
}
if(sumUnion == 1 && degreeFlag == 1){
cout << 1 << endl;
}else{
cout << 0 << endl;
}
}
return 0;
}
附注:
(1)一个无向图存在欧拉回路,当且仅当该图所有顶点度数都为偶数,且该图是连通图。
(2)一个有向图存在欧拉回路,所有顶点的入度等于出度且该图是连通图。
14、题目描述:糟糕的事情发生啦,现在大家都忙着逃命。但是逃命的通道很窄,大家只能排成一行。现在有n个人,从1标号到n。同时有一些奇怪的约束条件,每个都形如:a必须在b之前。同时,社会是不平等的,这些人有的穷有的富。1号最富,2号第二富,以此类推。有钱人就贿赂负责人,所以他们有一些好处。负责人现在可以安排大家排队的顺序,由于收了好处,所以他要让1号尽量靠前,如果此时还有多种情况,就再让2号尽量靠前,如果还有多种情况,就让3号尽量靠前,以此类推。那么你就要安排大家的顺序。我们保证一定有解。【HDU 4857】
- 输入格式:第一行一个整数T(1 <= T <= 5),表示测试数据的个数。然后对于每个测试数据,第一行有两个整数n(1 <= n <= 30000)和m(1 <= m <= 100000),分别表示人数和约束的个数。然后m行,每行两个整数a和b,表示有一个约束a号必须在b号之前。a和b必然不同。
- 输出格式:对每个测试数据,输出一行排队的顺序,用空格隔开。
- 样例输入:
- 1
- 5 10
- 3 5
- 1 4
- 2 5
- 1 2
- 3 4
- 1 4
- 2 3
- 1 5
- 3 5
- 1 2
- 样例输出:
- 1 2 3 4 5
示例代码:
#include <iostream>
#include <vector>
#include <cstring>
#include <queue>
#include <cstdio>
using namespace std;
const int MAX_N = 30001;
vector<int> graph[MAX_N];
int indegree[MAX_N];
int resultList[MAX_N];
int index;
void TopoSort(int n){
priority_queue<int> myQueue;
for(int i = 1; i <= n; i++){
if(indegree[i] == 0){
myQueue.push(i);
}
}
while(!myQueue.empty()){
int u = myQueue.top();
resultList[++index] = u;
myQueue.pop();
for(int i = 0; i < graph[u].size(); i++){
int v = graph[u][i];
indegree[v]--;
if(indegree[v] == 0){
myQueue.push(v);
}
}
}
return;
}
int main(){
int T, n, m;
cin >> T;
for(int i = 0; i < T; i++){
cin >> n >> m;
for(int j = 0; j <= n; j++){
graph[j].clear();
}
memset(indegree, 0, sizeof(indegree));
int from, to;
for(int j = 0; j < m; j++){
scanf("%d%d", &from, &to);
graph[to].push_back(from);
indegree[from]++;
}
index = 0;
TopoSort(n);
printf("%d", resultList[index]);
for(int j = index - 1; j > 0; j--){
printf(" %d", resultList[j]);
}
printf("\n");
}
return 0;
}
附注:
(1)大量数据输入时,不要用cin,用scanf!!!
15、题目描述:小Q十分富有,拥有非常多的硬币,小Q拥有的硬币是有规律的,对于所有的非负整数K,小Q恰好各有两个面值为2^K的硬币,所以小Q拥有的硬币就是1,1,2,2,4,4,8,8,…。小Q有一天去商店购买东西需要支付n元钱,小Q想知道有多少种方案从他拥有的硬币中选取一些拼凑起来恰好是n元(如果两种方案某个面值的硬币选取的个数不一样就考虑为不一样的方案)。【2018校招真题在线编程】
- 输入格式:输入包括一个整数n(1≤n≤10^18),表示小Q需要支付多少钱。注意n的范围。
- 输出格式:输出一个整数,表示小Q可以拼凑出n元钱的方案数。
- 样例输入:
- 6
- 样例输出:
- 3
示例代码:
#include <iostream>
#include <map>
#include <cstdio>
using namespace std;
map<long long, int> myMap;
int Count(long long n){
if(myMap.find(n) != myMap.end()){
return myMap[n];
}
if(n % 2 == 0){
return myMap[n] = Count(n>>1) + Count((n>>1) - 1);
}else{
return myMap[n] = Count(n>>1);
}
}
int main(){
long long n;
myMap.insert(pair<long long, int>(1, 1));
myMap.insert(pair<long long, int>(2, 2));
while(cin >> n){
cout << Count(n) << endl;
}
return 0;
}
16、题目描述:A 国一共发行了几种不同面值的硬币,分别是面值 1 元,2 元,5 元,10 元,20 元,50 元, 100 元。假设每种面值的硬币数量是无限的,现在你想用这些硬币凑出总面值为 n 的硬币, 同时你想让选出的硬币中,不同的面值种类尽可能多;在面值种类尽可能多的情况下,你想 让选择的硬币总数目尽可能多,请问应该怎么选择硬币呢?【京东】
- 输入格式:第一行包含一个数字𝑛,表示要凑出的面值。1 ≤ 𝑛 ≤ 10^9
- 输出格式:输出两个整数,分别表示最多能有多少种类型的硬币以及在类型最多的情况下最多能用上多少枚硬币。
- 样例输入:
- 3
- 样例输出:
- 2 2
示例代码:
#include <iostream>
using namespace std;
int moneyCategory[7] = {1, 2, 5, 10, 20 ,50, 100};
int main(){
int n;
while(cin >> n){
int cate = 0;
for(int i = 0; i < 7; i++){
if(moneyCategory[i] <= n){
n -= moneyCategory[i];
cate++;
}else{
break;
}
}
cout << cate << " " << cate + n << endl;
}
return 0;
}
17、题目描述:有数量不限的硬币,币值为25分、10分、5分和1分,请编写代码计算n分有几种表示法。给定一个int n,请返回n分有几种表示法。保证n小于等于100000,为了防止溢出,请将答案Mod 1000000007。【程序员面试金典_ 编程题】
- 输入格式:n分
- 输出格式:表示法数目
- 样例输入:6
- 样例输出:2
示例代码:
class Coins {
public:
static const int MAX_N = 100010;
static const int MOD = 1000000007;
int dp[MAX_N];//dp[i]表示硬币凑成j元的方案数
int money[4] = {1, 5, 10, 25};
int countWays(int n) {
memset(dp, 0, sizeof(dp));
dp[0] = 1;
for(int i = 0; i < 4; i++){
for(int j = money[i]; j <= n; j++){
dp[j] = (dp[j] + dp[j - money[i]]) % MOD;
}
}
return dp[n];
}
};
18、题目描述:给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。【LeetCode 322】
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
示例代码(动态规划):
class Solution {
public:
static const int MAX_N = 10000;
const int MAX_INT = 0x7fffffff;
int dp[MAX_N];//dp[i]表示价值为i元的最少取法
int coinChange(vector<int>& coins, int amount) {
fill(dp, dp + amount + 1, MAX_INT);
dp[0] = 0;
for(int i = 0; i < coins.size(); i++){
for(int j = coins[i]; j <= amount; j++){
if(dp[j - coins[i]] != MAX_INT){
dp[j] = min(dp[j - coins[i]] + 1, dp[j]);
}
}
}
if(dp[amount] == MAX_INT){
return -1;
}
return dp[amount];
}
};
19、题目描述:给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。 【LeetCode 518】
示例 1:
输入: amount = 5, coins = [1, 2, 5]
输出: 4
解释: 有四种方式可以凑成总金额:5=5,5=2+2+1,5=2+1+1+1,5=1+1+1+1+1
示例 2:
输入: amount = 3, coins = [2]
输出: 0
解释: 只用面额2的硬币不能凑成总金额3。
示例 3:
输入: amount = 10, coins = [10]
输出: 1
示例代码:
class Solution {
public:
const int MAX_N = 5010;
const int MAX_INT = 0x7fffffff;
int change(int amount, vector<int>& coins) {
int dp[MAX_N];
memset(dp, 0, sizeof(dp));
dp[0] = 1;
for(int i = 0; i < coins.size(); i++){
for(int j = coins[i]; j <= amount; j++){
dp[j] += dp[j - coins[i]];
}
}
if(dp[amount] == MAX_INT){
return 0;
}
return dp[amount];
}
};
20、题目描述:你总共有 n 枚硬币,你需要将它们摆成一个阶梯形状,第 k 行就必须正好有 k 枚硬币。给定一个数字 n,找出可形成完整阶梯行的总行数。n 是一个非负整数,并且在32位有符号整型的范围内。【LeetCode 441】
示例 1:
n = 5
硬币可排列成以下几行:
¤
¤ ¤
¤ ¤
因为第三行不完整,所以返回2.
示例 2:
n = 8
硬币可排列成以下几行:
¤
¤ ¤
¤ ¤ ¤
¤ ¤
因为第四行不完整,所以返回3.
示例代码:
class Solution {
public:
// (首项 + 末项) * 项数 / 2 = (1 + n) * n / 2 <= total
int arrangeCoins(int n) {
int level = 1;
while(level / 2.0 <= n / (level + 1.0)){
level++;
}
return level - 1;
}
};
参考文献:
[1]杨泽邦、赵霖. 计算机考研——机试指南(第2版). [M]北京:电子工业出版社,2019.11;