寒假CS每日打卡 Feb.16th


算法部分

1.Acwing 入门组每日一题
题目:Z字形扫描
在图像编码的算法中,需要将一个给定的方形矩阵进行 Z 字形扫描(Zigzag Scan)。
给定一个 n×n 的矩阵,Z 字形扫描的过程如下图所示:
zig.png
对于下面的 4×4 的矩阵,
1 5 3 9
3 7 5 6
9 4 6 4
7 3 1 3
对其进行 Z 字形扫描后得到长度为 16 的序列:1 5 3 9 7 3 9 5 4 7 3 6 6 4 1 3。
请实现一个 Z 字形扫描的程序,给定一个 n×n 的矩阵,输出对这个矩阵进行 Z 字形扫描的结果。

输入格式
输入的第一行包含一个整数 n,表示矩阵的大小。
输入的第二行到第 n+1 行每行包含 n 个正整数,由空格分隔,表示给定的矩阵。

输出格式
输出一行,包含 n×n 个整数,由空格分隔,表示输入的矩阵经过 Z 字形扫描后的结果。

数据范围
1≤n≤500,
矩阵元素为不超过 1000 的正整数。

输入样例:
4
1 5 3 9
3 7 5 6
9 4 6 4
7 3 1 3
输出样例:
1 5 3 9 7 3 9 5 4 7 3 6 6 4 1 3

题解:
  矩阵中的对角线横纵坐标和为常数,把握这一特征来解题,使用两层for循环,第一层for循环,遍历每一个对角线的和,从 0 到 2 * n - 2 ,内层for遍历起始的列坐标,这样子使用第一层for的变量减去列坐标就得到了横坐标,内层for循环的时候,使用bool标记来确定遍历的方向,因为我们是蛇形遍历矩阵的。

代码:

#include <iostream>
#include <algorithm>

using namespace std;

const int MAXN = 510;
int g[MAXN][MAXN];
int n;

int main(){
    bool op = false;

    cin >> n;

    for(int i = 0; i < n; i ++)
        for(int j = 0; j < n; j ++)
            cin >> g[i][j];

    for(int i = 0; i < 2 * n - 1; i ++){
    	//如果op为true 则 列坐标为i或者矩阵右边界,否则列坐标为0或者i - n + 1
        int j = op ? min(i, n - 1) : max(0, i - n + 1);
        for(; j >=0 && j < n && i - j >= 0 && i - j < n; ){
            cout << g[i - j][j] << " ";
            //op 为true 从右上到左下
            if(op)
                -- j;
            else	//从左下到右上
                ++ j;
        }
        op = !op;
    }

    return 0;
}

2.Acwing 提高组每日一题
题目:有趣的数
我们把一个数称为有趣的,当且仅当:
它的数字只包含 0,1,2,3,且这四个数字都出现过至少一次。
所有的 0 都出现在所有的 1 之前,而所有的 2 都出现在所有的 3 之前。
最高位数字不为 0。
因此,符合我们定义的最小的有趣的数是 2013。
除此以外,4 位的有趣的数还有两个:2031 和 2301。
请计算恰好有 n 位的有趣的数的个数。
由于答案可能非常大,只需要输出答案除以 109+7 的余数。

输入格式
输入只有一行,包括恰好一个正整数 n。

输出格式
输出只有一行,包括恰好 n 位的整数中有趣的数的个数除以 109+7 的余数。

数据范围
4≤n≤1000
输入样例:
4
输出样例:
3

题解:
  用到了数学组合数的知识,还没做出来。

3.LeetCode 每日一题
题目:数组拆分 I
给定长度为 2n 的整数数组 nums ,你的任务是将这些数分成 n 对, 例如 (a1, b1), (a2, b2), …, (an, bn) ,使得从 1 到 n 的 min(ai, bi) 总和最大。
返回该 最大总和 。

示例 1:
输入:nums = [1,4,3,2]
输出:4
解释:所有可能的分法(忽略元素顺序)为:

  1. (1, 4), (2, 3) -> min(1, 4) + min(2, 3) = 1 + 2 = 3
  2. (1, 3), (2, 4) -> min(1, 3) + min(2, 4) = 1 + 2 = 3
  3. (1, 2), (3, 4) -> min(1, 2) + min(3, 4) = 1 + 3 = 4
    所以最大总和为 4

示例 2:
输入:nums = [6,2,6,5,1,2]
输出:9
解释:最优的分法为 (2, 1), (2, 5), (6, 6). min(2, 1) + min(2, 5) + min(6, 6) = 1 + 2 + 6 = 9

提示:
1 <= n <= 104
nums.length == 2 * n
-104 <= nums[i] <= 104

题解:
  排序后贪心地选取邻接的两个数配对,这样得到的答案最大。
