力扣每日一题37:解数独

news2025/1/11 18:33:57

目录

题目

大致思路

方法一:回溯剪枝(正常人能想出来的)

方法二:位运算优化剪枝

需要使用的位运算技巧

代码

位运算怎么就优化了呢?

方法三:枚举优化。

官解代码

方法四:舞蹈链算法


题目

困难

编写一个程序,通过填充空格来解决数独问题。

数独的解法需 遵循如下规则

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。(请参考示例图)

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 1:

输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]]
输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]]
解释:输入的数独如上图所示,唯一有效的解决方案如下所示:


提示:

  • board.length == 9
  • board[i].length == 9
  • board[i][j] 是一位数字或者 '.'
  • 题目数据 保证 输入数独仅有一个解

面试中遇到过这道题?

1/5

通过次数

248.8K

提交次数

367.3K

通过率

67.7%

大致思路

使用一个数据记录每个每一行,每一列,每一个3*3宫格中数组1-9出现的情况。

对所有空格(也就是需要填数的地方)进行遍历填数,判断这个位置能填1-9中的哪个数字,如果数字num在空格所在的行、列、3*3宫格都没出现过,那就可以把num放进去,然后填写下一个空格。当填写完最后一个空格的时候,就代表解完了。

方法一:回溯剪枝(正常人能想出来的)

对于每一行、每一列、每个3*3宫格中1-9出现的情况,我们都用一个哈希表表示。

这样在对某个空格进行填数num时,我们只需要判断这个空格所在行、所在列、所在3*3宫格有没有出现过num即可。

class Solution {
public:
    //判断九行、九列、九个格中数字1-9有无出现的哈希表
    int rows[9][9];
    int columns[9][9];
    int boxes[3][3][9];

    vector<pair<int,int>> blanks;//需要填写的空格

    bool flag;//用来剪枝
    void solveSudoku(vector<vector<char>>& board) {
        //初始化哈希表
        memset(rows,0,sizeof(rows));
        memset(columns,0,sizeof(columns));
        memset(boxes,0,sizeof(boxes));
        flag=false;
        
        //遍历一次9*9宫,寻找空格并更新哈希表
        int i,j;
        for(i=0;i<9;i++)
        {
            for(j=0;j<9;j++)
            {
                char c=board[i][j];
                if(c=='.'){
                    blanks.emplace_back(i,j);//blanks.emplace_back({i,j});会有语法错误
                }else{
                    int digit=c-'0';
                    rows[i][digit-1]=columns[j][digit-1]=boxes[i/3][j/3][digit-1]=1;
                }
            }
        }
        backtrack(board,0);
    }
    void backtrack(vector<vector<char>>& board,int index){
        //填完了最后一个空格,
        if(index==blanks.size()){
            flag=true;
            return;
        }
        //1-9遍历
        int x=blanks[index].first;
        int y=blanks[index].second;
        for(int num=1;num<=9&&flag==false;num++){
            if(rows[x][num-1]==0&&columns[y][num-1]==0&&boxes[x/3][y/3][num-1]==0){
                rows[x][num-1]=columns[y][num-1]=boxes[x/3][y/3][num-1]=1;
                board[x][y]=num+'0';
                backtrack(board,index+1);
                rows[x][num-1]=columns[y][num-1]=boxes[x/3][y/3][num-1]=0;
            }
        }
    }
};

方法二:位运算优化剪枝

看了题解我才知道这也能优化…………

在方法一中,我们使用了长度为 9 的数组表示每个数字是否出现过(1为出现过,0为没有出现)。我们同样也可以借助位运算,仅使用一个整数表示每个数字是否出现过。

具体的做法是:一个二进制数的第i位数(最低为是第0位)是1,就表示 i+1 已经出现过。例如:

二进制数 (011010100) 代表数字 3、5、7、8已经出现过。

也就是说我们用一个二进制数来表示一个哈希表。

在方法一中,每在一个空格出现一个数字num,或者是填写一个数字num,都要将这个空格对于行、列、3*3宫格的哈希表的num值由0变成1,也就是 简单的[num-1]=1,填写完num发现数字不对后,又要将哈希表num的值由1变成0。也就是说,在用数组当作哈希表时,哈希表的更新就是将数组对应的值简单的0,1互换。

