寒假CS每日打卡 Feb.14th

这篇博客介绍了四道算法题目,包括数字反转、排序问题、实现具有最大值查询的队列以及寻找最接近目标值的子序列和。通过C++代码展示了如何解决这些问题,涉及二分查找、Floyd算法和单调队列等数据结构与算法技巧。
摘要由CSDN通过智能技术生成

算法部分

1.Acwing 入门组每日一题
题目:数字反转

给定一个整数,请将该数各个位上数字反转得到一个新数。
新数也应满足整数的常见形式,即除非给定的原数为零,否则反转后得到的新数的最高位数字不应为零。

输入格式
输入共1行,1个整数N。

输出格式
输出共1行,1个整数表示反转后的新数。

数据范围
|N|≤109
输入样例:
123
输出样例:
321
输入样例:
-380
输出样例:
-83

题解:
  简单的模拟题。

代码:

#include <iostream>

using namespace std;

int main(){
    int n, ans = 0;
    bool op = false;

    cin >> n;
    if(n < 0)
    	//使用op记录负数
        n = -n, op = true;

    while(n){
        ans = ans * 10 + n % 10;
        n /= 10;
    }
    if(op)
        ans = -ans;
    cout << ans << endl;
    return 0;
}

2.排序
题目:
给定 n 个变量和 m 个不等式。其中 n 小于等于26,变量分别用前 n 的大写英文字母表示。
不等式之间具有传递性,即若 A>B 且 B>C ,则 A>C。
请从前往后遍历每对关系,每次遍历时判断:
如果能够确定全部关系且无矛盾,则结束循环,输出确定的次序;
如果发生矛盾,则结束循环,输出有矛盾;
如果循环结束时没有发生上述两种情况,则输出无定解。

输入格式
输入包含多组测试数据。
每组测试数据,第一行包含两个整数n和m。
接下来m行,每行包含一个不等式,不等式全部为小于关系。
当输入一行0 0时,表示输入终止。

输出格式
每组数据输出一个占一行的结果。
结果可能为下列三种之一:
如果可以确定两两之间的关系,则输出 “Sorted sequence determined after t relations: yyy…y.”,其中’t’指迭代次数,'yyy…y’是指升序排列的所有变量。
如果有矛盾,则输出: “Inconsistency found after t relations.”,其中’t’指迭代次数。
如果没有矛盾,且不能确定两两之间的关系,则输出 “Sorted sequence cannot be determined.”。

数据范围
2≤n≤26,变量只可能为大写字母A~Z。

输入样例1:
4 6
A<B
A<C
B<C
C<D
B<D
A<B
3 2
A<B
B<A
26 1
A<Z
0 0
输出样例1:
Sorted sequence determined after 4 relations: ABCD.
Inconsistency found after 2 relations.
Sorted sequence cannot be determined.

输入样例2:
6 6
A<F
B<D
C<E
F<D
D<E
E<F
0 0
输出样例2:
Inconsistency found after 6 relations.

输入样例3:
5 5
A<B
B<C
C<D
D<E
E<A
0 0
输出样例3:
Sorted sequence determined after 4 relations: ABCDE.

题解:
  这种传递性的题目可以使用Floyd算法来求解,毕竟数据范围很小,O(n3)的复杂度能通过,使用Floyd算法能够根据已有不等式关系,得到所有传递得到的其他不等关系(传递性 A > B B > C 能够得到 A > C),使用二维数据g[i][j] 表示第 i 个元素和第 j 个元素的关系,若g[i][j]为 1,代表 i 元素小于 j 元素,所以,如果g[i][j] 和 g[j][i] 同时为1,那么是有冲突矛盾的,如果g[i][j] 和 g[j][i] 同时为0,那么代表这两个元素之间的关系是不能根据已有条件确定的。优化:可以根据已知条件二分来降低时间复杂度。

代码:

#include <iostream>
#include <string>
#include <cstring>
#include <algorithm>

using namespace std;

const int MAXN = 30;
bool g[MAXN][MAXN];
int n, m;
//二元关系
typedef pair<int, int> PII;
PII arr[400];

int floyd(int mid){
    memset(g, 0, sizeof(g));
    for(int i = 1; i <= mid; i ++){
    	//根据输入的二元关系更新二维矩阵
        g[arr[i].first][arr[i].second] = true;
    }
	//floyd算法
    for(int k = 0; k < n; k ++)
        for(int i = 0; i < n; i ++)
            for(int j = 0; j < n; j ++)
                g[i][j] |= (g[i][k] & g[k][j]);
    //先看有没有冲突
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < n; j ++)
            if(i != j && g[i][j] && g[j][i])
                return -1;
    //然后看有没有不能确定的元素
    //注意不能把这两个if放到同一个for里面写,因为一旦return 0 ,会影响到二分的正确性 
    //!!!我这里错了,debug了很久
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < n; j ++)
            if(i != j && !g[i][j] && !g[j][i])
                return 0;
    return 1;
}

