一. 数组_特定顺序遍历二维数组_498. 对角线遍历

题目描述
给你一个大小为 m x n 的矩阵 mat ,请以对角线遍历的顺序,用一个数组返回这个矩阵中的所有元素。

示例 1:
在这里插入图片描述
输入:mat = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,4,7,5,3,6,8,9]
示例 2:

输入:mat = [[1,2],[3,4]]
输出:[1,2,3,4]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/diagonal-traverse

首先通过观看可以看出来打印对角线其实只要找到一半的元素即可,另一半是对称的
若是使用打印上三角的方式的话,是不行的,因为上三角遍历的方式和当前方式不同
这个程序就是打印很多条对次对角线,次对角线i+j=阶数-1,这里可以看做是1阶,2阶,3阶,2阶,1阶对角线的打印
且对角线的起点正好在第0行和第0列
下面的解法是逐条打印对角线,这里关键的问题是找到对应的对角线,我用的办法是遍历所有的元素找到一条对角线,那么找到几条对角线就要遍历几次矩阵
这里根据观察可以看出来,i+j=0,i+j=1,i+j=2,i+j=3,i+j=4,nn的矩阵有m+n-1条对角线,所以根据这个规律只要一条一条的遍历对角线就能得到答案
这里因为方向问题,所以有的对象先需要正向遍历,有的对角线需要反向遍历
这里提速的关键是从一个矩阵中,不需要遍历所有元素就可以找到对应的对角线,那么就可以在O(m
n)的时间复杂度内完成

vector<int> findDiagonalOrder(vector<vector<int>>& mat) {
    int m = mat.size();
    int n = mat[0].size();
    vector<int> ret(m * n);
    int ind = 0;

    //这里标记一共有几条对角线
    for(int i = 0; i < m + n - 1; i++) {
        //取余等于0的话,那么反向遍历
        if(i % 2 == 0) {
            for(int j = m - 1; j >= 0; j--) {
                for(int k = n - 1; k >= 0; k--) {
                    if(j + k == i) {
                        ret[ind++] = mat[j][k];
                    }
                }
            }
        } else { //否则正向遍历
            for(int j = 0; j < m; j++) {
                for(int k = 0; k < n; k++) {
                    if(j + k == i) {
                        ret[ind++] = mat[j][k];
                    }
                }
            }
        }
    }

    return ret;
}

上面的有方法可以提速
想到的一个方法是遍历所有的元素,判断i+j等于多少,那么存储到对应的temp矩阵中,正向遍历一次,反向遍历一次,但是这样想一下时间
复杂度是上面的那个是相同的,没有减少多少,都是O(mnq),这里q是有多少个对角线.

这里想到了对角线的知识可以复习一下
打印主对角线,for i遍历0~n-1行,这里打印a[i][i]
打印次对角线,for i遍历0~n-1行,这里打印a[i][n-1-i]
这里的想法是找到所有的对角线,直接打印出来,这里找到对角线确定该对角线有多少元素,从第几行开始
正序和反序的话,可以看到是从低位行遍历还是从高位行遍历
下面的程序可以解决某些矩阵的对角线输出,但是不能通过[[1][2]],现在所有的示例都可以通过了。

vector<int> findDiagonalOrder(vector<vector<int>>& mat) {
    int m = mat.size();
    int n = mat[0].size();
    vector<int> ret(m * n);
    int base_i = 0, len = 0;   //基础下标为第0行,初始长度为0
    int ind = 0;

    for(int i = 0; i < m + n - 1; i++) {  //这里确定的是i+j的和为多少
        //这里确定基地址和长度
        //前几个基地址的位置都是0
        //但是这里m不一定等于n,所以,我们保持最大的长度,可能会有的有浪费时间
        //这里取最大值,也就我们把它当做一个m和n中最大值维度的方阵来看待,那么它遍历的元素自然可以包含原来的元素
        //基地址和个数也是按照方阵来计算的
        if(i >= max(m, n)) {  //到达一定的位置后,那么长度会减少,并且基地址一个一个的往后加,长度也是
            base_i++;
            len--;
        } else {
            len++;
        }

        //这里确定从前向后遍历还是从后向前遍历
        //因为,这里毕竟是扩充了的矩阵,所以有些元素可能原来的矩阵中不存在,那么这样的话,我们就需要判断是否要遍历当前元素
        if(i % 2 == 0) {
            for(int j = base_i + len - 1; j >= base_i; j--) {
                if(j >= 0 && j < m && i - j >= 0 && i - j < n) {
                    ret[ind++] = mat[j][i - j];
                }
            }
        } else {
            for(int j = base_i; j < base_i + len; j++) { //遍历所有可能的元素
                if(j >= 0 && j < m && i - j >= 0 && i - j < n) { //首先判断是否在范围内,若在范围内,则遍历,否则不遍历
                    ret[ind++] = mat[j][i - j];
                }
            }
        }
    }

    return ret;
}