在此方法中,哈希表的更新就变成了二进制数对应某一位上的0,1互换。要使用一些位运算的技巧,

需要使用的位运算技巧

(1)、将二进制数的第i位由i变成0,或由0变成1.

1<<i 的第i+1位二进制数是1,其余的二进制数是0。x和1<<i按位异或后,x=x^(1<<i)后,x的第i+1位就实现了0,1的互换。

(2)、遍历一个二进制数上的所有0。

方法一中,空格所在的行、列、3*3宫格的哈希表[num-1]==0就代表这个空格可以填数字num,而在二进制的哈希表中,我们要找二进制数对应的0。二进制数的0不太好取,我们就把0,1互换,互换后的1就是先前的0,而二进制的1是好取的(随后会讲)。

一个二进制数按位取反得到的1就是先前的0,但是我们只需要前9位的1,所以取反后的数还要和 (111111111)进行按位与操作,这样就能只留下前九位数。

(3)、取得二进制数最低位的1(包括后面的0)

x=~x&(111111111) 后,x中第i(从最低为第0位开始)位的1就代表数字 i+1可以填在空格里。

low_one=x&(-x)。因为-x在计算器用的是补码,所以-x和x的二进制数的最低为1和右边的0都相等,其余为都不想等,按位与刚好可以留下这一部分。留下这一部分后,low_one的位数就是可以填入空格的数字。

(4)、将二进制数的最低为1变成0。

在某个空格blanks中填入某个数后不一定对,这时候要填下一个数。我们只需要将最低位的1变成0,然后再取最低位的1,这样就取到了下一个数。

x&=(x-1);

代码

class Solution {
private:
    int rows[9];
    int cols[9];
    int boxes[3][3];
    vector<pair<int, int>> blanks;
    bool valid;
public:
    //将指定数位1变0,0变1
    void func(int i, int j, int digit) {
        rows[i] ^= (1 << digit);
        cols[j] ^= (1 << digit);
        boxes[i / 3][j / 3] ^= (1 << digit);
    }
    void backtrack(vector<vector<char>>& board, int index) {
        if (index == blanks.size()) {
            valid = true;
            return;
        }
        int x = blanks[index].first;
        int y = blanks[index].second;
        //创建掩码来遍历
        int mask = ~(rows[x] | cols[y] | boxes[x / 3][y / 3]);
        mask = mask & 0x1ff;            //和(111111111)按位取和,去除第九位之前的1
        while (mask && !valid) {
            //取最低位的1包括后面的0,然后转换成对应的数字
            int lowdigit = mask & (-mask);
            int digit = 0;
            while (lowdigit) {
                lowdigit >>= 1;
                digit++;
            }
            func(x, y, digit - 1);
            board[x][y] = '0' + digit;
            backtrack(board, index + 1);
            func(x, y, digit - 1);

            //去除掉最低位的1
            mask &= (mask - 1);
        }
    }
    void solveSudoku(vector<vector<char>>& board) {
        memset(rows, 0, sizeof(rows));
        memset(cols, 0, sizeof(cols));
        memset(boxes, 0, sizeof(boxes));
        valid = false;
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                if (board[i][j] == '.') {
                    blanks.emplace_back(i, j);
                }
                else {
                    int digit = board[i][j] - '0' - 1;
                    func(i, j, digit);
                }
            }
        }

        backtrack(board, 0);
    }
};

位运算怎么就优化了呢?

对比一下方法一和方法二的代码。区别就是哈希表的更新和在遍历空格时,对于填充数字的遍历。


先说哈希表的更新,方法一的代码是

rows[i][digit-1]=columns[j][digit-1]=boxes[i/3][j/3][digit-1]=1;
或
rows[i][digit-1]=columns[j][digit-1]=boxes[i/3][j/3][digit-1]=0;

而方法二是对下面函数的一次调用。

void func(int i, int j, int digit) {
        rows[i] ^= (1 << digit);
        cols[j] ^= (1 << digit);
        boxes[i / 3][j / 3] ^= (1 << digit);
    }

