题目描述:
给你一个 m
行 n
列的二维网格 grid
和一个整数 k
。你需要将 grid
迁移 k
次。每次「迁移」操作将会引发下述活动:
- 位于
grid[i][j]
的元素将会移动到grid[i][j + 1]
。 - 位于
grid[i][n - 1]
的元素将会移动到grid[i + 1][0]
。 - 位于
grid[m - 1][n - 1]
的元素将会移动到grid[0][0]
。
请你返回 k
次迁移操作后最终得到的 二维网格。示例:
输入:grid
= [[1,2,3],[4,5,6],[7,8,9]], k = 1
输出:[[9,1,2],[3,4,5],[6,7,8]]
输入:grid
= [[3,8,1,9],[19,7,2,5],[4,6,11,10],[12,0,21,13]], k = 4
输出:[[12,0,21,13],[3,8,1,9],[19,7,2,5],[4,6,11,10]]
约束条件:
1 <= grid.length <= 50
1 <= grid[i].length <= 50
-1000 <= grid[i][j] <= 1000
0 <= k <= 100
解题思路:
我的思路是将二维数组抽象成一个一维的数字序列,那么根据题目的转移要求可以知道k次迁移其实就是将这个一维的数字序列的后k个数字转移到数字序列的前方部分,整个过程可以想象成一个环形运动。所以我借助两个一维数组分别存储前n-k个元素和后k个元素,然后对这两个数组进行串行遍历,将元素存储到集合当中去。时间复杂度为O(m*n+(m+n))。代码如下所示:
List<List<Integer>> res_list; public List<List<Integer>> shiftGrid(int[][] grid, int k) { if (k % (grid.length * grid[0].length) == 0){ res_list = new LinkedList<>(); for (int[] g : grid){
//Integer[] gg = IntStream.of(g).boxed().collect(Collectors.toList()).toArray(new Integer[0]); List<Integer> ls = IntStream.of(g).boxed().collect(Collectors.toList()); res_list.add(ls); } return res_list; } int temp_len = grid.length * grid[0].length; k = k % (temp_len); int[] temp = new int[temp_len-k]; int[] temp2 = new int[k]; int index = 0; for (int[] ints : grid) { for (int anInt : ints) { if (index < (temp_len-k)) temp[index] = anInt; else{ temp2[index-(temp_len-k)] = anInt; } index++; } } LinkedList<Integer> temp_list = new LinkedList<>(); res_list = new LinkedList<>(); for (int i =0; i <= temp_len; i++) { if (i % grid[0].length == 0 &&(i >= grid[0].length)){ res_list.add(temp_list); temp_list = new LinkedList<>(); } if (i < k) temp_list.add(temp2[i]); else if (i < temp_len) temp_list.add(temp[i-k]); } return res_list; }
注意:
在写程序时犯了一些知识点的错误,就是不能往集合中存储基本数据类型,只能存储对象的引用。每个集合元素都是一个引用变量,实际内容都存放在堆内或方法区里面,但是基本数据类型是在栈内存上分配空间的,栈上的数据随时会被收回。所以可以通过包装类,把基本数据类型转化为对象类型,存放引用。更方便的,由于有了自动拆箱和装箱功能,基本数据类型和其对应对象之间的转换变得很方便,把基本数据类型存入集合中可以自动存,系统会自动将其装箱成封装类,然后将其加入到集合当中。
但是,在本程序中的ls集合计算时,我通过增强for循环依次获取二维数组的每一行作为一维数组,当我将这个一维数组直接通过Arrays.asList转换成集合后再将其存入结果集合会发生编译错误,提示no instance(s) of type variable(s) exist so that int[] conforms to Integer错误,所以我将g数组中的元素转换为包装类类型,然后再存入结果集合,具体的代码如上面所示。
其他更好的解法:取模运算
这个解法是Leetcode给出的官方解法,所以学习一下,时间复杂度为O(m*n)。具体的思路如下:
二维数组移动的问题上,除了模拟方法,直接计算元素迁移后的新位置更加高效。计算新位置分为两步:
- 什么是新列?
- 什么是新行?
假设在一个三行五列的网格中,位于 i = 1 和 j = 3 处的值,迁移次数 k = 88。
第一步:计算新列值
k步迁移之后,列值改变k次,每一步,列值都会改变一次,然而网格并不是无限大的,元素在横向上可以想象是在进行循环运动,因为列数是5所以每运动5次元素就会返回到原始的列位置(注意是列位置,并不是精确位置)。
所以k步迁移后,元素的列位置可以通过(88+3)%5计算出,所以新的列位置为1;
抽象为一般公式为:new_col = (j + k) % num_cols,j为元素起始列值,num_cols为网格总的列数。
第二步:计算新行值
行值变换并不频繁,只有当列值从最后一列变为第0列才会使得行值发生改变,同时元素在纵向上也是一个循环运动,所以要确定新的行值,需要确定从最后一列移动到第0列的次数,这里使用商计算行移动的次数。
抽象为一般公式:new_row = (i + (j + k) /num_cols) % num_rows,i为元素起始行值,num_rows总行数
代码如下:
public List<List<Integer>> shiftGrid(int[][] grid, int k) { int numCols = grid[0].length; int numRows = grid.length; // Setup the 2d list. List<List<Integer>> newGrid = new ArrayList<>(); for (int row = 0; row < numRows; row++) { List<Integer> newRow = new ArrayList<>(); newGrid.add(newRow); for (int col = 0; col < numCols; col++) { newRow.add(0); } } for (int row = 0; row < numRows; row++) { for (int col = 0; col < numCols; col++) { int newCol = (col + k) % numCols; int wrapAroundCount = (col + k) / numCols; int newRow = (row + wrapAroundCount) % numRows; newGrid.get(newRow).set(newCol, grid[row][col]); } } return newGrid; }
注意:List集合是有索引的,所以可以通过索引访问集合。