英雄哥《零基础算法》3/100之 “矩阵”

本学习笔记是根据《算法零基础100讲》进行的每讲总结,目标是采用刻意练习的方法对个人的学习和思考进行总结和归纳,方便复习和进一步深入理解和掌握内化。
每一讲的笔记内容大致分为三个部分:

  1. 设问,通过这一讲的学习应该理解什么内容?
  2. 题目,在学习完每讲内容后对题目先进行独立的思考和分析,记录下来;
  3. 题解报告,通过个人思考和学习其他优秀解题方案,记录个人认为较为优秀的解答并进行画图理解和内化吸收,并分析如何优化自己的思考;
  4. 知识点笔记,将本讲内容的知识点进行汇总和扩展,方便日后查阅复习;
  5. 解题模板,尽量将所学代码进行通用化处理。
    另外
    笔记中会尽量用图示的方式将所学内容进行可视化,方便记忆和内化,以及帮助读者进行理解。
    笔记至少会参考5篇以上的参考资料,并在文尾列出。

第三讲:矩阵

一、设问

  1. 在C语言中如何表示一个矩阵?
  2. 如何在C语言中将矩阵作为参数输入函数?
  3. 如何在C语言中将矩阵作为函数的输出?
  4. 矩阵在计算机中有哪些应用领域?
  5. 矩阵的基本运算有哪些?分别如何用C语言实现?

二、题目

最富有客户的资产总量

在这里插入图片描述

计算矩阵每行数值的和,再取最大值

二进制矩阵中的特殊位置

在这里插入图片描述

想办法找到值为1的[i][j],然后确定i行其他均为0,j列其他均为0;
想过用^和为1的方式,但是这会导致1的个数为奇数就会被判定正确
或者使用计算行和和列和的方式去减少判断次数,但是似乎不会有太大增益效果
最后还是只能一一遍历,只要实现该功能即可。

翻转图像

在这里插入图片描述

这道题需要开辟新的矩阵空间进行返回,因此操作空间较大,按照功能需求编写代码即可。

旋转图像

在这里插入图片描述
在这里插入图片描述

这道题卡了很久,因为不能开辟新空间而只能原理进行旋转,但旋转也不是两个数之间的相互交换,这一点让我有点难以转过弯来。

转置矩阵

在这里插入图片描述

本来以为可以直接使用上一题中的主轴翻转代码,但发现有坑。

将一维数组转变成二维数组

在这里插入图片描述

正常思路,主要考察对于一维数组和二维数组索引概念的理解。

判断矩阵经轮转后是否一致

在这里插入图片描述

只能想到模拟旋转并对比判断。

二维网格迁移

在这里插入图片描述

按照规律赋值即可,只需要知道如何将二维数组返回。

三、题解报告

最富有客户的资产总量

循环将每一行的数据都累加,与当前的max进行比对,更新最大的数据返回即可。
关于数组的题目每个参数的含义:

  1. 参数int **是输入的数组,名字叫什么随意
  2. 参数int xxSize是行数,名字一般就是数组名+Size
  3. 参数int *xxColSize是每行的列数,因为每行列数不一定相同,因此使用一个与行数大小相同的数组进行传入
  4. 如果需要输出的是一维数组或者二维数组,还会有int *returnSize和int **returnColSize,用于传出行数和列数
int maximumWealth(int** accounts, int accountsSize, int* accountsColSize){
    int max = 0;
    int customSize = accountsSize;
    int bankSize = *accountsColSize;
    int sum = 0;

    for (int custom = 0; custom < customSize; ++custom) {
        sum = 0;
        for (int bank = 0; bank < bankSize; ++bank) {
            sum += accounts[custom][bank];
        }
        max = max > sum? max : sum;
    }

    return max;
}

二进制矩阵中的特殊位置

找到值为1的位置,然后判断其所在行列其他值是否都为0
由于输入参数为m行n列方阵,因此只需要获取xxColSize[0]即可知道列数。