光看这一部分来说,方法二反而要更复杂。

但是考虑到对空格填充数字的遍历时,那就不一样了。

方法一是对数字1-9都有一次判断,一共有九次。

for(int num=1;num<=9&&flag==false;num++){
    if(rows[x][num-1]==0&&columns[y][num-1]==0&&boxes[x/3][y/3][num-1]==0){
        //填充数字
    }
}

而方法二是在创建掩码后,每次只取对应二进制数位上有1的数。

//创建掩码来遍历
        int mask = ~(rows[x] | cols[y] | boxes[x / 3][y / 3]);
        mask = mask & 0x1ff;            //和(111111111)按位取和,去除第九位之前的1
        while (mask && !valid) {
            //填充数字
            mask &= (mask - 1);
        }

假设某个空格所在行、列、3*3宫格都只有一个数9可以填的时候,方法一需要循环9次,而方法二在创建掩码后,只需要循环1次。也就是说,方法二自动过滤掉了空格所在行、列、3*3宫格中已经出现的数。这大大缩短了代码运行时间。

方法三:枚举优化。

看了题解还知道,位运算的方法还能优化………………

想一下,我们在手作数独游戏的时候,都是先将能确定数字的位置先填上。

就比如在刚开始给出的矩阵中,有一个空格所在行出现过1、2、3,所在列出现过4、5、6,所在九宫格出现过7,8。那么毫无疑问,这个位置就是填9,不用等其他位置填完也能确认。

这样一来,我们可以不断地对整个数独进行遍历,将可以唯一确定的空白格全部填入对应的数。随后我们再使用与方法二相同的方法对剩余无法唯一确定的空白格进行递归 + 回溯。

官解代码

class Solution {
private:
    int line[9];
    int column[9];
    int block[3][3];
    bool valid;
    vector<pair<int, int>> spaces;

public:
    void flip(int i, int j, int digit) {
        line[i] ^= (1 << digit);
        column[j] ^= (1 << digit);
        block[i / 3][j / 3] ^= (1 << digit);
    }

    void dfs(vector<vector<char>>& board, int pos) {
        if (pos == spaces.size()) {
            valid = true;
            return;
        }

        auto [i, j] = spaces[pos];
        int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
        for (; mask && !valid; mask &= (mask - 1)) {
            int digitMask = mask & (-mask);
            int digit = __builtin_ctz(digitMask);
            flip(i, j, digit);
            board[i][j] = digit + '0' + 1;
            dfs(board, pos + 1);
            flip(i, j, digit);
        }
    }

    void solveSudoku(vector<vector<char>>& board) {
        memset(line, 0, sizeof(line));
        memset(column, 0, sizeof(column));
        memset(block, 0, sizeof(block));
        valid = false;

        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] != '.') {
                    int digit = board[i][j] - '0' - 1;
                    flip(i, j, digit);
                }
            }
        }

        while (true) {
            int modified = false;
            for (int i = 0; i < 9; ++i) {
                for (int j = 0; j < 9; ++j) {
                    if (board[i][j] == '.') {
                        int mask = ~(line[i] | column[j] | block[i / 3][j / 3]) & 0x1ff;
                        if (!(mask & (mask - 1))) {
                            int digit = __builtin_ctz(mask);
                            flip(i, j, digit);
                            board[i][j] = digit + '0' + 1;
                            modified = true;
                        }
                    }
                }
            }
            if (!modified) {
                break;
            }
        }

        for (int i = 0; i < 9; ++i) {
            for (int j = 0; j < 9; ++j) {
                if (board[i][j] == '.') {
                    spaces.emplace_back(i, j);
                }
            }
        }

        dfs(board, 0);
    }
};

方法四:舞蹈链算法

我以为吕布已经天下无敌了,没想到有人比他还要勇猛…………

我翻看评论的时候,看到有个大佬发的一种算法,叫做舞蹈链算法,自称是解数独最快的算法,没有之一。这算法我也不会,直接把代码贴上来吧。

/*
最快的是舞蹈链算法
然后是贪心加回溯算法
最慢的是正常的回溯算法
*/
#include <bits/stdc++.h>

using namespace std;

const int N = 9 * 9 * 9 * 4;