int main(){
    string s;

    while(cin >> n && n){
        cin >> m;
        for(int i = 1; i <= m; i ++){
            cin >> s;
            //记录输入的二元关系
            arr[i] = {s[0] - 'A', s[2] - 'A'};
        }

        int le = 1, ri = m, mid;
        //二分求答案
        while(le < ri){
            mid = le + ri >> 1;
            int flag = floyd(mid);
            //flag为1 代表确定了所有关系 为-1代表有冲突,这两者都能够得到答案,所以mid需要减小
            if(flag)
                ri = mid;
            //flag代表有元素的大小关系不能确定
            else
                le = mid + 1;
        }
        int flag = floyd(ri);
        if(flag == 1){
            printf("Sorted sequence determined after %d relations: ", ri);
			//得到元素的大小关系
            int cnt[30];
            for(int i = 0; i < n; i ++){
                int t = 0;
                for(int j = 0; j < n; j ++){
                    if(i == j)
                        continue;
                    if(g[i][j])
                        ++ t;
                }
                //第 i 行 有 t 个 1,代表有t个元素大于i,所以第n - t - 1个位置放i
                cnt[n - t - 1] = i;
            }
            for(int i = 0; i < n; i ++)
                cout << (char)(cnt[i] + 'A');
            cout << '.' << endl;
        }else if(flag == -1)
            printf("Inconsistency found after %d relations.\n", ri);
        else
            printf("Sorted sequence cannot be determined.\n");
    }
    return 0;
}

3.队列的最大值
题目:
请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的均摊时间复杂度都是O(1)。
若队列为空,pop_front 和 max_value 需要返回 -1

示例 1:
输入:
[“MaxQueue”,“push_back”,“push_back”,“max_value”,“pop_front”,“max_value”]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]

示例 2:
输入:
[“MaxQueue”,“pop_front”,“max_value”]
[[],[],[]]
输出: [null,-1,-1]

限制:
1 <= push_back,pop_front,max_value的总操作数 <= 10000
1 <= value <= 10^5

题解:
  经典的单调队列又双叒来了,默写一遍框架,咳咳。

代码:

class MaxQueue {
public:
	//普通的队列
    queue<int> Q;
    //返回max的单调队列
    deque<int> DQ;

    MaxQueue() {}
    
    int max_value() {
        if(DQ.empty())
            return -1;
        else
            return DQ.front();
    }
    
    void push_back(int value) {
    	//将前面所有小于value的元素都pop
        while(!DQ.empty() && DQ.back() < value)
            DQ.pop_back();
        //加入当前元素
        DQ.push_back(value);
        Q.push(value);
    }
    
    int pop_front() {
        if(Q.empty())
            return -1;
        int t = Q.front();
        Q.pop();
        //如果出队的元素是队头,那么deque也要pop
        if(DQ.front() == t)
            DQ.pop_front();
        return t;
    }
};

4.最接近目标值的子序列和
题目:

给你一个整数数组 nums 和一个目标值 goal 。
你需要从 nums 中选出一个子序列,使子序列元素总和最接近 goal 。也就是说,如果子序列元素和为 sum ,你需要 最小化绝对差 abs(sum - goal) 。
返回 abs(sum - goal) 可能的 最小值 。
注意,数组的子序列是通过移除原始数组中的某些元素(可能全部或无)而形成的数组。

示例 1:
输入:nums = [5,-7,3,5], goal = 6
输出:0
解释:选择整个数组作为选出的子序列,元素和为 6 。
子序列和与目标值相等,所以绝对差为 0 。

示例 2:
输入:nums = [7,-9,15,-2], goal = -5
输出:1
解释:选出子序列 [7,-9,-2] ,元素和为 -4 。
绝对差为 abs(-4 - (-5)) = abs(1) = 1 ,是可能的最小值。

示例 3:
输入:nums = [1,2,3], goal = -7
输出:7

提示:
1 <= nums.length <= 40
-107 <= nums[i] <= 107
-109 <= goal <= 109

