励志
题目来源:https://leetcode-cn.com/leetbook/detail/illustration-of-algorithm/
配合学习文档:https://www.kancloud.cn/alex_wsc/dataalg/1853982
书籍推荐:
《漫画算法》
视频推荐:https://www.bilibili.com/video/BV1E4411H73v
目录
一、剑指 Offer 03. 数组中重复的数字
题:
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
示例 1:
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3
限制:
2 <= n <= 100000
解:
解题思路:哈希表
AC代码:
class Solution {
public int findRepeatNumber(int[] nums) {
Set<Integer> dic = new HashSet<>();
for(int num : nums){
if(dic.contains(num)) return num;
dic.add(num);
}
return -1;
}
}
- 时间复杂度 O(N) : 遍历数组使用 O(N),HashSet 添加与查找元素皆为 O(1)
- 空间复杂度 O(N) : HashSet 占用 O(N) 大小的额外空间
解题思路:索引映射
(数组元素的索引和值是一对多的关系)
AC代码:
class Solution {
public int findRepeatNumber(int[] nums) {
int index = 0; // 指针
int len = nums.length;
while(index < len){
if(nums[index] == index){ // 索引一一对应
index++;
continue;
}
if(nums[nums[index]] == nums[index]) return nums[index];
// 交换
int temp = nums[index];
nums[index] = nums[temp];
nums[temp] = temp;
}
return -1;
}
}
时间复杂度 O(N) : 遍历数组使用 O(N) ,每轮遍历的判断和交换操作使用 O(1)。
空间复杂度 O(1) : 使用常数复杂度的额外空间。
二、剑指 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
解:
解题思路:消消乐
小了往上走,大了往右走。
AC代码:
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
int i = matrix.length - 1, j = 0; // mark坐标
while(i >= 0 && j < matrix[0].length){
if(target < matrix[i][j]) i--; // target小,往上移
else if(target > matrix[i][j]) j++; // target大,往右移
else return true;
}
return false;
}
}
- 时间复杂度 O(M+N):其中,N 和 M 分别为矩阵行数和列数,此算法最多循环 M+N 次。
- 空间复杂度 O(1): i, j 指针使用常数大小额外空间。
# 三、剑指 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
## 解:
解题思路:二分法
(排序数组的查找问题首先考虑使用 二分法 解决,其可将 遍历法 的 线性级别 时间复杂度降低至 对数级别 。)
AC代码:
class Solution {
public int minArray(int[] numbers) {
int len = numbers.length;
int i = 0, j = len - 1; // 左右指针
while(i < j){
int mid = (i + j) / 2;
if(numbers[mid] < numbers[j]) j = mid;
else if(numbers[mid] > numbers[j]) i = mid + 1; // mid一定不是最小值,越过
else j--; // 缩小二分范围
}
return numbers[i];
}
}
补充:
四、剑指 Offer 50. 第一个只出现一次的字符
题:
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例:
s = "abaccdeff"
返回 "b"
s = ""
返回 " "
限制:
0 <= s 的长度 <= 50000
解:
解题思路:哈希表
AC代码:
class Solution {
public char firstUniqChar(String s) {
Map<Character, Boolean> dic = new HashMap<>();
char[] sc = s.toCharArray();
for(char c : sc){
dic.put(c,!dic.containsKey(c));
}
for(char c : sc){
if(dic.get(c)) return c;
}
return ' ';
}
}
- 时间复杂度 O(N): N 为字符串 s 的长度;需遍历 s 两轮,使用 O(N) ;HashMap 查找操作的复杂度为 O(1) ;
- 空间复杂度 O(1) : 由于题目指出 s 只包含小写字母,因此最多有 26 个不同字符,HashMap 存储需占用 O(26) = O(1)的额外空间。
解题思路:有序哈希表
(缩小了第二次遍历的范围)
AC代码:
class Solution {
public char firstUniqChar(String s) {
Map<Character, Boolean> dic = new LinkedHashMap<>();
char[] sc = s.toCharArray();
for(char c : sc)
dic.put(c, !dic.containsKey(c));
// 大同小异
for(Map.Entry<Character, Boolean> d : dic.entrySet()){
if(d.getValue()) return d.getKey();
}
return ' ';
}
}
五、剑指 Offer 53 - I. 在排序数组中查找数字 I
题:
统计一个数字在排序数组中出现的次数。
示例 1:
输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:
输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
提示:
0 <= nums.length <= 105
-109 <= nums[i] <= 109
nums 是一个非递减数组
-109 <= target <= 109
解:
解题思路:二分法
AC代码:
class Solution {
public int search(int[] nums, int target) {
// 搜索右边界 right
int i = 0, j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
// 卡左(等号)指针位置
if(nums[m] <= target) i = m + 1;
else j = m - 1;
}
int right = i;
// 若数组中无 target ,则提前返回
if(j >= 0 && nums[j] != target) return 0;
// 搜索左边界 left
i = 0; j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
// 卡右(等号)指针位置
if(nums[m] >= target) j = m - 1;
else i = m + 1;
}
int left = j;
return right - left - 1;
}
}
代码有点冗余,优化优化(数组 nums 中元素都为整数):
通俗的讲就是 (tar - 1卡右) 等于 (tar 卡左 + 1),把卡右提取一个方法,减少冗余
AC代码:
class Solution {
public int search(int[] nums, int target) {
return findRight(nums, target) - findRight(nums, target - 1);
}
int findRight(int[] nums, int tar){
int i = 0, j = nums.length - 1;
while(i <= j){
int m = (i + j) / 2; // 下取整
// 卡左(限制i),取右(tar在右),舍边(i=m无效)
if(nums[m] <= tar) i = m + 1;
else j = m - 1;
}
return i;
}
}
六、剑指 Offer 53 - II. 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 <= 数组长度 <= 10000
解:
解题思路:二分法
AC代码:
class Solution {
public int missingNumber(int[] nums) {
int i = 0, j = nums.length - 1;
while(i <= j){
int m = (i + j) / 2;
// 卡i,去边,取右
if(nums[m] == m) i = m + 1;
else j = m - 1;
}
return i;
}
}