提交 da7e0695 编辑于 作者: unknown's avatar unknown
浏览文件

update former solution

上级 b197a9df
.idea/
/cmake-build-debug
\ No newline at end of file
cmake_minimum_required(VERSION 3.20)
project(3_for_a_while)
set(CMAKE_CXX_STANDARD 14)
add_executable(josephus josephus.c)
add_executable(binary binary.c)
add_executable(series series.c)
add_executable(sudoku sudoku.c)
add_executable(insertion-sort insertion-sort.c)
add_executable(mines mines.c)
add_executable(palindrome palindrome.c)
# 3-for-a-while
> Last Update: 2021/11/2 0:10
## 写在前面
本仓库仅作为参考,请不要照抄代码,也请带着思考来阅读这些代码和readme。适合阅读本题解的人有:
1. 思考很长时间没有思路的同学
2. 解法始终无法拿到满分的同学
3. 想要优化自己代码的同学
## A Josephus谜题(josephus.c)
本题是十分著名的**约瑟夫问题**。软院的小朋友在大二的数据结构课上可能会再一次的遇到这个问题。该问题的思路是**模拟**:我们使用数组来存放这 $n$ 个人,并通过 $n - 1$ 次循环来剔除掉 $n - 1$ 个人。
### 思路
1. 初始化数组的索引`index = -1`,这里用 $-1$ 是因为第一个人我们是当做 $1$ 来数的,如果用 $index = 0$ 的话其实第一个人我们是当做 $0$ 来数的(why?
2. `index = (index + k) % size`,这里的`size`是数组的长度,可以知道每经过一次循环,数组长度都减一
3. 我们找到索引之后就将处于该索引的人删去,通过将数组后面的元素不断前移即可实现“删去”这一操作。
4. 删去之后我们需要将 `index--, size--`.
1. `index--`的用途与前面将`index = -1`的用途是一样的,请自己思考
2. `size--`是因为我们删去了一个人,需要将数组的范围缩小 $1$。
通过以上的**提醒**,可能你会稍微有点思路,但这其实不是最优解,或许你可以考虑一下降低**时间复杂度****空间复杂度**的解法是什么?
### 如何优化
> 提示一:这里的删除操作耗费的时间太久,必须要将该位置以后的数组全部遍历一遍
>
> 提示二:这是个数学问题,或许我们可以有公式推导出来?
## B 二进制转换(binary.c)
这题应该是这次作业中比较简单的一道题了。学过**计算系统基础**的大家都知道,计算机中的所有数据都是由二进制来表示的,整数也是如此,本题所做的就是将一串二进制数表示成整数。
本题思路也是**模拟**:对某个二进制数 $s_ns_{n-1}...s_1$,其十进制数表示为 $s_n \times 2^{n-1} + s_{n-1} \times x^{n-2} + \dots + s_1 \times 2^0$ ,因此我们只需要模拟这个过程即可。
### 注意!!!
本题的二进制数长度最多可能达到**30位**,即使我们使用了`long long`也会面临着溢出的风险,因此我们**应该用一个`char`数组来存储二进制数字**,避免溢出,与此同时,答案其实并不需要和我源代码写的一样用`long long`,因为`C``int`型是4个字节也就是32位,所以用`int`完全能够放得下30位的一个长度~
### 思路
1. 通过定义`product`表示 $2$ 的阶乘。
2. **从尾到头**遍历`char`数组,每次循环将`product *= 2`,这么做是为了省去每次都要计算 $2$ 的阶乘(实际上是多余的操作),节省了一重循环,优化了时间复杂度。
> **从尾到头**遍历数组是因为我们读取数字的时候,二进制数的高位实际上是在`char`数组的低位的,所以我们要从`char`数组的最高位开始遍历(实际上是二进制数的最低位
3. 如果`char`数组的第 $i$ 位是 $1$,那么答案就加上 `product`,否则就跳过。
4. 循环结束,输出答案
### 思考
细心的你可能会发现,本题其实用的是**无符号数**的二进制数表示,即我们转换二进制数实际上只能够得到正数。我们知道,整数在计算机中是以补码的方式存储的,而补码是有符号位的,补码的负数是通过对其相对应的整数“**取反加一**”得到的,这样就可以表示正负数啦~
> 思考一:如果要将二进制**补码**转换成十进制,该怎么实现呢?
>
> 思考二:十进制转成补码又该怎么实现呢?
## C 级数求和(series.c)
这道题也是这次作业中比较简单的一道题了,也是除了**二进制转换**之外目前通过人数最多的一道题(截止至2021/11/2 10:09),这道题我们也采用的是**模拟**的思想,即根据题意来模拟<span style = 'color: red'>**得出级数的过程**</span>。思路如下:
### 思路
1.**[二进制转换](#B 二进制转换(binary.c))**相同,我们也用了一个`product`来记录阶乘,当然这里要注意的是在`i == 0``i == 1`的情况下,**阶乘都是1**
2. 之后我们就通过**循环累加**,模拟求级数的过程就可以啦~
### 优化
**源代码中**有一句`double temp = pow(x, i)`,其实这一句并不是必要的,我们可以通过得出阶乘的方式来获得 $x$ 的 $n$ 次方,感兴趣的同学可以自己去尝试~
### 思考
我们知道,真正的级数其实是一直累加到 $\infin$ 的,在计算机内部实现的过程中,我们并不能够让其一直**加到无穷**(不然就是**死循环**啦!),那我们应该怎么做到将**无穷级数**真正计算到趋近于它真实的值呢?
> 方法一:将循环次数变的极大,如果一个级数是收敛的那么$\lim_{n \rightarrow \infin} \sum = 0$,此时我们增大循环次数对级数值的影响会逐渐变小,也就是我们已经十分接近级数的值了~
>
> 方法二:通过设立**阈值**,即设立最小误差,比如要与真正级数相差 $10^{-9}$ 才能够推出循环,这种方法需要我们已经知道了这个级数计算出来的真正值。
## D 数独(sudoku.c)
这道题可能是**稍微比较复杂**的一道题了,其复杂在于你明白该怎么去做,但是要实现出来需要经过一番的思考,相信大家的思路都和我一样:分别检查**行****列****3x3的九宫格**,如果都符合题意就输出`YES`,否则就输出`NO`。但是在具体的检查方面我们又该如何实现呢?我的思路如下:
### 思路
1. 创建了一个一维数组`check`用来检查每个数字出现的次数,其大小为9,每个位置代表每个数字出现的次数。
> 举例:如果数独中的某一行为`1 1 5 4 7 8 9 6 3`,则`check`数组中从0 - 8位置的元素分别是`2 0 1 1 1 1 1 1 1`,即代表1出现了2次,2出现了0次,其余元素出现了1次
2. 使用了一个函数`clearCheck`,用来清空`check`数组中记录的信息。
3. 用到了三个`int`型变量`isCorrectRow, isCorrectColumn, isCorrectBlock`,分别用来记录**行、列、块**是否正确,只有当三个都不为0时才会输出`YES`
> 这里其实是用`int`型变量来代替`bool`,`C`语言中并没有`bool`变量,编译器认为`0`是`false`,除了`0`之外的都是`true`,其他语言的`bool`变量具体实现也是如此,因此此处用`int`变量来起到一个记录的作用。
4. 检查行、列、块(具体该怎么做呢?请阅读源码
5. 如果有一个不符合的我们可以直接`break`,且不需要做后续的检查
6. 只有当三个都符合的时候输出`YES`
### 思考
本题因为数据量比较小且确定,所以我们如果使用了多重循环或许也没有关系,但是对于数据量较大的时候,我们有方法能够优化这种**遍历检查**的方式吗?
## E 插入排序(insertion-sort.c)
[插入排序](https://zh.wikipedia.org/wiki/%E6%8F%92%E5%85%A5%E6%8E%92%E5%BA%8F)是一种理解起来**最为简单**的排序,也是我们实际生活中最为常用的一种排序,即对每一个位置上的元素,我们都认为其**之前的元素是排序好的**
> 比如:现在有`5 4 9 7`四个元素,排序流程如下:
>
> 1. 遍历这四个元素
> 2. 遇到第一个数字`5`,我们认为其前面的数字已经排序好了,所以将`5`插到这堆已经排序好的数字中的正确位置,因为其前面为空,所以不用动
> 3. 遇到第二个数字`4`,因为其比`5`小,所以`swap(4, 5)`
> 4. 遇到第三个数字`9`,此时前两个数字是排序好的,我们要将`9`插入到前面这个已经排序好的序列中,因为其比`5`大,所以不用交换,保持原样即可
> 5. 遇到第四个数字`7`,同理,因为其比`9`小,所以与`9`交换,因为其比`5`大,所以不用交换
> 6. 最后得到结果`4 5 7 9`
### 思路
1. 通过**遍历数组**,将每个位置都插入其之前已经排序好的顺序之中
2. 如何插入?具体做法就是不断比较该元素与其之前一个位置的元素,如果小于,则交换,否则就原地不动就可以
> 代码中的实现是不断比较`insertValue`和其之前的元素,如果大于的话就将其往后移一个,一直重复直到当前元素不是大于`insertValue`的,然后将当前元素的**后一个**元素置成`insertValue`即可
3. 遍历后我们就可以得到答案啦~至于题目要的输出,可以让大家更加清楚的了解到插入排序的排序过程。大家也可以通过**断点 + 单步调试**来更加仔细观察每一步的过程。
### 思考
这里用了`insert`函数,为什么在里面更改数组中的值可以影响到**主函数**呢?(还不清楚的同学可以看看**前言**
## F 扫雷(mines.c)
这题是一道后面会很常见的**边界情况**的题目,题目中会有几个边界情况需要特殊考虑,以后无论是在实际工程或者在课程作业中,考虑边界情况都是一个十分重要的事情。可能有些同学想到的是分**角、边、内部**去分别讨论,我的解法用到了一个小技巧来**消除**这种边界情况:将地图外围围上一圈不是`o`,即多了一圈不是地雷的东西,让地图从 $n \times n$ 变成 $(n+2) \times (n+2)$,这样我们就不需要去特殊的考虑边界情况,也不用担心数组越界的问题,直接将所有点当成内部的点来算即可~
### 思路
1. 创建一个`char`数组,这里的大小是`102 x 102`,因为题目规定了最大的边长为100,我们需要在此基础上再加2。
2. 初始化数组,将所有元素都置成`o`
3. 读入数据
4. 遍历数组,对每一个不是地雷的位置,我们都**遍历其周围的八个地方**,并用一个变量`bombNum`来记录雷的数量
5. 根据第四步记录的雷的数量,将当前位置变成相应数字即可
6. 输出**答案矩阵**,注意不要把我们在外围包裹的那一圈`o`给输出了!!
### 注意
1. `scanf("%c", &ch)`会读入一切的字符,包括**换行符**,所以在读取完`n`之后,我们需要多一个`getchar()`来读取换行符。在读取每一行的数据之后,也要一个`getchar()`来读取每一行**行尾的换行符**
## G 回文字符串(palindrome.c)
**回文字符串**是一个十分重要的东西,其定义十分的简单。本题是让大家根据已有的位置来构造出一个回文字符串。我们可以利用<span style = 'color: red'>**双指针**</span>的想法,即指定`left,right`两个指针分别指向字符串的**头部和尾部**,因为回文字符串倒过来是不变的,即位置`i`和位置`n - 1 - i`的元素是一样的,因此我们就可以通过两个指针来遍历这个字符串从而更改所有`?`位置的元素~具体思路如下:
### 思路
1. 构建`char`数组(这里用了变长数组来节省内存,使用完请记得**释放内存**
2. 声明两个指针`int left = 0;``int right = n - 1;`,分别指向字符串的头部和尾部
3. 通过一个`while`循环来从两端同时遍历数组。
4. 如果有某个位置的元素是`?`,就取另一端的元素替换掉这个`?`即可
5. 两个指针同时向“前”走一格,即`left++; right--;`
### 注意
1. 这里用的输出是`printf("%s", str)`,具体用法大家可以**查阅文档**`%s`的用途是输出一个字符串,`str`在这里是一个指向字符串的**头指针**,会一直输出字符直到遇到`'\0'`才会结束。
## 结语
以上就是本次作业的所有题解啦~只是一个思路,仅供大家参考。如果大家觉得哪里有疑问的话,欢迎PR!!或者可以给我发邮件沟通。希望大家能够从我的题解中学到新的知识!
//
// Created by Zyi on 2021/11/1.
//
#include <stdio.h>
#include <malloc.h>
int main() {
int n;
// 变长数组
char* binaryNumber;
scanf("%d", &n);
binaryNumber = (char*) malloc(n * sizeof(char));
// %s是读入一个字符串,读入的数据会以'\0'结尾
scanf("%s", binaryNumber);
int ans = 0;
long long product = 1;
for (int i = n - 1; i >= 0; i--) {
if (binaryNumber[i] == '1') {
ans += product;
}
product *= 2;
}
printf("%d", ans);
free(binaryNumber);
return 0;
}
//
// Created by Zyi on 2021/11/1.
//
#include <stdio.h>
#include <malloc.h>
void insert(int* arr, int insertValue, int size);
int main()
{
int n;
scanf("%d", &n);
int* arr = (int*) malloc(n * sizeof(int));
for (int i = 0; i < n; i++) {
scanf("%d", &arr[i]);
}
// 打印初始状态
printf("%d\n", arr[0]);
// 插入排序
for (int i = 1 ; i < n; i++) {
int insertValue = arr[i];
// [0, i)是已经排序好的
insert(arr, insertValue, i);
}
free(arr);
return 0;
}
void insert(int* arr, int insertValue, int size) {
// 将insertValue插入到arr的[0, size - 1)位的对应位置
// 打印[0, size)位的数据
int i;
for (i = size - 1; i >= 0 && insertValue < arr[i]; i--) {
arr[i + 1] = arr[i];
}
arr[i + 1] = insertValue;
for (i = 0; i <= size; i++) {
printf("%d ", arr[i]);
}
printf("\n");
}
#include <stdio.h>
#include <malloc.h>
void delete(int* arr, int index, int size);
int main() {
int n;
int k;
// 变长数组
int* arr;
scanf("%d %d", &n, &k);
arr = (int*) malloc(n * sizeof(int));
// 初始化数组
for (int i = 0; i < n; i++) {
arr[i] = i + 1;
}
int index = -1;
int size = n;
// 循环 n - 1 次
for (int i = 0; i < n - 1; i++, index--, size--) {
index = (index + k) % size;
delete(arr, index, size);
}
printf("%d", arr[0]);
// 释放内存
free(arr);
return 0;
}
void delete(int* arr, int index, int size) {
// 将index位置移出且将后面的位置移到前面
for (int i = index; i < size - 1; i++) {
arr[i] = arr[i + 1];
}
}
//
// Created by Zyi on 2021/11/1.
//
#include <stdio.h>
int main()
{
int n;
scanf("%d", &n);
// 读入换行
getchar();
char matrix[102][102];
for (int i = 0; i <= n + 1; i++) {
for (int j = 0; j <= n + 1; j++) {
// 初始化一个 (n+2) x (n+2)的矩阵
matrix[i][j] = 'o';
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
scanf("%c", &matrix[i][j]);
}
// 获取换行符
getchar();
}
// 特殊处理,在matrix的外围再多围一圈o即可,即变成n+2 x n+2
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
if (matrix[i][j] == 'o') {
int bombNum = 0;
// 此时没有特殊情况,全部当成正常情况来处理
for (int k = i - 1; k <= i + 1; k++) {
for (int m = j - 1; m <= j +1; m++) {
if (matrix[k][m] == '*') {
bombNum++;
}
}
}
matrix[i][j] = (char)(bombNum + '0');
}
}
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= n; j++) {
printf("%c", matrix[i][j]);
}
printf("\n");
}
return 0;
}
//
// Created by Zyi on 2021/11/1.
//
#include <stdio.h>
#include <malloc.h>
int main()
{
int n;
scanf("%d", &n);
// 获取换行符
getchar();
char* str = (char*) malloc(n * sizeof(char));
scanf("%s", str);
int left = 0;
int right = n - 1;
while (left <= right) {
if (str[left] == '?') {
str[left] = str[right];
} else if (str[right] == '?') {
str[right] = str[left];
}
left++;
right--;
}
printf("%s", str);
// 释放内存
free(str);
return 0;
}
//
// Created by Zyi on 2021/11/1.
//
#include <stdio.h>
#include <math.h>
int main()
{
int n, x;
double ans = 0;
scanf("%d %d", &n, &x);
// 用一个数来记录阶乘,减小时间复杂度
double product = 1;
for (int i = 0; i <= n; i++) {
// 这句增加了不少多余的计算,我们该怎么优化?
double temp = pow(x, i);
if (i >= 1) {
// i = 0; i = 1时阶乘都是1
product *= i;
}
ans += temp / product;
}
printf("%.3lf", ans);
return 0;
}
//
// Created by Zyi on 2021/11/1.
//
#include <stdio.h>
const int ROW_NUMBER = 9;
const int COLUMN_NUMBER = 9;
void clearCheck(int* check);
int main()
{
int arr[ROW_NUMBER][COLUMN_NUMBER];
int check[ROW_NUMBER];
// initialize check array
for (int i = 0; i < ROW_NUMBER; i++) {
check[i] = 0;
}
for (int i = 0; i < ROW_NUMBER; i++) {
for (int j = 0; j < COLUMN_NUMBER; j++) {
scanf("%d", &arr[i][j]);
}
}
// 首先来判断行和列
// 首先是行
int isCorrectRow = 1;
for (int i = 0; i < ROW_NUMBER; i++) {
// 记录每一行之后都要清空该行的数据
clearCheck(check);
for (int j = 0; j < COLUMN_NUMBER; j++) {
// 判断数字是否在1 - 9之内
if (arr[i][j] > 9 || arr[i][j] < 1) {
isCorrectRow = 0;
break;
}
// 对应位置出现次数增加
check[arr[i][j] - 1]++;
if (check[arr[i][j] - 1] > 1) {
isCorrectRow = 0;
break;
}
}
if (!isCorrectRow) {
printf("NO");
break;
}
}
// 再来是列,如果行出错的话就不用处理列了
int isCorrectColumn = 1;
if (isCorrectRow) {
for (int j = 0; j < COLUMN_NUMBER; j++) {
clearCheck(check);
for (int i = 0; i < ROW_NUMBER; i++) {
if (arr[i][j] > 9 || arr[i][j] < 1) {
isCorrectColumn = 0;
break;
}
check[arr[i][j] - 1]++;
if (check[arr[i][j] - 1] > 1) {
isCorrectColumn = 0;
break;
}
}
if (!isCorrectColumn) {
printf("NO");
break;
}
}
}
// 再来是块
int isCorrectBlock = 1;
if (isCorrectRow && isCorrectColumn) {
int rowIndex = 0;
while (rowIndex < ROW_NUMBER) {
int columnIndex = 0;
while (columnIndex < COLUMN_NUMBER) {
clearCheck(check);
for (int i = rowIndex; i < rowIndex + 3; i++) {
for (int j = columnIndex; j < columnIndex + 3; j++) {
if (arr[i][j] > 9 || arr[i][j] < 1) {
isCorrectBlock = 0;
break;
}
check[arr[i][j] - 1]++;
if (check[arr[i][j] - 1] > 1) {
isCorrectBlock = 0;
break;
}
}
if (!isCorrectBlock) {
printf("NO");
break;
}
}
columnIndex += 3;
if (!isCorrectBlock) {
break;
}
}
rowIndex += 3;
if (!isCorrectBlock) {
break;
}
}
}
if (isCorrectRow && isCorrectColumn && isCorrectBlock) {
printf("YES");
}
return 0;
}
void clearCheck(int* check) {
for (int i = 0; i < ROW_NUMBER; i++) {
check[i] = 0;
}
}
.idea
./cmake-build-debug
cmake_minimum_required(VERSION 3.20)
project(5_function C)
set(CMAKE_C_STANDARD 99)