在这里插入图片描述

代码:

class Solution {
public:
    int arrayPairSum(vector<int>& nums) {
        int ans = 0;
        sort(nums.begin(), nums.end());
        for(int i = 0; i < nums.size(); i = i + 2)
            ans += nums[i];
        return ans;
    }
};

4.春招冲刺 – 预测赢家
题目:
给定一个表示分数的非负整数数组。 玩家 1 从数组任意一端拿取一个分数,随后玩家 2 继续从剩余数组任意一端拿取分数,然后玩家 1 拿,…… 。每次一个玩家只能拿取一个分数,分数被拿取之后不再可取。直到没有剩余分数可取时游戏结束。最终获得分数总和最多的玩家获胜。

给定一个表示分数的数组,预测玩家1是否会成为赢家。你可以假设每个玩家的玩法都会使他的分数最大化。

示例 1:
输入:[1, 5, 2]
输出:False
解释:一开始,玩家1可以从1和2中进行选择。
如果他选择 2(或者 1 ),那么玩家 2 可以从 1(或者 2 )和 5 中进行选择。如果玩家 2 选择了 5 ,那么玩家 1 则只剩下 1(或者 2 )可选。
所以,玩家 1 的最终分数为 1 + 2 = 3,而玩家 2 为 5 。
因此,玩家 1 永远不会成为赢家,返回 False 。

示例 2:
输入:[1, 5, 233, 7]
输出:True
解释:玩家 1 一开始选择 1 。然后玩家 2 必须从 5 和 7 中进行选择。无论玩家 2 选择了哪个,玩家 1 都可以选择 233 。
最终,玩家 1(234 分)比玩家 2(12 分)获得更多的分数,所以返回 True,表示玩家 1 可以成为赢家。

提示:
1 <= 给定的数组长度 <= 20.
数组里所有分数都为非负数且不会大于 10000000 。
如果最终两个玩家的分数相等,那么玩家 1 仍为赢家。

题解1:
  数据范围只有20,使用dfs复杂度为 O(2n),可以接受。
int dfs(int le, int ri) le为数组左区间,ri为数组右间, 那么深搜如下:
nums[le] 为先手拿左边的数值 dfs(le + 1, ri) 为拿了左边之后 后手拿到的最大数值,nums[k] - dfs(le + 1, ri) 如果大于0 则代表先手获胜

代码:

class Solution {
public:
    bool PredictTheWinner(vector<int>& nums) {
        return dfs(nums, 0, nums.size() - 1) >= 0;
    }

    int dfs(vector<int> &nums, int le, int ri){
        if(le == ri)
            return nums[le];
        //可以拿左边也可以拿右边
        int a = nums[le] - dfs(nums, le + 1, ri);
        int b = nums[ri] - dfs(nums, le, ri - 1);
        return max(a, b);
    }
};

题解2:
  动态规划,两层for循环从小区间到大区间递归解决,思路和dfs一样。

代码:

class Solution {
public:
    bool PredictTheWinner(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> dp(n, vector<int>(n, 0));
		//dp[i][i]为当前只有一个数的时候,先手拿到的数值
        for(int i = 0; i < nums.size(); i ++)
            dp[i][i] = nums[i];
		//从小区间向大区间递推
        for(int i = n - 2; i >= 0; i --){
            for(int j = i + 1; j < n; j ++){
            	//和dfs一样的思路
                dp[i][j] = max(nums[i] - dp[i + 1][j], nums[j] - dp[i][j - 1]);
            }
        }
        return dp[0][n - 1] >= 0;
    }
};

5.春招冲刺-- 发下午茶
题目:
有 K 名字节君,每天下午都要推着推车给字节的同学送下午茶,字节的同学分布在不同的工区,字节的工区分布和字节君的位置分布如下。
在这里插入图片描述
在上图中,每个方框内的单位长度为 1。已知字节君的推车可以装无限份下午茶,所以不需要字节君回到初始地点补充下午茶。每个字节君只有两个动作。
把推车向前移动一个单位。
把一份下午茶投放到当前工区。
现在告诉你字节君的数量以及每个工具需要的下午茶个数请问,所有的字节君最少花费多长时间才能送完所有的下午茶?

输入描述:
第一行是字节君的数量K和工区的数量 N
第二行 N 个数字是每个工区需要的下午茶数量 Ti

数据范围:
0< K,N <= 1000
0<= Ti <= 10000

输出描述:
输出一个数字代表所有字节均最少花费多长时间才能送完所有的下午茶

测试用例
样例 1
输入
3 3
7 1 1
输出
5
说明
字节君1:右移->放置->放置->放置->放置
字节君2:右移->放置->放置->放置
字节君3:右移->右移->放置->右移->放置

