蓝桥杯《算法很美》第四章 多维数组和矩阵

【1】顺时针打印二维数组

#include <iostream>
#include <iomanip>
#define MAX 10
/**
    回型打印N行M列矩阵
    通过不断定位“口”字的左上角和右下角
    先打印一圈,再找到重新定位左上角和右下角的规律
*/
void Print_arr(int A[MAX][MAX], int N, int M){
    int leftUpRow = 0;//左上角的行
    int leftUpCol = 0;//左上角的列
    int rightDownRow = N-1;//右上角的行
    int rightDownCol = M-1;//右上角的列
    int r, c;//控制正在打印的行列值
    while (leftUpRow <= rightDownRow && leftUpCol <= rightDownCol){
        //初始化,起始位置是左上角的行列值
        r = leftUpRow;
        c = leftUpCol;
        //上面一条边
        while(c <= rightDownCol){//行不变,列在变,当行到达右下角的行边界时停止
            cout << A[r][c++] << " ";
        }
        //定位新起点,行+1,列为右下角的列值
        r ++;
        c=rightDownCol;
        //右边一条边
        while(r <= rightDownRow){//列不变,行在变,当列到达右下角的行边界时停止
            cout << A[r++][c] << " ";
        }
        //定位新起点,列-1,行为右下角的行值
        c --;
        r = rightDownRow;
        //下面一条边
        while(c >= leftUpCol){//行不变,列在变,当到达右下角的列边界时停止
            cout << A[r][c--] << " ";
        }
        //定位新起点,行-1,列为左上角的列值
        r --;
        c = leftUpCol;
        //左边一条边
        while(r > leftUpRow){
            cout << A[r--][c] << " ";
        }
        //一个“口”字打印完毕
        leftUpRow ++;
        leftUpCol ++;
        rightDownRow --;
        rightDownCol --;
    }
}
int main() {
    int A[MAX][MAX];
    int r,c;
    cin >> r >> c;
    int i,j;
    for(i=0; i<r; i++){
        for(j=0; j<c; j++){
            cin >> A[i][j];
        }
    }
    Print_arr(A,r,c);
}

【2】将0所在的行列清零

#include <iostream>
#include <iomanip>
using namespace std;
#define MAX 10

void Print2Darr(int A[MAX][MAX], int M, int N){
    int i,j;
    for(i=0; i<M; i++){
        for(j=0; j<N; j++){
            cout << A[i][j] << " ";
        }
        cout << endl;
    }
}
/**
    将0所在的那一行和列置为全0
*/
void ClearZero(int A[MAX][MAX], int M, int N){
    //记录哪些行出现了0
    int rowRecord[MAX];
    //记录哪些列出现了0
    int colRecord[MAX];
    memset(rowRecord,0,sizeof(rowRecord));
    memset(colRecord,0,sizeof(colRecord));
    for(int i=0; i<M; i++){
        for(int j=0; j<N; j++){
            if(A[i][j] == 0){
                rowRecord[i] = 1;
                colRecord[j] = 1;
            }
        }
    }
    for(int row=0; row<M; row++){
        for(int col=0; col<N; col++){
            //当前的行或者列被标记了,这个元素就应该变为0
            if(rowRecord[row] == 1 || colRecord[col] == 1){
                A[row][col] = 0;
            }
        }
    }
}
int main() {
    int A[MAX][MAX];
    memset(A,0,sizeof(A));
    int r,c;
    cin >> r >> c;
    int i,j;
    for(i=0; i<r; i++){
        for(j=0; j<c; j++){
            cin >> A[i][j];
        }
    }
    ClearZero(A,r,c);
    Print2Darr(A,r,c);
}

【3】Z形打印二维数组

