二维数组的查找
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数
示例:
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
解题思路1
🧠 解题思路
根据题意已知,二维数组从左往右递增,从上往下递增,所以得出以下结论:
某列的某个数字,该数之上的数字,都比其小;
某行的某个数字,该数右侧的数字,都比其大;
所以,解题流程如下所示:
以二维数组左下角为原点,建立直角坐标轴。
若当前数字大于了查找数,查找往上移一位。
若当前数字小于了查找数,查找往右移一位。
var findNumberIn2DArray = function(matrix, target) {
if(!matrix.length) return false;
let x = matrix.length - 1, y = 0;
while(x >= 0 && y < matrix[0].length){
if(matrix[x][y] === target){
return true;
}else if(matrix[x][y] > target){
x--;
}else{
y++;
}
}
return false;
};
解题思路2
数组扁平化,利用flat()函数
知识点:
const numbers = [1, 2, [3, 4, [5, 6]]];
// Considers default depth of 1
numbers.flat();
> [1, 2, 3, 4, [5, 6]]
// With depth of 2
numbers.flat(2);
> [1, 2, 3, 4, 5, 6]
// Executes two flat operations
numbers.flat().flat();
> [1, 2, 3, 4, 5, 6]
// Flattens recursively until the array contains no nested arrays
numbers.flat(Infinity)
> [1, 2, 3, 4, 5, 6]
var findNumberIn2DArray = function(matrix, target) {
return matrix.flat(Infinity).includes(target)
};
这个时间空间复杂度有点高
顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
解题思路1
可以模拟打印矩阵的路径。初始位置是矩阵的左上角,初始方向是向右,当路径超出界限或者进入之前访问过的位置时,顺时针旋转,进入下一个方向。
判断路径是否进入之前访问过的位置需要使用一个与输入矩阵大小相同的辅助矩阵 \textit{visited}visited,其中的每个元素表示该位置是否被访问过。当一个元素被访问时,将 \textit{visited}visited 中的对应位置的元素设为已访问。
如何判断路径是否结束?由于矩阵中的每个元素都被访问一次,因此路径的长度即为矩阵中的元素数量,当路径的长度达到矩阵中的元素数量时即为完整路径,将该路径返回。
var spiralOrder = function(matrix) {
if (!matrix.length || !matrix[0].length) {
return [];
}
const rows = matrix.length, columns = matrix[0].length;
const visited = new Array(rows).fill(0).map(() => new Array(columns).fill(false));
const total = rows * columns;
const order = new Array(total).fill(0);
let directionIndex = 0, row = 0, column = 0;
const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]];
for (let i = 0; i < total; i++) {
order[i] = matrix[row][column];
visited[row][column] = true;
const nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];
if (!(0 <= nextRow && nextRow < rows && 0 <= nextColumn && nextColumn < columns && !(visited[nextRow][nextColumn]))) {
directionIndex = (directionIndex + 1) % 4;
}
row += directions[directionIndex][0];
column += directions[directionIndex][1];
}
return order;
};
复杂度分析
时间复杂度:O(mn)。
空间复杂度:O(mn)。
解题思路2
可以将矩阵看成若干层,首先打印最外层的元素,其次打印次外层的元素,直到打印最内层的元素。
[[1, 1, 1, 1, 1, 1, 1],
[1, 2, 2, 2, 2, 2, 1],
[1, 2, 3, 3, 3, 2, 1],
[1, 2, 2, 2, 2, 2, 1],
[1, 1, 1, 1, 1, 1, 1]]
定义矩阵的第 k 层是到最近边界距离为 k 的所有顶点。例如,下图矩阵最外层元素都是第 1层,次外层元素都是第 2 层,剩下的元素都是第3层。
对于每层,从左上方开始以顺时针的顺序遍历所有元素。假设当前层的左上角位于 (top, left),右下角位于(bottom,right),按照如下顺序遍历当前层的元素。
从左到右遍历上侧元素,依次为 (top,left) 到(top,right)。
从上到下遍历右侧元素,依次为(top+1,right) 到 (bottom,right)。
如果left<right 且 top<bottom,则从右到左遍历下侧元素,依次为 (bottom,right−1) 到 (bottom,left+1),以及从下到上遍历左侧元素,依次为 (bottom,left) 到)(top+1,left)。
遍历完当前层的元素之后,将left和top分别增加 11,将right和bottom分别减少 11,进入下一层继续遍历,直到遍历完所有元素为止。
var spiralOrder = function(matrix) {
if(!matrix.length || !matrix[0].length){
return []
};
var rows = matrix.length;
var cols = matrix[0].length;
var order = [];
var left = 0,right = cols-1,top = 0,bottom = rows-1;
while(left<=right&&top<=bottom){
for(var col=left;col<=right;col++){
order.push(matrix[top][col])
};
for(var row=top+1;row<=bottom;row++){
order.push(matrix[row][right])
};
if(left<right&&top<bottom){
for(var col=right-1;col>=left;col--){
order.push(matrix[bottom][col])
};
for(var row=bottom-1;row>=top+1;row--){
order.push(matrix[row][left])
}
}
[left, right, top, bottom] = [left + 1, right - 1, top + 1, bottom - 1];
}
return order
};
时间复杂度:O(mn),其中 m 和 n 分别是输入矩阵的行数和列数。矩阵中的每个元素都要被访问一次。
空间复杂度:O(1)。除了输出数组以外,空间复杂度是常数。
在排序数组中查找数字 I
解题思路1
直接查找
var search = function(nums, target) {
var n=0;
for(let i=0;i<nums.length;i++){
if(nums[i] == target){
n++
}
}
return n
};
解题思路2
利用indexOf 和lastIndexOf
var search = function(nums, target) {
return nums.indexOf(target)!=-1?nums.lastIndexOf(target)-nums.indexOf(target)+1:0
};
解题思路3
二分法
数组无序的情况下,直接查找效率更高
数组有序的情况下,二分查找效率更高
var search = function(nums, target) {
let start = end = -1;
let n = nums.length;
let left = 0;
let right = n-1;
while(left<=right){
let mid = Math.floor((right+left)/2)
if(nums[mid] == target){
start = mid;
right = start-1;
}else if(nums[mid]<target){
left = mid+1
}else{
right = mid-1
}
}
left = 0;
right = n-1;
while(left<=right){
let mid = Math.floor((right+left)/2)
if(nums[mid] == target){
end = mid;
left = end+1;
}else if(nums[mid]<target){
left = mid+1
}else{
right = mid-1
}
}
return start==-1?0:end-start+1
};
0~n-1中缺失的数字
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
示例 1:
输入: [0,1,3]
输出: 2
示例 2:
输入: [0,1,2,3,4,5,6,7,9]
输出: 8
解题思路1
二分法
var missingNumber = function(nums) {
let left = 0;
let right = nums.length - 1;
while(left<=right){
let mid = Math.floor((left+right)/2);
if(nums[mid]>mid){
right = mid-1;
}else if(nums[mid] == mid){
left = mid+1;
}
}
return left
};
解题思路2
比较巧妙的方法
var missingNumber = function(nums) {
for(let i=0;i<=nums.length;i++){
if(nums[i] != i){
return i
}
}
};