一、二维数组中的查找
剑指 Offer 04. 二维数组中的查找https://leetcode.cn/problems/er-wei-shu-zu-zhong-de-cha-zhao-lcof/
在一个 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
解题思路:
1.暴力解法:顺序查找,时间复杂度O(NM),没有用到每一行都按照从左到右递增的顺序,每一列都按照从上到下递增的顺序的特点,显然还有更高效的时间复杂度算法。
2.由每一行都按照从左到右递增的顺序,每一列都按照从上到下递增的顺序的特点,故从左下角的元素开始,同列元素都小于他,同行元素都大于他,因此可以高效找到目标元素。
2.右上角元素,同列元素都大于他,同行元素都小于他,也可高校找到目标元素。
具体细节:
1.从矩阵左下角元素matrix[i][j] 开始遍历:
2.当 matrix[i][j] > target 时,i-- ;
3.当 matrix[i][j] < target 时,j++;
4.当 matrix[i][j] = target 时,返回 true ,找到目标值。
5.若行索引或列索引越界,则代表矩阵中无目标值,返回 false 。
复杂度分析:
1.时间复杂度:从左下到右上还没找到的话最坏时间复杂度O(N+M)。
2.空间复杂度:常数级别变量O(1)。
代码实现:
class Solution {
public:
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
int n = matrix.size();
if (n == 0) return false;
int m = matrix[0].size();
int i = n - 1;
int j = 0;
while (i >= 0 && j < m) {
if (matrix[i][j] > target) i--;
else if (matrix[i][j] < target) j++;
else return true;
}
return false;
}
};
二、旋转数组的最小数字
剑指 Offer 11. 旋转数组的最小数字https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
给你一个可能存在 重复 元素值的数组 numbers ,它原来是一个升序排列的数组,并按上述情形进行了一次旋转。请返回旋转数组的最小元素。例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一次旋转,该数组的最小值为 1。
注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]] 。
示例 1:
输入:numbers = [3,4,5,1,2]
输出:1
示例 2:
输入:numbers = [2,2,2,0,1]
输出:0
提示:
n == numbers.length
1 <= n <= 5000
-5000 <= numbers[i] <= 5000
numbers 原来是一个升序排序的数组,并进行了 1 至 n 次旋转
解题思路:
1.暴力解法:遍历数组时间复杂度为O(N),并未用到数组升序排列的特点,显然有更优的时间复杂度。
2.二分查找:数组可以分为两个升序数组,左数组元素都大于右数组元素。故最小元素即为右升序数组的第一个元素。
具体细节:
1.初始化left = 0,right = n - 1;
2.循环二分: 计算 mid = (left + right) / 2;
3.当 nums[mid] > nums[right] 时: mid 一定在左排序数组中,left = mid +1;
4.当 nums[mid] < nums[right] 时: mid 一定在右排序数组中,right = mid;
5.当 nums[mid] = nums[right] 时: 无法判断 mid 在哪个排序数组中,right = right - 1 缩小判断范围。
6.返回值: 当 left >= right 时跳出循环,返回右数组的第一个元素nums[left]。
复杂度分析:
1.时间复杂度:二分查找O(logN)。
2.空间复杂度:O(1)。
代码实现:
class Solution {
public:
int minArray(vector<int>& numbers) {
int left = 0, right = numbers.size() - 1;
while (left < right) {
int mid = (left + right) / 2;
if (numbers[mid] > numbers[right]) left = mid + 1;
else if (numbers[mid] < numbers[right]) right = mid;
else right = right - 1;
}
return numbers[left];
}
};
三、第一个只出现一次的字符
剑指 Offer 50. 第一个只出现一次的字符https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例 1:
输入:s = "abaccdeff"
输出:'b'
示例 2:
输入:s = ""
输出:' '
限制:
0 <= s 的长度 <= 50000
解题思路:
1.暴力解法:i从0 ~ n - 1遍历字符串,j从i + 1 ~ n - 1遍历字符串,若i都不等于j的话则返回,时间复杂度O(N^2),没有用到字符串只包含小写字母这一特点,仍有更优解。
2.哈希表:使用数组代替哈希表将字符串映射到数组中,数组值大于1则返回字符,没有则返回空格。
具体细节:
1.初始化一个大小为26的数组map[26]用作映射字符串。
2.遍历字符串将其映射到数组map中:map[s[i] - 'a'] ++;
3.再遍历一遍找到第一个只出现一次的字母。
复杂度分析:
1.时间复杂度:遍历两遍字符串,O(N)。
2.空间复杂度:常数级别的数组,O(1)。
代码实现:
class Solution {
public:
char firstUniqChar(string s) {
int map[26] = {0};
for (int i = 0; i < s.size(); i++) {
map[s[i] - 'a'] ++;
}
for (int i = 0; i < s.size(); i++) {
if (map[s[i] - 'a'] == 1) return s[i];
}
return ' ';
}
};