样例 2
输入
2 4
3 3 1 1
输出
7
说明
字节君1:右移->放置->放置->放置->右移->放置->放置
字节君2:右移->右移->放置->右移->放置->右移->放置

题解:
  问题具有单调性,正面求解有难度,不妨换个思路,二分所需要的时间,然后判断能否完成任务,可以的话缩小时间,否则扩大时间,在本题数据范围内是能通过的。

代码:

#include <iostream>
#include <cstring>

using namespace std;

const int MAXN = 1010;
//arr[i] 为第i个区域要的数量
int arr[MAXN], tmp[MAXN];
int n, m;

bool check(int mid){
    memcpy(tmp, arr, sizeof(tmp));
	//遍历n个字节君,从第m个区域倒过来遍历(也可以正序)
    for(int i = 0, j = m; i < n; i ++){
    	//首先减去走路的花费
        int t = mid - j;
        //当该字节君还能动
        while(t){
        	//能解决一个区域,则服务上一个区域
            if(t >= tmp[j]){
                t -= tmp[j];
                j --;
                //都服务完成了
                if(! j)
                    return true;
            }else{
                tmp[j] -= t;
                t = 0;
            }
        }
    }
    return false;
}

int main(){
    int a;

    cin >> n >> m;

    for(int i = 1; i <= m; i ++){
        cin >> arr[i];
    }
	//二分找答案
    int le = m, ri = 1e7 + 10, mid;
    while(le < ri){
        mid = le + ri >> 1;
        //找最短时间,所以更新右端点
        if(check(mid))
            ri = mid;
        else
            le = mid + 1;
    }
    cout << ri << endl;
    return 0;
}

6.春招冲刺 – 电话号码的字母组合
题目:
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
在这里插入图片描述
示例 1:
输入:digits = “23”
输出:[“ad”,“ae”,“af”,“bd”,“be”,“bf”,“cd”,“ce”,“cf”]

示例 2:
输入:digits = “”
输出:[]

示例 3:
输入:digits = “2”
输出:[“a”,“b”,“c”]

提示:
0 <= digits.length <= 4
digits[i] 是范围 [‘2’, ‘9’] 的一个数字。

题解:
  深搜即可,不是很难,这里提供一种递推的解法,使用Queue来模拟中间过程生成的字符串。

代码:

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        if(digits.empty())
            return {};
        string arr[] = {"", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};

        queue<string> a;
        a.push("");
        //遍历每一种拨打的数字        
        for(char c : digits){
        	//取得上一级队列中的所有元素
            int size = a.size();
            while(size --){
            	//取得中间字符串后出队
                string s = a.front();
                a.pop();
                //遍历字符串,要在末尾加上新的字符串后入队
                for(char c : arr[c - '0' - 1]){
                    a.push(s + c);
                }
            }
        }
        vector<string> b;
        while(!a.empty()){
            b.push_back(a.front());
            a.pop();
        }
        return b;
    }
};

7.夏季特惠
题目:
某公司游戏平台的夏季特惠开始了,你决定入手一些游戏。现在你一共有X元的预算,该平台上所有的 n 个游戏均有折扣,标号为 i 的游戏的原价ai​ 元,现价只要 bi 元(也就是说该游戏可以优惠 a i​ − b i 元)并且你购买该游戏能获得快乐值为 w i​ 。由于优惠的存在,你可能做出一些冲动消费导致最终买游戏的总费用超过预算,但只要满足获得的总优惠金额不低于超过预算的总金额,那在心理上就不会觉得吃亏。现在你希望在心理上不觉得吃亏的前提下,获得尽可能多的快乐值。

输入描述:
所有输入均为整型数。
第一行包含两个数 n 和 X,(1 <= n <= 500, 0 <= x <= 10,000)。
接下来 n 行包含每个游戏的信息,原价 ai,现价 bi,能获得的快乐值为 wi(0<=b_i<=a_i<=500, 1<=w_i<=1,000,000,000)
前 30% 的数据, 小数据集 (n<=15)
中间 30% 的数据,中等数据集 (n<=100)
后 40% 的数据,大数据集 (n<=500)

输出描述:
输出一个数字,表示你能获得的最大快乐值

测试用例
样例 1
输入
4 100
100 73 60
100 89 35
30 21 30
10 8 10
输出
100
说明
买 1、3、4 三款游戏,获得总优惠 38 元,总金额 102 元超预算 2 元,满足条件,获得 100 快乐值。

样例 2
输入
3 100
100 100 60
80 80 35
21 21 30
输出
60
说明
只能买下第一个游戏,获得 60 的快乐值。