#include <iostream>
#include <iomanip>
using namespace std;
#define MAX 10
/**
    Z型打印二维数组
*/
void PrintZarr(int A[MAX][MAX], int m, int n)
{
    int r = 0, c = 0;
    bool l2r = true;//从左到右
    while(r < m && c < n)
    {
        //从左下到右上的斜线
        if(l2r)
        {
            cout << A[r][c] << " ";
            //现在在第一行,列未到边界,这是只能向右走
            if(r == 0 && c < n - 1)
            {
                l2r = !l2r;//方向切换
                c++;
                continue;
            }
            else if(r > 0 && c == n - 1)     //现在在最后一列,只能向下走
            {
                l2r = !l2r;
                r++;
                continue;
            }
            else    //继续走上坡
            {
                r--;
                c++;
            }
        }
        else    //反,走下坡
        {
            cout << A[r][c] << " ";
            if(c == 0 && r < m - 1)   //走到第一列,只能往下走
            {
                l2r = !l2r;
                r++;
                continue;
            }
            else if(r == m - 1)     //到最后一行,只能往右走
            {
                l2r = !l2r;
                c++;
                continue;
            }
            else
            {
                r++;
                c--;
            }
        }
    }
}
int main()
{
    int A[MAX][MAX];
    memset(A, 0, sizeof(A));
    int r, c;
    cin >> r >> c;
    int i, j;
    for(i = 0; i < r; i++)
    {
        for(j = 0; j < c; j++)
        {
            cin >> A[i][j];
        }
    }
    PrintZarr(A, r, c);
}

【4】找出边界为1的最大子方阵

#include <iostream>
using namespace std;
#define MAX 10
/**
 给定一个N×N的矩阵matrix,在这个矩阵中,只有0和1两种值,返回边框全是1的最大正方形的边长长度。 
   例如: 
 {0, 1, 1, 1, 1},
 {0, 1, 0, 0, 1},
 {0, 1, 0, 0, 1},
 {0, 1, 1, 1, 1},
 {0, 1, 0, 1, 1} 
   其中,边框全是1的最大正方形的大小是4*4,返回4
*/
int rec[MAX][MAX][MAX];
void print3Darr(int N)
{
    for(int i = 0; i < N; i++)
    {
        for(int j = 0; j < N; j++)
        {
            cout << rec[i][j][0] << "," << rec[i][j][1] << "\t";
        }
        cout << endl;
    }
}

/**
 记录每个元素往右和往下有多少个连续的1
 生成辅助数组,三维,保存右边和下面1的个数(算自己)
 */
void generateHelpRec(int A[MAX][MAX], int N)
{
    memset(rec, 0, sizeof(rec));
    int row = N - 1;//行
    //初始化最后一行,类似于动态规划
    for(int j = N - 1; j >= 0; j--)
    {
        //从最后一行最后一列的元素开始
        int value = A[row][j];
        if(value == 1)
        {
            if(j == N - 1)
            {
                rec[row][j][0] = 1;//右侧连续1的个数为1
            }
            else
            {
                //A的元素值为1,rec在这个位置的连续1的个数=右边位置的连续1的个数+1
                rec[row][j][0] = rec[row][j + 1][0] + 1;
            }
            //最后一行的下方的1的连续数==1(它自己)
            rec[row][j][1] = 1;
        }
    }
    //初始化其他行
    row--;
    for(int i = row; i >= 0; i--)
    {
        for(int j = N - 1; j >= 0; j--)
        {
            int value = A[i][j];
            // 利用右边和下边已经生产的数据来推出现在这个位置上右侧和下方有多少个1
            if(value == 1)
            {
                if(j == N - 1)
                    rec[i][j][0] = 1;//右侧连续1的个数为1(它自己)
                else
                    rec[i][j][0] = rec[i][j + 1][0] + 1;
                rec[i][j][1] = rec[i + 1][j][1] + 1;//向下连续1的个数
            }
        }
    }
}
//O(1)
bool check(int i, int j, int n)
{
    //左上角那个点往右数的1的数目要≥n
    //右上角那个点往下数的1的数目要≥n
    //左下角那个点往右数的1的数目要≥n
    if(rec[i][j][0] >= n && rec[i][j][1] >= n && rec[i][j + n - 1][1] >= n && rec[i + n - 1][j][0] >= n)
        return true;
    return false;
}
//O(n³)
int solve(int A[MAX][MAX], int N)
{
    int n = N;//阶数
    while(n > 0)
    {
        for(int i = 0; i < N; i++)
        {
            if(i + n > N) break;//检查点的行值
            for(int j = 0; j < N; j++)
            {
                if(j + n > N) break;//检查点的列值
                //笨方法,O(n^4)
                // //检查四个边
                // int r = i, c = j;
                // while (c < j + n) {
                //   if (A[r][c++] == 0) continue l3;
                // }
                // c--;
                // while (r < i + n) {
                //   if (A[r++][c] == 0)
                //     continue l3;
                // }
                // r--;
                // while (c >= j) {
                //   if (A[r][c--] == 0)
                //     continue l3;
                // }
                // c++;
                // while (r >= i) {
                //   if (A[r--][c] == 0)
                //     continue l3;
                // }
                if(check(i, j, n))
                    return n;
            }
        }
        n--;
    }
    return n;
}

