Skip to content

大二上开学第一周

约 2679 字大约 9 分钟

2025-09-14

正式进入新专业学习,第一次接触科班内容,略显兴奋。一周做了很多题,光课内题目就不少,科班的编程强度确实还是远超其它工科的。

课内做题

课内不少课程都有安排编程习题训练。挑几道需要动点脑子的来讲。

软工实训初级

糖果

有 n 个孩子站成一排,每个孩子有一个评分(ratings)。你需要按照以下要求给他们分发糖果:

1. 每个孩子至少分配到 1 个糖果
2. 相邻的孩子中,评分更高的孩子必须比他邻居获得更多的糖果

目标是计算出最少需要准备多少颗糖果。

一开始的思路是贪心,从左往右扫,保证相邻评分更高的孩子糖果一定更多。但是发现扫描过的索引很难维护(有可能无法维护)遂放弃。

标准解法思路是从左往右扫描一次,先保证右比左大的情况全部成立,再从右往左扫,维护左比右大的情况。解法如下:

#include <iostream>

int n;
int ratings[1024] {};
int cnt[1024] {};
int all = 0;

int main() {
    std::cin >> n;
    for (int i = 0; i < n; i++) {
        std::cin >> ratings[i];
        cnt[i] = 1;
    }
    for (int i = 0; i < n; i++) {
        if (i == 0) continue;
        if (ratings[i] > ratings[i - 1]) {
            cnt[i] = cnt[i - 1] + 1;
        }
    }
    for (int i = n - 2; i >= 0; i--) {
        if (n == 0) continue;
        if (ratings[i] > ratings[i + 1]) {
            cnt[i] = std::max(cnt[i], cnt[i + 1] + 1);
        }
    }
    for (int i = 0; i < n; i++) {
        all += cnt[i];
    }
    std::cout << all << std::endl;
}

颜色排序

有一个包含三种颜色球的序列,颜色分别为 red(红色)、green(绿色)、blue(蓝色)。需要将这些球按照特定顺序进行排序。

具体要求:

1. 输入是一系列颜色名称字符串,可能包含 "red"、"green" 和 "blue"
2. 需要将这些颜色按照特定顺序重新排列:red、green、blue(按照这个顺序输出)

老师要求不能用stl的sort函数,但说实在这题即便自己手搓普通排序算法也挺没意思的,遂考虑学字符串时学的计数排序。

写这段代码时本来是照着LSD写的,但后来发现不用那么麻烦,可是我也懒得改了。

#include <iostream>
#include <string>
#include <map>

int main() {
    int count[4] {0};
    std::map<std::string, int> color_map {{"blue", 2}, {"green", 1}, {"red", 0}};
    std::string str;
    while (std::cin >> str) {
        if (str == "blue") {
            count[color_map["blue"] + 1]++;
        } else if (str == "red") {
            count[color_map["red"] + 1]++;
        } else if (str == "green") {
            count[color_map["green"] + 1]++;
        }
    }
    
    for (int i = 0; i < count[color_map["red"] + 1]; i++) {
        std::cout << "red ";
    }
    for (int i = 0; i < count[color_map["green"] + 1]; i++) {
        std::cout << "green ";
    }
    for (int i = 0; i < count[color_map["blue"] + 1]; i++) {
        std::cout << "blue ";
    }

}

数据结构与算法

这门课题不多就四道,但强度比实训高多了。Matrix挺刺激的,还能滚榜。

蛇形图案

给出一个不大于 9 的正整数 n,输出 n×n 的蛇形方阵。

从左上角填上 1 开始,顺时针方向依次填入数字,如同样例所示。注意每个数字有都会占用 3 个字符,前面使用空格补齐。

没思路时就先把不用动脑的部分做了。首先把数组打出来,这里用了向量,懒得手动分配内存维护动态数组了。

int n;
std::vector<std::vector<int>> arr;
std::cin >> n;

for (int i = 0; i < n; i++) {
    std::vector<int> temp;
    for (int j = 0; j < n; j++) {
        temp.push_back(0);
    }
    arr.push_back(temp);
}

这个题重点是找到数字增大的方向,不然很容易往dfs的方向想,复杂化了。初始化横纵坐标,写好大的循环条件。我们从头开始,数字是从左往右增大的。