考虑一个复杂的问题之前,先考虑他的简单情况,这个问题,可以看做是打印矩阵的对角线,不考虑翻转的情况
那么第一行和第一列所有的元素都是对角线的起点。
打印矩阵的对角线,可以将原来的列表存储下来,在进行对换即可。

vector<int> findDiagonalOrder(vector<vector<int>>& mat) {
    int m = mat.size();
    int n = mat[0].size();
    vector<int> ret(m * n);
    vector<int> temp(max(m, n));  //这里存储的是当前对角线,如果是要逆序输出的话,需要利用当前数组保存
    int ind = 0;
    int i = 0, j = -1;   //i,j指向第一行和第一列的所有元素
    bool is_reverse = true;

    while(ind < m * n) {   //当遍历完成所有的元素之后,那么停止
        if(j < n - 1) {   //将第一行遍历完成之后,便继续遍历第一列
            j++;
        } else {
            i++;
        }

        int i1 = i;
        int j1 = j;
        int k1 = 0;

        //将当前对角线保存到对应的数组中k1为长度
        //这里进行的是行加和列减,所以行有可能超过m,列有可能小于0,所以只需要检查i<m,j>=0就可以了
        while(i1 >= 0 && i1 < m && j1 >= 0 && j1 < n) {
            temp[k1++] = mat[i1++][j1--];
        }

        //这里,如果是需要逆置那么从后向前复制,不需要逆置,那么从前向后复制
        if(is_reverse) {
            for(int l = k1 - 1; l >= 0; l--) {
                ret[ind++] = temp[l];
            }
        } else {
            for(int l = 0; l < k1; l++) {
                ret[ind++] = temp[l];
            }
        }

        //这里的话,隔一次,进行一次逆置
        is_reverse = !is_reverse;
    }

    return ret;
}

这里介绍一种新的做法,不需要额外的空间复杂度
首先,我们可以知道,一共有m+n-1条对角线,这些对角线有方向和有对应的起点,我们只要知道了这两点之后,就能遍历完成对角线
起点和对角线的方向,对角线的方向每次都是和上一次相反的,所以这里有一个bool变量即可。
对角线的起点比较难理解,可以分为分为两种情况,这里当向下遍历的时候,如果不是最后一行(i+1,j)或最后一行(i, j+1)
向上遍历的时候,第一行(i, j+1)或不是第一行(i-1, j)
注:这里虽然答案是对的,但是感觉判断逻辑那块写的特别啰嗦,感觉官方题解写的特别好。
官方的逻辑是i_new和j_new是否在范围内,若在范围内,则直接赋值
若不在范围内,则用i和j、flag,计算下一个点的坐标。并且取反

vector<int> findDiagonalOrder(vector<vector<int>>& mat) {
    int m = mat.size();
    int n = mat[0].size();
    vector<int> ret(m * n);
    int i = 0, j = 0;
    int ind = 0;
    bool flag = 1;  //1是向上,0是向下

    while(ind < m * n) {
        //如果当前i,j都是在范围内的话
        if(i >= 0 && i < m && j >= 0 && j < n) {
            ret[ind++] = mat[i][j];

            //向上或者向下的方式不同
            if(flag) {
                i--;
                j++;
            } else {
                i++;
                j--;
            }
        } else {
            //如果不在范围内的话
            if(flag) {
                i++; //首先让i,j到上一步的位置
                j--;

                if(j == n - 1) {   //判断下一个节点的位置
                    i++;
                } else {
                    j++;
                }
            } else {
                i--;
                j++;

                if(i == m - 1) {
                    j ++;
                } else {
                    i++;
                }
            }

            flag = !flag;  //下次遍历的时候就需要反向了。
        }
    }

    return ret;
}
int main() {
    vector<vector<int>>  matrix(3, vector<int>(3));
    matrix[0][0] = 1;
    matrix[0][1] = 2;
    matrix[0][2] = 3;
    //matrix[0][3] = 4;
    matrix[1][0] = 4;
    matrix[1][1] = 5;
    matrix[1][2] = 6;
    //matrix[1][3] = 8;
    matrix[2][0] = 9;
    matrix[2][1] = 10;
    matrix[2][2] = 11;
    //matrix[2][3] = 12;
    /*
    matrix[3][0] = 13;
    matrix[3][1] = 14;
    matrix[3][2] = 15;
    matrix[3][3] = 16;
    */
    findDiagonalOrder(matrix);
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值