bool check(int **mat, int m, int n, int x, int y){
        for(int i = 0; i < m; i ++){
            if(i != x && mat[i][y] == 1) return false;
        }
        for(int i = 0; i < n; i ++){
            if(i != y && mat[x][i] == 1) return false;
        }
        return true;
    }

int numSpecial(int** mat, int matSize, int* matColSize){
    int res = 0;
    int m = matSize, n = matColSize[0];
    
    for(int i = 0; i < m; i ++){
        for(int j = 0; j < n; j ++){
            if(mat[i][j] == 1 && check(mat, m, n, i, j)){
                res++;   
            }
        }
    }

    return res;
}

在这里插入图片描述

翻转图像

找到相应的关系进行赋值即可。

int** flipAndInvertImage(int** image, int imageSize, int* imageColSize, int* returnSize, int** returnColumnSizes){
    int **newImage = (int **)malloc(sizeof(int*) * imageSize);
    *returnColumnSizes = (int *)malloc(sizeof(int) * imageSize);
    *returnSize = imageSize;
    int col;

    for (int i = 0; i < imageSize; ++i) {
        col = imageColSize[i];
        newImage[i] = (int*)malloc(sizeof(int) * col);
        (*returnColumnSizes)[i] = col;
        for(int j = 0; j < col; ++j) {
            newImage[i][j] = 1 - image[i][col - 1 - j];
        }
    }

    return newImage;
}

在这里插入图片描述

旋转矩阵

由于题目需要在原地进行旋转,因此需要分析矩阵旋转的特征
在这里插入图片描述
画图分析得出如上结论,其中的row和col为了方便分析,指的是行列索引的最大值,即行数和列数分别 - 1之后的值。
分析利用此结论进行旋转换位置的可行性:
我们大致可以看出这个旋转不仅仅是两个数之间的事情,需要继续分析
在这里插入图片描述
按照得出的结论进行转换,可以得知四个数可形成旋转的闭环,且当i,j正好为中间的数时,如:mat[1][1] = mat[1][2 - 1], 也符合情况,但实际上它并不需要参与旋转,因此可以写代码,利用一个临时变量,将这四个数进行旋转。
最后需要注意的是,由于旋转是每4个数一组,因此需要考虑如何使每个数都只旋转一次,最简单的方式是进行行列值/2,如图所示
在这里插入图片描述
只考虑 (row+1)/2 和 col/2,或者 row/2 和(col + 1)/2(此处的row和col是行和列数)即可。
另外还有一种解题方案是两次翻转,每次更换两个数之间的值
在这里插入图片描述

void rotate(int** matrix, int matrixSize, int* matrixColSize){
    int n = matrixColSize[0] - 1;

    for (int i = 0; i < matrixSize / 2; ++i) {
        for (int j = 0; j < (n + 2) / 2; ++j) {
            int tmp = matrix[i][j];
            matrix[i][j] = matrix[n - j][i];
            matrix[n - j][i] = matrix[n - i][n - j];
            matrix[n - i][n - j]= matrix[j][n - i];
            matrix[j][n - i] = tmp;
        }
    }
}

void rotate(int** matrix, int matrixSize, int* matrixColSize){
    int m = matrixColSize[0] - 1;
    int n = matrixSize - 1;

	///< 水平翻转
    for (int i = 0; i < (n + 1) / 2; ++i) {
        for (int j = 0; j <= m; ++j) {
            matrix[i][j] ^= matrix[n - i][j];
            matrix[n - i][j] ^= matrix[i][j];
            matrix[i][j] ^= matrix[n - i][j];
        }
    }

	///< 主轴翻转
    for (int i = 0; i <= n; ++i) {
        for (int j = 0; j < i; ++j) {
            matrix[i][j] ^= matrix[j][i];
            matrix[j][i] ^= matrix[i][j];
            matrix[i][j] ^= matrix[j][i];
        }
    }
}

在这里插入图片描述
在这里插入图片描述

转置矩阵

