「这是我参与11月更文挑战的第四天,活动详情查看:2021最后一次更文挑战」
各位小伙伴大家好啊,今天是第五天了。已经有几天没有打卡了,主要是这两天满课,从早上八点上到晚上十点,确实没办法打卡。今天继续刷题,把之前的慢慢补上。
第一题 剑指 Offer 04. 二维数组中的查找
在一个 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]
]
给定 target = 5,返回 true。
给定 target = 20,返回 false。
限制:
0 <= n <= 1000
0 <= m <= 1000
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/er…
解题思路:
二分查找
题目中明确指出——每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。那么说明每一行都是有序数组,每一列也是有序数组。所以和上一次提到的思路一致,有序数列又是寻找元素,那么就可以考虑二分查找了。因为每一行最右边的数为当前行最大值,那么如果最大值都小于了target那就不用查找了,一定没有target。如果相等就直接返回。
时间复杂度:O(nlogn),空间复杂度:O(1)
线性查找
一般来说,二分查找就应该可以了,但是提交之后,发现依旧不是最优解。在翻阅题解后,发现了一种类似二叉排序树的线性查找方法,在此记录下来。它从右上角开始,其左边的元素均小于它自己,下方的元素均大于它自己。那么要查找比它本身大的元素,row++,要查找比它本身小的元素,col--。此算法最多减m列,加n行,所以时间复杂度O(n + m)。
时间复杂度:O(n + m),空间复杂度:O(1)
代码实现:
// 二分查找
var findNumberIn2DArray = function(matrix, target) {
let flag = -1;
for(let arr of matrix) {
if(arr[arr.length - 1] > target) {
flag = binarySearch(arr, target);
if(flag !== -1) {
return true;
}
} else if(arr[arr.length - 1] === target) {
return true
}
}
return false;
};
// 二分查找函数实现
function binarySearch(arr, target) {
let left = 0, right = arr.length - 1;
let mid;
while(left < right) {
mid = Math.floor((left + right) / 2);
if(arr[mid] < target) {
left = mid + 1;
} else if (arr[mid] > target) {
right = mid - 1;
} else {
return mid;
}
}
return arr[left] === target ? left : -1;
}
// 线性遍历
var findNumberIn2DArray = function(matrix, target) {
if(matrix.length === 0 || matrix[0].length === 0) {
return false;
}
let row = 0, col = matrix[0].length - 1, val;
while(row < matrix.length && col >= 0) {
val = matrix[row][col];
if(val > target) {
col--;
} else if (val < target) {
row++;
} else {
return true;
}
}
return false;
};
第二题 剑指 Offer 11. 旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。
示例 1:
输入:[3,4,5,1,2]
输出:1
示例 2:
输入:[2,2,2,0,1]
输出:0
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/xu…
解题思路:此时我们看到了一个关键字——递增排序数组。题目要求是查找最小值,可能大家都已经在猜想是否可以用二分查找了。但此题目的二分查找与寻常二分法不同,因为数组不是严格有序的。
二分查找
我们要找到最小值(旋转点),就要找到右排序数组的首个元素。
left:左边界,right:右边界,mid:中间值
分三种情况讨论:
arr[mid]<arr[right]:右排序数组的首个元素只可能在[left, mid]中。arr[mid]>arr[right]:右排序数组的首个元素只可能在[mid+1, right]中。arr[mid]===arr[right]:无法确定最小值范围,right--缩小范围。因为arr[mid]是等于arr[right]的,所以arr[right]的去除不会影响最小值。
**注意:**为什么要使用arr[right]而不使用arr[left]呢?正常情况下,arr[mid]>arr[left]右边界的首个元素应该在[mid+1, right]中,但如果旋转了0次,例如[1, 3, 5]。此时arr[mid]>arr[left],右排序数组的首个元素不在[mid+1, right]中,与左边界比较arr[mid]>arr[left]时所以无法确定右排序数组首个元素的位置。
代码实现:
// 二分查找
var minArray = function(numbers) {
let left=0, right=numbers.length - 1;
let mid;
while(left < right) {
// (right + left) / 2有可能造成整数溢出问题
mid = left + Math.floor((right - left) / 2);
if(numbers[mid] < numbers[right]) {
right = mid;
} else if(numbers[mid] === numbers[right]) {
right--;
} else {
left = mid + 1;
}
}
return numbers[left];
};
第三题 剑指 Offer 50. 第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例 1:
输入:s = "abaccdeff"
输出:'b'
示例 2:
输入:s = ""
输出:' '
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/di…
解题思路:统计问题,一般来说可以尝试哈希表的做法。
哈希
让我们来看看这题能否使用哈希,我们利用哈希表来存储每个字符出现的个数,最后输出第一个只出现一次的字符。最后成功通过了,但是效率不是很高。
indexOf
利用JavaScript内置的函数indexOf查看在当前位置加一后是否依然有该字符出现,如果没有(indexOf返回-1)则返回该字符。
代码实现:
// 哈希表
var firstUniqChar = function(s) {
let map = new Map();
for(let c of s) {
// 字符出现过次数加一,否则存储1次
map.has(c) ? map.set(c, map.get(c)+1) : map.set(c, 1);
}
for(let [key,val] of map) {
if(val === 1) {
return key;
}
}
return ' ';
};
// indexOf
var firstUniqChar = function(s) {
for(let c of s) {
let index = s.indexOf(c, s.indexOf(c) + 1);
if(index === -1){
return c;
}
}
return ' ';
};
总结
今天主要还是考察了二分查找算法,第三题可以用哈希来解决。二分查找算法一般对应于有序数组的查找问题,有时候他也会有变形,比如第二题将有序数组旋转之后再使用二分,但是其本质不变,还是分为左边界、右边界、中间值来讨论即可。第三题虽然用哈希解决了问题,但效率不高,反而用内置的indexOf的效率更高,所以有时候不要一味的追求规范方法。如果有什么不对的地方,或者有更好的方案,欢迎提出,大家一起讨论。