访谈的问题是,存在一个具有整数的矩阵。找到最长的数字序列的长度加一。允许的方向是[从左到右,从上到下]。
4 2 2
5 6 3
7 5 4
例如,2 3 4 5 6
是最长的序列。
我的回答是说我们遍历了每个数字,然后通过访问其4个邻居来递归尝试查找该数字的序列。然后我被问到您的算法的复杂性是什么。我说k * (4 ^ k)
是因为我遍历了每个数字(因此取了k),然后我可以看到每个数字的4个邻居。 k是n * n表示矩阵中元素的数量。但是我不确定我对复杂性的回答是否正确。另一方面,由于我最多在考虑我们访问矩阵中所有数字的每个数字,在这种情况下,复杂度为k ^ 2.
答案 0 :(得分:1)
方法和示例贯穿
如果我们创建第二个网格,我们将在其中存储可以从每个单元格向上进行的步数,那么我们可以更高效地执行此操作。让我们使用以下示例:
3 1 2 5 - - - -
4 2 5 6 - - - -
5 3 4 7 - - - -
6 7 5 4 - - - -
我们从第一个单元格(左上3)开始,检查它是否有一个值为4的邻居,如果是,则移至该单元格并寻找5,依此类推;我们被困在值为7的单元格上。我们用1标记最后一个位置,然后向后走,标记单元格2、3、4和5:
3 1 2 5 x - - - 5 - - -
4 2 5 6 x - - - 4 - - -
5 3 4 7 x - - - 3 - - -
6 7 5 4 x x - - 2 1 - -
我们现在知道左上方的单元格是向上5步序列的开始。然后,我们切换到下一个未标记的单元格,即3右边的1;它有两个值为2的相邻单元格;我们首先向右移动并停留在2。我们将该单元格标记为1,然后返回到起点并将其临时标记为2。
3 1 2 5 - x x - 5 2 1 -
4 2 5 6 - - - - 4 - - -
5 3 4 7 - - - - 3 - - -
6 7 5 4 - - - - 2 1 - -
现在,我们沿着第二条路径从起点开始,一直到2和3,然后一直到4,然后有两个相邻的5;我们尝试先降低5,然后卡在该单元格中;我们将其标记为1,然后回到4并临时标记为2:
3 1 2 5 - x - - 5 2 1 -
4 2 5 6 - x - - 4 - - -
5 3 4 7 - x x - 3 - 2 -
6 7 5 4 - - x - 2 1 1 -
然后,我们尝试4之上的5,继续下去,直到陷入7;我们将其标记为1,然后返回,直到到达临时标记为2的单元格;它在当前路径中的标记将是4,这是较高的标记,因此我们将2替换为4,然后再返回,直到到达该路径的起点(临时标记为2)。新标记为7,因此我们替换了2乘7得到:
3 1 2 5 - x - - 5 7 1 -
4 2 5 6 - x x x 4 6 3 2
5 3 4 7 - x x x 3 5 4 1
6 7 5 4 - - - - 2 1 1 -
我们继续到下一个未标记的单元格,即右上角的5。它有一个相邻的6,已经标记为2,这意味着我们可以将该单元格标记为3(您将看到它确实是从5到7的3步路径的起点):
3 1 2 5 - - - x 5 7 1 3
4 2 5 6 - - - x 4 6 3 2
5 3 4 7 - - - - 3 5 4 1
6 7 5 4 - - - - 2 1 1 -
我们继续到下一个未标记的单元格,它是右下角的4。它有一个相邻的5,已经标记为1,这意味着我们可以将该单元标记为2。
3 1 2 5 - - - - 5 7 1 3
4 2 5 6 - - - - 4 6 3 2
5 3 4 7 - - - - 3 5 4 1
6 7 5 4 - - x x 2 1 1 2
第二个网格现已完成,我们添加的最大数字为7,这意味着网格中最长的序列的长度为7。
复杂度
我们已经访问了每个单元格作为路径的一部分,并在第二个网格中输入值的同时向后走,因此复杂度与单元格数或O(N)成线性关系。当然,此方法需要第二个网格,因此空间复杂度也为O(N)。
代码示例
这是我编写的一个JavaScript快速代码示例,以测试该方法并检查我对时间复杂度的假设。结果可在代码段下方找到。
function longestSequence(val) {
var dx = [0, 1, 0, -1], dy = [-1, 0, 1, 0]; // up, right, down, left
var height = val.length;
var width = val[0].length;
var max = 0; // max length found so far
var stack = []; // cells in the current path
var len = []; // length of upwards sequence from each cell
for (var y = 0; y < height; y++) {
len[y] = [];
for (var x = 0; x < width; x++) {
len[y][x] = 0; // initialize length grid
}
}
for (var y = 0; y < height; y++) { // iterate over every cell
for (var x = 0; x < width; x++) {
if (len[y][x] != 0) continue; // skip cells already checked
stack.push({x: x, y: y}); // start from this cell upwards ...
while (stack.length) { // and do a depth-first search
var cur = stack.pop(); // take current cell from stack
for (var i = 0; i < 4; i++) { // check four neighbouring cells
var nbr = {x: cur.x + dx[i], y: cur.y + dy[i]}; // get neighbouring cell
if (nbr.x < 0 || nbr.x == width || nbr.y < 0 || nbr.y == height) {
continue; // skip if off-grid
}
if (val[nbr.y][nbr.x] == val[cur.y][cur.x] + 1) { // neighbour has next value
if (len[nbr.y][nbr.x] == 0) { // neighbour not yet checked
stack.push(cur); // this cell is not last in path
stack.push(nbr); // move to neighbouring cell
break;
}
else if (len[nbr.y][nbr.x] >= len[cur.y][cur.x]) { // neighbour has higher length
len[cur.y][cur.x] = len[nbr.y][nbr.x] + 1; // take length from neighbour
}
}
}
if (len[cur.y][cur.x] == 0) { // no suitable neighbours ...
len[cur.y][cur.x] = 1; // cell is end-point of path
}
}
if (len[cur.y][cur.x] > max) { // new maximum length found
max = len[cur.y][cur.x];
}
}
}
return max;
}
var grid = [[3, 1, 2, 5],
[4, 2, 5, 6],
[5, 3, 4, 7],
[6, 7, 5, 4]];
document.write(longestSequence(grid));
检查线性复杂度
通过查看带有嵌套循环的代码来判断该算法的复杂性并不是那么简单。为了检查我的线性假设,我使用从1到9的随机数网格运行代码,并添加了一个计数器以查看总共有多少个单元格被压入堆栈:
grid size cells push/pop
8 x 8 64 75
16 x 16 256 297
32 x 32 1,024 1,235
64 x 64 4,096 4,912
128 x 128 16,384 19,557
256 x 256 65,536 78,254
512 x 512 262,144 313,371
1024 x 1024 1,048,576 1,253,540
结果证实,复杂度的确与细胞数量呈线性关系。推入堆栈的单元格数量大约是网格中单元格数量的120%,而并非精确地是100%,这是因为根据路径中的位置,单元格被访问一次,两次或多次(结束)点,中点,十字路口)。
要说明实际速度:上述JavaScript版本可在不到一秒钟的时间内解决1024×1024网格。
答案 1 :(得分:0)
顺便说一句有趣的小技巧:按值对单元格进行排序,并为每个元素分配1 +上一个可连接单元格中标记的最长可实现序列的长度。例如:
4 2 2
5 6 3
7 5 4
2 2 3 4 4 5 5 6 7
2 -> 1
2 -> 1
3 -> 1 + 1 = 2
4 -> 1 + 2 = 3
5 -> 1 + 3 = 4
6 -> 1 + 4 = 5
7 -> 1
对于重复项,我们需要为每个值创建位置的哈希,因此我们可以快速找到所遍历的下一个元素的可连接单元格。 O(n*m * log(n*m))
答案 2 :(得分:0)
我说k *(4 ^ k),因为我遍历了每个数字(因此是k)
它不会将复杂度乘以k。您可以安全地添加O(k)以确保复杂度至少为O(k),但是除非您在每个步骤中都经过〜k个值(递归调用),否则无需将复杂度乘以k。
然后我可以看到每个人的四个邻居。
您看到4个邻居,但是除非您在每个邻居中分叉递归,否则它不会计数。您可以简单地乘以4来反映4个选择,但是无论如何,常数都会在O数学中删除。
实际上,根据问题的定义,您不能每次都使用全部4种方法,因为上一项在当前项的旁边。因此,最多只能将其乘以3,并且由于圆弧状平面组成的原因,它不能长时间工作-矩阵密度不足。不幸的是,我不知道简单的方法
有效地,您对每个有效点所做的不超过n *种不同方式* 4(用于下一步)。
即使我有一个达到O(2 ^ k)复杂度的坏情况示例。进行对角线填充:在左上角的单元格中为1,在下一个对角线中为2,在下一个对角线中为3,等等。以矩阵的最长对角线结束,右下角部分填充零。
0,1,2,3,4,5,6
1、2、3、4、5、6、0
2、3、4、5、6、0、0
3、4、5、6、0、0、0
4、5、6、0、0、0、0
5、6、0、0、0、0、0
6,0,0,0,0,0,0
另一方面,由于我最多只考虑每个数字,因此我们访问所有数字 在矩阵中,在这种情况下,复杂度将为k ^ 2。
错误,如果您以简单的递归方式执行此操作而没有“ visited”标志,则递归将使每个单元超过1次访问。对于上面的矩阵,您可以模仿带有RIGHT = 0或DOWN = 1元素的6个元素的数组的方法,显然每种组合都有一种方法,显然它们是不同的,因此对于2次访问,您有2 ^ 6种不同的有效方法。
还有一件事。通过结果长度或值来计算复杂度是不切实际的。您具有矩阵维之类的输入值,这是我们在O数学中通常使用的值,因为人们无法预测他们将得到什么答案...