题解:
  挺好的一题,暴搜 + 二分查找。题意就是在数组中挑选一些数字(子集),使得这些数字和与goal的差最小,本质是暴搜,暴搜也有技巧,数据范围只有40,但是纯暴搜240会超时,所以将数据分为两半,le 和 ri 来分别暴搜,le + ri 就是选出来的和,这样子就可以将复杂度降低为220,先将前一半暴搜后的结果加入数组中,后一半暴搜得到ri,然后去前一半的数组中寻找le,找到一个最接近答案的数字。注意我们可以把le数组排序,然后二分和goal比较。

代码:

class Solution {
public:
    int minAbsDifference(vector<int>& nums, int goal) {
        int n = nums.size() / 2, m = nums.size() - n, ans = INT_MAX;
        //用来存储le暴搜的结果,有2^n个
        vector<int> q(1 << n);
		//使用位运算得到子集
        for(int i = 0; i < (1 << n); i ++){
            int t = 0;
            for(int j = 0; j < n; j ++){
                if((i >> j) & 1)
                    t += nums[j];
            }
            q[i] = t;
        }
		//排序
        sort(q.begin(), q.end());
        //ri也得到子集,每得到一个子集就二分去le数组中找最优解
        for(int i = 0; i < (1 << m); i ++){
            int t = 0;
            for(int j = 0; j < m; j ++){
                if((i >> j) & 1)
                    t += nums[j + n];
            }
            //二分找答案
            int le = 0, ri = q.size() - 1, mid;
            while(le < ri){
                mid = le + ri >> 1;
                if(q[mid] + t < goal)
                    le = mid + 1;
                else
                	//保证q[ri] + t >= goal
                    ri = mid;
            }
            //在ri和ri - 1 中找答案
            if(abs(t + q[ri] - goal) < ans)
                ans = abs(t + q[ri] - goal);
            if(ri)
                if(abs(t + q[ri - 1] - goal) < ans)
                    ans = abs(t + q[ri - 1] - goal);
        }
        return ans;
    }
};

5.袋子里最少数目的球
题目:
给你一个整数数组 nums ,其中 nums[i] 表示第 i 个袋子里球的数目。同时给你一个整数 maxOperations 。
你可以进行如下操作至多 maxOperations 次:
选择任意一个袋子,并将袋子里的球分到 2 个新的袋子中,每个袋子里都有 正整数 个球。
比方说,一个袋子里有 5 个球,你可以把它们分到两个新袋子里,分别有 1 个和 4 个球,或者分别有 2 个和 3 个球。
你的开销是单个袋子里球数目的 最大值 ,你想要 最小化 开销。
请你返回进行上述操作后的最小开销。

示例 1:
输入:nums = [9], maxOperations = 2
输出:3
解释:

  • 将装有 9 个球的袋子分成装有 6 个和 3 个球的袋子。[9] -> [6,3] 。
  • 将装有 6 个球的袋子分成装有 3 个和 3 个球的袋子。[6,3] -> [3,3,3] 。
    装有最多球的袋子里装有 3 个球,所以开销为 3 并返回 3 。

示例 2:
输入:nums = [2,4,8,2], maxOperations = 4
输出:2
解释:

  • 将装有 8 个球的袋子分成装有 4 个和 4 个球的袋子。[2,4,8,2] -> [2,4,4,4,2] 。
  • 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,4,4,4,2] -> [2,2,2,4,4,2] 。
  • 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,2,2,4,4,2] -> [2,2,2,2,2,4,2] 。
  • 将装有 4 个球的袋子分成装有 2 个和 2 个球的袋子。[2,2,2,2,2,4,2] -> [2,2,2,2,2,2,2,2] 。
    装有最多球的袋子里装有 2 个球,所以开销为 2 并返回 2 。

示例 3:
输入:nums = [7,17], maxOperations = 2
输出:7

提示:
1 <= nums.length <= 105
1 <= maxOperations, nums[i] <= 109

题解:
  又是二分查找(有点做🤮了吧,hhh), 二分来找答案,check若为true则缩小答案,否则扩大答案。注意每个袋子中的球都是独立的,所以若第 i 个袋子有nums[i] 个球, 最终每一个袋子只允许有 len 个球,那么需要把第 i 个袋子操作 (nums[i] - 1) / len 次。

代码:

class Solution {
public:
    int minimumSize(vector<int>& nums, int maxOperations) {
        int le = 1, ri = 1e9, mid;

        while(le < ri){
            mid = le + ri >> 1;
            if(check(mid, nums, maxOperations))
                ri = mid;
            else
                le = mid + 1;
        }
        return ri;
    }

    bool check(int len, vector<int> nums, int k){
        int cnt = 0;
        for(int i : nums){
            cnt += (i - 1) / len;
        }
        return cnt <= k;
    }
};

书籍部分

算法竞赛.进阶指南 0x61最短路 floyd ✔

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值