class node {
public:
    int u{}, d{}, l{}, r{}; //上下左右
    int row{}, col{};   //具体坐标
    int s{};          //col节点数量
    int h{};          //row头节点
    int ans{};        //选了那些row

    node() = default;
};

class DLX {
public:
    vector<vector<int>> a;
    const int n = 9 * 9 * 9, m = 9 * 9 * 4;
    int num{};
    vector<node> nodes;

    explicit DLX(vector<vector<int>> &B) {
        a = B;
        dance();
        B = a;
    }

    void dance() {
        init();
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                for (int k = 1; k <= 9; k++) {
                    if (a[i][j] != k && a[i][j] != 0) {
                        continue;
                    }
                    int id = i * 9 * 9 + j * 9 + k;
                    Link(id, i * 9 + j + 1);
                    Link(id, i * 9 + k + 81);
                    Link(id, j * 9 + k + 162);
                    Link(id, (i / 3 * 3 + j / 3) * 9 + k + 243);
                }
            }
        }
        Dance(0);
    }

    void init() {
        num = m + 1;
        nodes.assign(N, node());
        for (int i = 0; i <= m; i++) {
            node &it = nodes[i];
            it.l = i - 1;
            it.r = i + 1;
            it.u = it.d = i;
        }
        nodes[0].l = m;
        nodes[m].r = 0;
    }

    void Link(int r, int c) {
        num++;
        node &row = nodes[r];
        node &col = nodes[c];
        node &nums = nodes[num];

        nums.row = r;
        nums.col = c;
        col.s++;

        // node[col.u] <-> nums <-> col
        nums.u = col.u;
        nodes[col.u].d = num;
        col.u = num;
        nums.d = c;

        if (row.h == 0) {
            row.h = nums.l = nums.r = num;
        } else {
            node &head = nodes[row.h];
            //node[head.l] <-> nums <-> head
            nums.l = head.l;
            nodes[head.l].r = num;
            head.l = num;
            nums.r = row.h;
        }
    }

    void Remove(int c) {
        node &col = nodes[c];
        // node[col.l] <-> node[col.r]
        nodes[col.l].r = col.r;
        nodes[col.r].l = col.l;

        //下右
        for (int i = col.d; i != c; i = nodes[i].d) {
            for (int j = nodes[i].r; j != i; j = nodes[j].r) {
                node &it = nodes[j];
                //node[it.u] <-> node[it.d]
                nodes[it.d].u = it.u;
                nodes[it.u].d = it.d;
                nodes[it.col].s--;
            }
        }
    }

    void Resume(int c) {
        // node[col.l] -> node[c] <- node[col.r]
        node &col = nodes[c];
        nodes[col.r].l = c;
        nodes[col.l].r = c;
        //上左
        for (int i = nodes[c].u; i != c; i = nodes[i].u) {
            for (int j = nodes[i].l; j != i; j = nodes[j].l) {
                node &it = nodes[j];
                //node[it.u] -> node[j] <- node[it.d]
                nodes[it.d].u = j;
                nodes[it.u].d = j;
                nodes[it.col].s++;
            }
        }
    }

    int Dance(int dep) { // NOLINT(*-no-recursion)
        if (nodes[0].r == 0) {
            for (int i = 0; i < dep; i++) {
                node &it = nodes[i];
                int x = (it.ans - 1) / 9 / 9;
                int y = (it.ans - 1) / 9 % 9;
                int val = (it.ans - 1) % 9 + 1;
                a[x][y] = val;
            }
            return 1;
        }
        int c = nodes[0].r;
        for (int i = nodes[0].r; i != 0; i = nodes[i].r) {
            if (nodes[i].s == 0 || nodes[c].s == 0) {
                return 0;
            }
            if (nodes[i].s < nodes[c].s) {
                c = i;
            }
        }
        Remove(c);
        for (int i = nodes[c].d; i != c; i = nodes[i].d) {
            node &it = nodes[i];
            nodes[dep].ans = it.row;
            for (int j = nodes[i].r; j != i; j = nodes[j].r) {
                Remove(nodes[j].col);
            }
            if (Dance(dep + 1) == 1) {
                return 1;
            }
            for (int j = nodes[i].l; j != i; j = nodes[j].l) {
                Resume(nodes[j].col);
            }
        }
        Resume(c);
        return 0;
    }
};