样例 3
输入
2 100
100 30 35
140 140 100
输出
135
说明
两款游戏都买,第一款游戏获得优惠 70 元,总开销 170 元,超过预算 70 元,超出预算金额不大于获得优惠金额满足条件,因此可以获得最大快乐值为 135。

题解:
  背包问题变形,首先更新一下每个物品的重量,为 折后价 - (原价 - 折后价),然后每个物品的价值就是物品的快乐值,这样子就是标准的0 1 背包了,还要注意更新后的物品重量可能为负数,这里需要注意,为负的话直接累加到答案中,同时扩大我们的背包容量(又获得快乐值又给背包扩容了,白给 ><),该商品就不参与后面的dp了。

代码:

#include <iostream>

using namespace std;

const int MAXN = 1e5 + 10;
long long v[MAXN], w[MAXN], dp[MAXN];
int n, m;

int main(){
    long long a, b, ans = 0, t = 0, i = 0;

    cin >> n >> m;
    while(n --){
        cin >> a >> b >> v[i];
        //更新重量
        w[i] = b - (a - b);
        //小于0 则直接累加答案,同时给背包扩容
        if(w[i] < 0)
            m -= w[i], t += v[i];
        else
            i ++;
    }
    n = i;
	//标准的01背包 dp
    for(int i = 0; i < n; i ++){
        for(int j = m; j >= w[i]; j --){
            dp[j] = max(dp[j], dp[j - w[i]] + v[i]);
            ans = max(ans, dp[j]);
        }
    }
    //返回dp的答案和倒贴的商品
    cout << ans + t << endl;
    return 0;
}

8.春招冲刺 – 青蛙过河
题目:
一只青蛙想要过河。 假定河流被等分为 x 个单元格,并且在每一个单元格内都有可能放有一石子(也有可能没有)。 青蛙可以跳上石头,但是不可以跳入水中。
给定石子的位置列表(用单元格序号升序表示), 请判定青蛙能否成功过河(即能否在最后一步跳至最后一个石子上)。 开始时, 青蛙默认已站在第一个石子上,并可以假定它第一步只能跳跃一个单位(即只能从单元格1跳至单元格2)。
如果青蛙上一步跳跃了 k 个单位,那么它接下来的跳跃距离只能选择为 k - 1、k 或 k + 1个单位。 另请注意,青蛙只能向前方(终点的方向)跳跃。

请注意:
石子的数量 ≥ 2 且 < 1100;
每一个石子的位置序号都是一个非负整数,且其 < 231;
第一个石子的位置永远是0。

示例 1:
[0,1,3,5,6,8,12,17]
总共有8个石子。
第一个石子处于序号为0的单元格的位置, 第二个石子处于序号为1的单元格的位置,
第三个石子在序号为3的单元格的位置, 以此定义整个数组…
最后一个石子处于序号为17的单元格的位置。
返回 true。即青蛙可以成功过河,按照如下方案跳跃:
跳1个单位到第2块石子, 然后跳2个单位到第3块石子, 接着
跳2个单位到第4块石子, 然后跳3个单位到第6块石子,
跳4个单位到第7块石子, 最后,跳5个单位到第8个石子(即最后一块石子)。

示例 2:
[0,1,2,3,4,8,9,11]
返回 false。青蛙没有办法过河。
这是因为第5和第6个石子之间的间距太大,没有可选的方案供青蛙跳跃过去。

题解:
  深搜 + 记忆化。bool dp[i][j] 为当前在第i个位置,上次是迈出j个长度到达的,如果为false代表之前没走过,true代表之前走到过但是失败了,如果之前走到这里成功了,那么那一次深搜返回true会结束递归,就不会有别的深搜搜到这种情况,所以dp[i][j] 为true 代表这次尝试是失败的。

代码:

bool dp[2020][2020];

class Solution {
public:
    bool canCross(vector<int>& stones) {
        memset(dp, false, sizeof(dp));
        return dfs(stones, 0, 0);
    }

    bool dfs(vector<int> stones, int pos, int len){
        if(pos == stones.size() - 1)
            return true;
        //之前搜到过,return false
        if(dp[pos][len])
            return false;
		//遍历后面所有的石头
        for(int i = pos + 1; i < stones.size() ;i ++){
        	//误差在1之内
            if(abs(stones[pos] + len - stones[i]) <= 1){
            	//尝试深搜,如果成功直接返回true
                if(dfs(stones, i, stones[i] - stones[pos]))
                    return true;
            }
            //太远的石头直接跳过
            if(stones[pos] + len + 1 < stones[i])
                break;
        }
        //更新dp,记住这次搜素是失败的
        dp[pos][len] = true;
        return false;
    }
};

书籍部分

LeetBook 硬核操作系统指南 计算机系统博物馆 ✔
LeetBook 硬核 Linux 攻略 进程与线程 ✔

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值