这里要注意一个坑,就是转置不完全等同于主轴翻转,行列转置还会导致行列发生变化,因此不能像上一题一样处理,必须开辟新的数组空间。

int** transpose(int** matrix, int matrixSize, int* matrixColSize, int* returnSize, int** returnColumnSizes){
    int n = matrixColSize[0];
    int m = matrixSize;
    int **res = (int **)malloc(sizeof(int *) * n);
    *returnColumnSizes = (int *)malloc(sizeof(int) * n);

    *returnSize = matrixColSize[0];
    for (int i = 0; i < n; ++i) {
        (*returnColumnSizes)[i] = m;
        res[i] = (int *)malloc(sizeof(int) * m);
        for (int j = 0; j < m; ++j) {
            //printf("%d %d\n", n, m);
            res[i][j] = matrix[j][i];
        }
    }

    return res;
}

在这里插入图片描述

将一维矩阵转变为二维矩阵

先判断是否可以构建二维数组,然后根据索引关系进行赋值。

int** construct2DArray(int* original, int originalSize, int m, int n, int* returnSize, int** returnColumnSizes){
    int **res = (int **)malloc(sizeof(int *) * m);
    *returnColumnSizes = (int *)malloc(sizeof(int) * m);
    *returnSize = 0;
    *returnColumnSizes[0] = 0;
    if (originalSize != m * n) {
        return res;
    }
    *returnSize = m;
    for (int i = 0; i < m; ++i) {
        res[i] = (int *)malloc(sizeof(int) * n);
        (*returnColumnSizes)[i] = n; 
        for (int j = 0; j < n; ++j) {
            res[i][j] = original[i * n + j];
        }
    }

    return res;
}

判断矩阵经轮转后是否一致

常规思路,如果行列数不一致一定错误;
尝试最多四次旋转,如果和target完全一致则返回true,否则返回false。

void rotate(int** matrix, int matrixSize, int* matrixColSize){
    int n = matrixColSize[0] - 1;

    for (int i = 0; i < matrixSize / 2; ++i) {
        for (int j = 0; j < (n + 2) / 2; ++j) {
            int tmp = matrix[i][j];
            matrix[i][j] = matrix[n - j][i];
            matrix[n - j][i] = matrix[n - i][n - j];
            matrix[n - i][n - j]= matrix[j][n - i];
            matrix[j][n - i] = tmp;
        }
    }
}

bool matrixEq(int **mat, int **target, int n, int m) {
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            if (mat[i][j] != target[i][j]) {
                return false;
            }
        }
    }

    return true;
}
bool findRotation(int** mat, int matSize, int* matColSize, int** target, int targetSize, int* targetColSize){
    int n = matSize;
    int m = matColSize[0];

    if (n != targetSize || m != targetColSize[0]) {
        return false;
    }
    for (int i = 0; i < 4; ++i) {
        rotate(mat, matSize, matColSize);
        if (matrixEq(mat, target, n, m)) {
            return true;
        }
    }

    return false;
}

在这里插入图片描述

二维网格迁移

按规律赋值。
需要特别注意输入矩阵为1x1时的情况,另外需要仔细考虑好赋值的规律要如何用代码进行表示。
由于输入的二维数组往往是malloc出来的,无法像局部变量一样直接用地址+偏移量(i * n + m)获得第[i][j]的数据,因此规律描述比想象中麻烦。

int** shiftGrid(int** grid, int gridSize, int* gridColSize, int k, int* returnSize, int** returnColumnSizes){
    int **res = (int *)malloc(sizeof(int *) * gridSize);
    int m = gridSize;
    int n = gridColSize[0];
    int size = n * m - 1;
    *returnSize = m;
    *returnColumnSizes = (int *)malloc(sizeof(int) * m);
    int row = (size - k)/m;
    int column = (size - k)%m;
    int idx = 0;
    if (size == 0) {
        k = 0;
    }
    k = k % (size + 1);

    for (int i = 0; i < m; ++i) {
        res[i] = (int *)malloc(sizeof(int) * n);
        (*returnColumnSizes)[i] = n;
        for (int j = 0; j < n; ++j) {
            idx = i * n + j;
            if (idx < k) {
                row = (size - k + idx + 1)/n;
                column = (size - k + idx + 1)%n;;
            } else {
                row = (idx - k) / n;
                column = (idx - k) % n;
            }
            printf("%d %d\n", row, idx);
            res[i][j] = grid[row][column];
        }
    }

    return res;
}