int x {0}, y {0};
while (cnt <= n * n) {
    while (y < n && arr[x][y] == 0) {
        arr[x][y++] = cnt++;
    }

这时循环停止时我们会发现指针落在了数组边界右边,这时我们往左回退一步,再往下走一步,开始第二次增大。

y--;x++;

while (x < n && arr[x][y] == 0) {
    arr[x++][y] = cnt++;
}

这时又走到边界下方了。我们易得规律。数字增大方向必定是“右->下->左->上”这样循环,所以得到循环内部所有代码

while (cnt <= n * n) {
    while (y < n && arr[x][y] == 0) {
        arr[x][y++] = cnt++;
    }


    y--;x++;
    
    while (x < n && arr[x][y] == 0) {
        arr[x++][y] = cnt++;
    }
    x--;y--;
    while (y >= 0 && arr[x][y] == 0) {
        arr[x][y--] = cnt++;
    }
    y++;x--;
    while (x >= 0 && arr[x][y] == 0) {
        arr[x--][y] = cnt++;
    }
    x++;y++;
}

最后根据要求输出即可。

for (int i = 0; i < n; i++) {
    for (int j = 0; j < n; j++) {
        printf("%3d ", static_cast<int>(arr[i][j]));
    }
    std::cout << std::endl;
}

神秘数字

神秘数字指质因子仅包含 2,3,5 的数。特别地,1 也是神秘数字。

请找出第 1500 个神秘数字。

这周四道题里最难的一道。Leetcode和洛谷都有,题目原名叫丑数。正规做法应该是维护最小堆或者直接动态规划。但是数据结构与算法这门课才开第一个星期,不可能直接这样超纲,所以用点本方法。

一开始想从1开始往上一个个数判断是否为丑数,但是这样百分之百TLE的,所以想着从小往上用数学性质找到所有丑数,但发现正常找法必定会漏,除非维护一个很胖的树状结构。

最后思路是通过排列组合找到大部分数,然后进行排序(很容易漏就是了,但我相信这题没这么难,毕竟开学第一周)

for (int i = 0; i < 32; i++) {
    for (int j = 0; j < 20; j++) {
        for (int k = 0; k < 15; k++) {
            unsigned long long c = 1;
            for(int a = 0; a < i; a++) c *= 2;
            for(int a = 0; a < j; a++) c *= 3;
            for(int a = 0; a < k; a++) c *= 5;
            
            int jud = 0;
            for (unsigned long long s : list) {
                if (s == c) jud = 1;
            }
            if (jud) continue;
            else list.push_back(c);
        }
    }
}

我这里还犯了一个很蠢的错误,2,3,5三个数互质,所以我根本不需要每次生成新数字都判断一下有没有统计过。

值得一题的是这题还有一个坑,一开始我估算的1500 < 15 * 15 * 15,所以三层循环都是以15结束,但是发现输出的数总是不对。最后想到2小,所以2和3都尽量要列多几次,所以提高了循环次数,得到正确答案。这就是我这个方法的问题,不适用所有情况,全凭狗运。以后有时间再码最小堆优先队列那个方法。

代码最后输出这个vector里1499索引的数即可。

课外做题

力扣热门100做了一点,基本集中在链表、双(快慢)指针这类专项。题目思路重复性比较大,没啥想说的。环形链表 II、相交链表都利用了数学性质,剩下的都是基本功。最想吐槽回文链表,直接反转一遍判断一不一样就行了。

课外活动

活动报名网站

帮高中工作过的学生组织部署了个静态网站,用来收集报名信息。本来很简单的CRUD内容,却搞出了不少问题。

后端用的supabase个人免费计划,不可能出问题,事实也如此。问题集中在前端。前端是用AI写的,由于写的时候只做了外观要求,功能要求阐述不够清晰且没有和后端做联动,所以出了几个岔子。

1. 异常处理约等于没有

学了这么久编程,一直不太重视异常处理,这次真是给我整糊涂了。网站前端的设计问题,报名成功的信息是在任何情况下百分百会弹出的,这使得报名即便失败(数据没有传上数据库),按下报名按钮时虽然会弹出显示catch到的error的窗口,但是报名成功的界面仍然会弹出来,这会给用户造成错觉,以为自己报名成功了(最搞的是报名成功的信息是在错误产生的信息之后弹出的,这使得用户忽视错误的概率大大增加)。

这本来也不会造成大范围问题,毕竟正常报名根本不可能会出错并弹出错误信息。但是问题还是发生了,因为线下工作失误,造成用来展示报名网站并让学生报名的电脑经历了长时间的断网,所以很多报名信息丢失了。而用这台电脑报名的学生大部分都是没带手机的,使得找回他们的概率几乎为0。

解决方案:严格编写异常处理逻辑,出现异常立马中断,不可以再有后续的非异常处理的操作逻辑。

多废话一句,暑假看的CSAPP将CPU流水线异常处理也提到了这一点,没有学以致用着实让我羞愧难当。

2. 防抖没做

因为这次静态页面为了省事,直接通过国外cdn引入了supabase-js和tailwind,所以页面加载会有点慢。点击报名后,报名成功的窗口弹出的等待时间比正常要略长一点。这时报名按钮依旧处于可激活状态,所以不耐烦的用户就会多点几次,使得后台有了很多条一模一样的数据,增加了后期处理报表的难度。

解决方案:用户点击报名按钮后,修改其状态(比如禁用并显示“提交中”)。

3. 网站js引入

这次由于用ai写网站,它直接通过cdn引入js,我后期也懒得改。可是jsdelivr在国内就是会出现访问不上的情况,而我的报名是通过supabase-js向数据库写入数据,所以只要他的服务一挂,网站也得挂。最后是通过我自己在cloudflare上搭建的反代来保证访问jsdelivr成功。但由于发现问题较晚(毕竟jsdelivr挂的概率也很小),所以来不及上学校服务器改,只能启用备用服务(netlify)上的,用了我很丑很丑的一个域名。

解决方案:js和css要本地引入,cdn的方式只能用于开发环境测试!

结尾

第一周周末过的挺充实,周中更是忙且充实。希望以后越来越好,现下需要开始准学习更多开发相关的内容了,毕竟下学期要找实习去了。草草结束,有更多的以后补充。