class Solution {
public:
    static void solveSudoku(vector<vector<char>> &b) {
        vector<vector<int>> a(9, vector<int>(9));
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                int val = b[i][j] == '.' ? 0 : int(b[i][j] - '0');
                a[i][j] = val;
            }
        }
        DLX o(a);
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                b[i][j] = char(a[i][j] + '0');
            }
        }
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1661384.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

《ESP8266通信指南》15-MQTT连接、订阅MQTT主题并打印消息(基于Lua|适合新手|非常简单)

往期 《ESP8266通信指南》14-连接WIFI&#xff08;基于Lua&#xff09;-CSDN博客 《ESP8266通信指南》13-Lua 简单入门&#xff08;打印数据&#xff09;-CSDN博客 《ESP8266通信指南》12-Lua 固件烧录-CSDN博客 《ESP8266通信指南》11-Lua开发环境配置-CSDN博客 《ESP826…

XTuner笔记

为什么要微调&#xff1a; 1. 模型不具备一些私人定制的知识 2。模型回答问题的套路你不满意。 对应衍生出来两种概念 增量预训练微调&#xff1a; 使用场景&#xff1a;让基座模型学习到一些新知识&#xff0c;如某个垂类领域的常识训练数据&#xff1a;文章、书籍、代码等…

关于JAVA-JSP电子政务网实现参考论文(论文 + 源码)

【免费】关于JAVA-JSP电子政务网.zip资源-CSDN文库https://download.csdn.net/download/JW_559/89292355关于JAVA-JSP电子政务网 摘 要 当前阶段&#xff0c;伴随着社会信息技术的快速发展&#xff0c;使得电子政务能够成为我国政府职能部门进行办公管理的一个重要内容&#x…

AnaTraf局域网流量分析:告别网络故障困扰

网络故障是困扰网络管理员的一大难题&#xff0c;它会导致网络性能下降、服务中断&#xff0c;甚至造成数据泄露等严重后果。传统的网络故障排查方法往往效率低下&#xff0c;耗时费力&#xff0c;难以快速定位问题根源。 AnaTraf网络流量分析仪的出现&#xff0c;为网络管理员…

Java入门基础学习笔记13——数据类型

数据类型的分类&#xff1a; 基本数据类型 引用数据类型 基本数据类型&#xff1a;4大类8种类型&#xff1a; 定义整形用int&#xff0c;再大的数用long。 package cn.ensource.variable;public class VariableDemo2 {public static void main(String[] args) {//目标&#x…

[NISACTF 2022]popchains

第一步&#xff1a;看到 include($value); 作为链尾&#xff0c;则要触发 append($value) -->>__invoke()&#xff0c;看到$function()。 __invoke()&#xff1a;对象以函数形式被调用时触发 第二步&#xff1a;$function() &#xff0c;则要触发 __get($key)&#xff0…

LeetCode算法题:8.字符串转换整数 (atoi)

请你来实现一个 myAtoi(string s) 函数&#xff0c;使其能将字符串转换成一个 32 位有符号整数&#xff08;类似 C/C 中的 atoi 函数&#xff09;。 函数 myAtoi(string s) 的算法如下&#xff1a; 读入字符串并丢弃无用的前导空格检查下一个字符&#xff08;假设还未到字符末…

pydev debugger: process **** is connecting

目录 解决方案一解决方案二 1、调试时出现pydev debugger: process **** is connecting 解决方案一 File->settings->build,execution,deployment->python debugger 下面的attach to subprocess automatically while debugging取消前面的勾选&#xff08;默认状态为勾…

走进C++:C到C++的过渡

目录 什么是C呢&#xff1f; C的发展史 多了一些吃前来很香的“语法糖”。 语法糖一&#xff1a;命名空间 命名空间有个强大的功能 如何使用 语法糖二&#xff1a;缺省参数 语法糖三&#xff1a;函数重载 语法糖四&#xff1a;引用 引用传参 引用返回 引用和…