在这里插入图片描述

四、知识点笔记

  1. 矩阵相当于C语言中的二维数组, A n × m A_{n\times m} An×m 表示n行m列矩阵,在C语言中可表示为matrix[n][m],其中matrix[i][j]表示第i行j列的元素;

  2. “如果不熟悉线性代数的概念,要去学习自然科学,现在看来就和文盲差不多。”
    “按照现行的国际标准,线性代数是通过公理化来表述的,它是第二代数学模型”

  3. C中的二维数组如果是malloc的话,代表着是一维数组名的一维数组,需要malloc了一维数组指针之后,再循环对一维数组指针进行malloc。
    在这里插入图片描述

    int **createMatrix(int n, int m, int* returnSize, int **returnColumnSizes)
    {
    	int **matrix = (int **)malloc(sizeof(int *) * n);
        *returnColumnSizes = (int *)malloc(sizeof(int) * n);
        *returnSize = m;
    	
    	for (int i = 0; i < n; ++i) {
            matrix[i] = (int *)malloc(sizeof(int) * m);
            (*returnColumnSizes)[i] = m; 
            for (int j = 0; j < m; ++j) {
                matrix[i][j] = 0;
            }
        }
        return matrix;
    }
    
  4. 矩阵是数的排列,其运算包括标量运算和点积运算:标量运算是与常量的运算(与矩阵中每个值进行+ - * /);点积运算是指矩阵乘法,较为复杂。可以查看矩阵乘法器,矩阵乘法必须第一个矩阵的列数与第二个矩阵的行数相等,并不可互换。
    在这里插入图片描述

  5. 矩阵中没有除法的概念,只有乘上第二个矩阵的逆矩阵,工程师用类似的计算(当然是用大得多的矩阵)来设计楼宇。类似的计算也应用在很多其他的领域,例如在电玩和电脑动画制作里用来显示三维物体。同时也是解线性方程组的方法。

  6. 线性代数是向量计算的基础,很多重要的数学模型都要用到向量计算。矩阵的本质就是线性方程式,两者是一一对应关系。矩阵的最初目的,只是为线性方程组提供一个简写形式。
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  7. 在线性空间中,当你选定一组基之后,不仅可以用一个向量来描述空间中的任何一个对象,而且可以用矩阵来描述该空间中的任何一个运动(变换)。而使某个对象发生对应运动的方法,就是用代表那个运动的矩阵,乘以代表那个对象的向量。
    简而言之,在线性空间中选定基之后,向量刻画对象,矩阵刻画对象的运动,用矩阵与向量的乘法施加运动。
    矩阵的本质是运动的描述。

《算法零基础100讲》介绍

该栏目是CSDN@英雄哪里出来大佬的万人千题计划第一阶段,其目标是让想要学会算法的人抱团成长,能通过这个项目稳步成长,扎扎实实掌握算法,最终用算法改变世界!

img

大佬将算法学习的路线总结如下,并分别根据五步路线总结写出了相应的教学内容,关于目前的第一阶段算法入门的详细的介绍可以看博文《万人千题:第一阶段,算法零基础抱团打卡》学习路线指引

img

100讲的学习大纲如下:

img

参考资料

此处列出除文中链接之外的参考资料

  1. 矩阵旋转-LeetCode官方题解
  2. 矩阵 - 数学乐
  3. 矩阵的运算及其运算规则
  4. 如何直观理解矩阵和线性代数?-知乎
  5. 矩阵基础知识
  6. 理解矩阵(一)
  7. 理解矩阵乘法
  8. 【官方双语/合集】线性代数的本质 - 系列合集
  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值