int main()
{
    int A[MAX][MAX];
    memset(A, 0, sizeof(A));
    int r, c;
    cin >> r >> c;
    int i, j;
    for(i = 0; i < r; i++)
    {
        for(j = 0; j < c; j++)
        {
            cin >> A[i][j];
        }
    }
    generateHelpRec(A, r);
    print3Darr(r);
    cout << endl << solve(A, r) ;
}

【5】返回子数组最大累加和

#include <iostream>
using namespace std;
#define MAX 10
/**
 * 求和最大的连续子数组,有可能不唯一,返回一个即可
 */
// 暴力法破解 Θ(n²)
void findByForce(int arr[MAX], int N)
{
    int maxSum = arr[0];
    for(int j = 0; j < N; j++)
    {
        int sum = arr[j];// 某个元素为子数组的第一个元素
        int maxOfJ = sum;
        for(int i = j + 1; i < N; i++)
        {
            sum += arr[i];// 累加后续元素
            if(sum > maxOfJ)
            {
                maxOfJ = sum;
            }
        }
        if(maxOfJ > maxSum)
        {
            maxSum = maxOfJ;
        }
    }
    cout << maxSum << endl;
}

// 递推法 Θ(n)
int findByDp(int arr[MAX], int N)
{
    // System.out.println("======="+N);
    if(N == 0) return 0;
    int sumJ = arr[0];  // 前J个元素的最大贡献
    int max = sumJ;
    int left = 0, right = 0;
    for(int j = 1; j < N; j++)
    {
        if(sumJ >= 0)     // 左子表的最大和为正,继续向后累加
        {
            sumJ += arr[j];
        }
        else
        {
            sumJ = arr[j];
            left = j;//丢弃前部分和的同时,更新left
        }
        if(sumJ > max)
        {
            max = sumJ;
            right = j;//更新max的同时更新right
        }
    }
    cout << max <<",left=" << left << ",right:" << right << endl;
    return max;
}

int main()
{
    int A[MAX];
    memset(A, 0, sizeof(A));
    int r;
    cin >> r ;
    int i, j;
    for(i = 0; i < r; i++)
    {
        cin >> A[i];
    }
    cout << findByDp(A,r);
}

【6】求子矩阵最大累加和

#include <iostream>
using namespace std;
#define MAX 10
/**
 * 假定只有一行,那就和求最大和子数组一样
 * 如果限定两行,可以把两行按列求和,同上
 * ……
 * 所以我们以第一行当做起点,依次累加后面的每一行后,都求一个最大子数组和
 * 以第二行作为起点,依次累加后面的每一行后,都求一个最大子数组和
 * ……
 * 每次求出来的和与历史最大值比较,如果更大,则更新
 */
//N^3时间复杂度
int sums[MAX];//按列求和
int maxSum(int matrix[MAX][MAX], int M, int N)
{
    int beginRow = 0;//以它为起始行
    int max = 0;//历史最大的子矩阵和
    while(beginRow < M)   //起始行
    {
        for(int i = beginRow; i < M; i++)   //从起始行到第i行
        {
            //按列累加
            for(int j = 0; j < N; j++)
            {
                sums[j] += matrix[i][j];
            }
            //  累加完成
            //  求出sums的最大和子数组O(n)
            int t = findByDp(sums, N);
            if(t > max)
                max = t;
        }
        //另起一行作为起始行.把sums清零
        memset(sums, 0, sizeof(sums));//快速地将sums的每个元素都设定为0
        beginRow++;
    }
    return max;
}

int main()
{
    int A[MAX][MAX];
    //memset(A, 0, sizeof(A));
    int r,c;
    cin >> r >> c;
    int i, j;
    for(i = 0; i < r; i++)
    {
        for(j=0; j <c; j++)
            cin >> A[i][j];
    }
    cout << maxSum(A, r, c);
}

 

点赞