记一次DNS故障导致用户无法充值的问题(下)

上一篇说到DNS故障导致无法充值&#xff0c;后来我们通过拨测发现业务域名的解析目标地址被解析到了【127.0.0.1】IP。 1、联系阿里云厂商&#xff0c;通过沟通&#xff0c;阿里云反馈我们的域名被XX省通管单位封禁了&#xff0c;导致解析到了不正确的地址。 2、为了解决用户问…

文章分享:《肿瘤DNA甲基化标志物检测及临床应用专家共识(2024版)》

本文摘自于《肿瘤DNA甲基化标志物检测及临床应用专家共识&#xff08;2024版&#xff09;》 目录 1. DNA甲基化标志物概述 2 DNA甲基化标志物的临床检测 2.1 临床样本前处理注意事项 2.2 DNA甲基化标志物检测技术方法 2.2.1 DNA提取与纯化 2.2.2 DNA转化 2.2.3 DNA 甲基…

MDK/keil高阶使用手册

1.开启watch和内存窗口的值实时更新 2.调试模式可以查看局部变量的值 只不过要让任意被观察变量退出<cannot evaluate>状态,都需要先在该变量被赋值的地方先打个断点,此后该变量的值就能实时更新了。 3.可以在watch窗口直接输入你要查看的变量的名称 4.可以在watch窗…

解决mybatis的配置文件没代码提示的问题

1.将org.apache.ibatis.builder.xml包里的两个dtd文件复制出来&#xff0c;jar包里复制 2.复制dtd的url地址&#xff1a; http://mybatis.org/dtd/mybatis-3-mapper.dtd 一样的做法&#xff01; 3.关闭两个配置文件&#xff0c;重新打开&#xff0c;就可以有代码提示了&…

子查询之二(不相关子查询与相关子查询)

1. 相关子查询 如果子查询的执行依赖于外部查询&#xff0c;通常情况下都是因为子查询中的表用到了外部的表&#xff0c;并进行的条件关联&#xff0c;因此每一次执行一次外部查询&#xff0c;子查询都会重新计算一次&#xff0c;这样的子查询称为关联子查询. 相关子查询按照…

工作中遇见的问题总结

1. 当我执行下面代码的时候&#xff0c;下边的的代码还是会执行 if(name"aaa"){console.log("111");}

合并两个有序链表(C语言)———链表经典算法题

题目描述​​​​​​21. 合并两个有序链表 - 力扣&#xff08;LeetCode&#xff09;&#xff1a; 答案展示: 迭代&#xff1a; /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ struct ListNode* mergeTwoLis…

Go PDF文件操作

目录 介绍 安装 gofpdf API 代码示例 结果展示 介绍 gofpdf 是一个在 Go 语言中用于生成 PDF 文档的库。 安装 gofpdf 首先&#xff0c;你需要安装 gofpdf 库。你可以使用 go get 命令来安装它&#xff1a; go get github.com/jung-kurt/gofpdf API 功能 函数名参数解释示…

高校教务选课管理系统开发方案

一、项目背景与目标 &#xff08;一&#xff09;项目背景 随着高校教育规模的扩大&#xff0c;教务管理变得越来越复杂&#xff0c;传统的手工管理方式已经无法满足现代高校的需求。因此&#xff0c;开发一套高效、便捷的高校教务选课管理系统显得尤为重要。该系统将涵盖学生…

C++对象的赋值

同类的对象之间可以互相赋值&#xff0c;即一个对象的值可以赋值给另一个对象。对象之间的赋值通过“”进行。默认就是把一个对象所有非static数据成员的值依次赋值给另一个对象。 对象赋值的一般形式为&#xff1a; 对象名1 对象名2; 注意:对象名1和对象名2必须是属于同一个…

苹果电脑内存清理神器CleanMyMac2024中文版下载

很多朋友在使用mac电脑一段时间后&#xff0c;可能都会发现&#xff0c;自己的电脑不知道什么原因开始有些卡顿。其实这主要因为内存被占用过多&#xff0c;及时地清理不必要的内存占用&#xff0c;可以为mac减负&#xff0c;也能让电脑的运行变得更加流畅。那么问题来了&#…