思考算法题 之126 357 489

[    

    [1,     2,     6]    ,

    [3,     5,     7]    ,

    [4,     8,     9]    ,

]

一个二维数组, N行, M列, 按照如上规则排序.

希望将该数组输出, 其结果为 [1, 2, 3, 4, 5, 6, 7, 8, 9]


遇到如上情况时, 有些人可能会一头雾水. 算法哎, 不会不熟哎. 要不要去刷刷算法题? 

在我看来, 需要这种情况, 第一步是先摸清规则. 尝试把复杂的问题, 拆分, 拆分, 一个大问题, 拆成十个中问题, 一个中问题, 拆分成十个小问题, 一个小问题, 再拆分成十个点. 这一千个点, 每个都没有那么难了.

上述题目, 观察看来, 他的数值是斜着 变化的, 即 [向右上]增加, 和[左下走] 增加. 二维数组, 是M和N来定位数值. 

[        M0    M1    M2

N0:    [1,      2,      6]    ,

N1:   [3,      5,      7]    ,

N2:   [4,      8,      9]    ,

]

第一步分析

分析一下斜着走, 是如何做到的,  2 的坐标系[N, M] 换成数值就是 [0, 1] . 当2 -> 3变动时, 向下走一行, N + 1. 同时向左走一列, M - 1 ; [0, 1] -> [1, 0]

当4 -> 5变动时, 向上走一行, N - 1. 同时向右走一列, M + 1 ; [2, 1] -> [1, 1]

当N 加 1 时, M 就减 1. 当 N 减 1 时, M 就加 1. 这个是斜着走的规则. 按照如上规则, 一直斜着走, 就能一直找到坐标.

第二步分析

斜着走, 总有遇到 数组 边界 的时候. 就像按照斜着走的规则, 当3 向4的坐标走的时候.



数字4 应该是继续沿用原本规则, 向左下方前进. 但是这样的话, 就会导致数组 索引越界 3 [1, 0] -> 4 [2, -1]. -1明显不在 数组的索引 0 .. 2 之间, 这时候, 就需要强制让数字落在 正常 的范围内. 那么[2, -1] -> [2, 0].

当m或者n的值, 小于0时, 让其变为0. 防止出界. 可以处理之前遇到的问题, 但是还有新的问题, 当m或者n大于2的时候, 让他们等于2, 是不是正确的方法呢. 同时大于, 小边界时呢.

这时发现, 数字斜着走, 是会遇到 边界 的那么, 第二步, 就是处理所有的边界问题, 

1. M < 0, N正常

2. M > 2, N正常

3. M < 0, N < 0

4. M < 0, N > 2

5. N < 0, M正常

6. N > 2, M正常

7. N < 0, M > 2

8. N > 2, M > 2

有以上8种, 会导致, M和N越界的情况. 通过if else处理当各种情况异常时, 如何处理.

第三步分析

分析如何循环, 程序将二维数据按规则排序. 如何自动化的进行.

这时, 首先想到的, 可能是 递归, 自己调用自己, 逐步完成. 

通过方法的参数拿到一个坐标数据, 计算他的下一个位置, 打印出来. 递归 将当前坐标当做参数传入方法

再次计算下一个坐标, 打印. 递归 将当前坐标传入

通过方法的参数拿到坐标数据, 打印出来. 计算下一个坐标, 通过递归 将新坐标传入方法

再次打印坐标数据, 计算下一个坐标. 如此反复.


递归可能是比较容易想到的一个解决方案, 它提供了简单的解决思路. 但是递归是有缺点的. 当M和N足够大时, 斜着的一行就会无限长, 递归的次数就会增加, 递归 会就变成 func -> func -> func ->func ->func ->func -> ..... 当递归层级达到一定程度, 就会报错. 所以, 此时 还需要另一种解决思路, 用循环代替递归. 这里暂且不谈该方式

第四步分析

如何结束, 无论采用递归/循环 等方式, 如何达成条件, 结束 递归/循环.

最先想到的, 可能是, 当M和N同时大于2的时候, 结束. 但实际上, 是不会的, 因为按照上述流程, 先打印坐标数据, 坐标值, 一定在范围内. 第二步, 计算下一个坐标的位置. 如果越界了, 会自动处理, 让M和N回到合理范围内.

所以M和N, 永远不会越界. 

我采用的方式是, 用一个新的一维数组, 每次 打印 数据时, 不是打印, 而是将数据 add到数据中. 当结束后, 得到的数组, 就是最终顺序的数据集合.

结束方式也是, 将数据添加到数据中, 当数组的长度, 等于 M x N时, 即所有数据已经处理完全. 退出 循环/ 递归 即可.

第五步分析

如何开始, 这个在第三步的时候, 我写了一段 划线.

这个是我一开始思维上遇到的错误, 喜欢先去计算. 拿到数据时, 先计算, 再输出结果. 但是问题来了, (0, 0) 的坐标数据, 在什么时候输出呢? 如果方法调用前, 先输出 (0, 0), 那么没有任何问题, 但是这样似乎不太符合方法的完整性. 这个方法不能完整的处理这个算法.

所以改成了, 先打印, 再计算. 如果打印时, 已经满足结束条件, 那么也不用再进行计算了

第六步分析

数据问题, 上述题目描述的比较简单, 直观的看起来, 处理看到的问题.

但是其中还隐藏着一些没能看到的问题.  第二步边界处理时, 有8种情况, 但是 题目中, 不会遇到全部8中情况,

[1,      2,      6,      7]

[3,     5,      8,      13]

[4,     9,      12,     14]

[10,   11,     15,     16]

如果 M和N是个 4x4的数组呢? 5x5呢?  3x5呢? 9x4呢? 就会遇到全部的边界问题了.



后记: 递归变为 循环.

上述情况 每次处理 m, n

Ary result;

func (int m, int n){

    if( result.count >= (m * n) ){

        return;

    }

    ....

    ....

    m = ...;

    n =  ...;

    func (m, n);

}


while(result.count < (m * n) ){

    ....

    ....

    m = ...;

    n =  ...;

}

你可能感兴趣的:(思考算法题 之126 357 489)