03、数组中重复数字
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
输入: [2, 3, 1, 0, 2, 5, 3] 输出:2 或 3
限制:2 <= n <= 100000
方法一:先排序,然后看相邻元素是否有相同的,有直接return。不过很慢,时间O(nlogn)了,空间O(1)
class Solution {
public int findRepeatNumber(int[] nums) {
Arrays.sort(nums);
for (int i = 1; i < nums.length; i++)
{
if (nums[i] == nums[i -1])
return nums[i];
}
return -1;
}
}
方法二:哈希表 时间O(n),空间O(n)
int n = nums.length;
Set<Integer> set = new HashSet<Integer>();
for (int i = 0; i < n; i++) {
if (set.contains(nums[i]))
return nums[i];
set.add(nums[i]);
方法三:时间复杂度O(n),空间复杂度O(1)。可以看做是一种原地哈希,不过没有用到字典。具体做法就是因为题目中给的元素是 < len(nums)的,所以我们可以让 位置i 的地方放元素i。如果位置i的元素不是i的话,那么我们就把i元素的位置放到它应该在的位置,即 nums[i] 和nums[nums[i]]的元素交换,这样就把原来在nums[i]的元素正确归位了。如果发现 要把 nums[i]正确归位的时候,发现nums[i](这个nums[i]是下标)那个位置上的元素和要归位的元素已经一样了,说明就重复了,重复了就return。
class Solution {
public int findRepeatNumber(int[] nums) {
int temp;
for(int i=0;i<nums.length;i++){
while (nums[i]!=i){
if(nums[i]==nums[nums[i]]){
return nums[i];
}
temp=nums[i];
nums[i]=nums[temp];
nums[temp]=temp;
}
}
return -1;
}
}
方法四:(不修改数组找出重复数字)
04、二维数组中的查找
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。
[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
方法一:逐行使用二分查找
思路分析:
要查找矩阵中是否存在某个元素,并且表示矩阵每一行的数组都是有序的,可以对每一行进行二分查找,如果每一行都没有查找到结果,就返回false。
使用java库函数Arrays.binarySearch(matrix[i], target),返回值非负说明查找到target。
还需要处理一些特殊情况:
给定的矩阵为null或者给定的矩阵不存在任何元素matrix.length == 0 || matrix[0].length == 0,肯定找不到目标元素,直接返回。
矩阵的每一列也是从小到达排列的,所以在对每一行进行二分查找的循环是,如果matrix[i][0] > target,这一行肯定没有指定元素,更下面的每一行的所有元素都大于matrix[i][0]也一定找不到指定元素,所以可以直接返回false。
时间复杂度为O(mlog(n)),空间复杂度为O(1)。
public boolean searchMatrix(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0)
return false;
for (int i = 0; i < matrix.length; i++) {
if (matrix[i][0] > target)
return false;
if (Arrays.binarySearch(matrix[i], target) >= 0)
return true;
}
return false;
}
方法二:线性查找(类似于Binary Search Tree 二叉搜索树,节点左子树都小于该节点,右子树都大于该节点)
时间复杂度:O(n+m)。访问到的下标的行最多增加 n 次,列最多减少 m 次,因此循环体最多执行 n + m 次。
空间复杂度:O(1)。
如果当前元素大于目标值,说明当前元素的下边的所有元素都一定大于目标值,因此往下查找不可能找到目标值,往左查找可能找到目标值。如果当前元素小于目标值,说明当前元素的左边的所有元素都一定小于目标值,因此往左查找不可能找到目标值,往下查找可能找到目标值。
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0)
{
return false;
}
int rows = matrix.length, columns = matrix[0].length;
int row = 0, column = columns - 1;
while (row < rows && column >= 0)
{
int num = matrix[row][column];
if (num == target)
{return true;}
else if (num > target)
{column--; }
else
{ row++; }
}
return false;
}
29、顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例 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]
限制:
0 <= matrix.length <= 100
0 <= matrix[i].length <= 100
方法一:设定边界
算法流程:
空值处理: 当 matrix 为空时,直接返回空列表 [] 即可。
初始化: 矩阵 左、右、上、下 四个边界 l , r , t , b ,用于打印的结果列表 res 。
循环打印: “从左向右、从上向下、从右向左、从下向上” 四个方向循环,每个方向打印中做以下三件事 (各方向的具体信息见下表)
根据边界打印,即将元素按顺序添加至列表 res 尾部;
边界向内收缩 1 (代表已被打印);
判断是否打印完毕(边界是否相遇),若打印完毕则跳出
打印方向 | 1. 根据边界打印 | 2. 边界向内收缩 | 3. 是否打印完毕 |
---|---|---|---|
从左向右 | 左边界l,右边界 r | 上边界 t 加 1 | 是否 t > b |
从上向下 | 上边界 t ,下边界b | 右边界 r 减 1 | 是否l > r |
从右向左 | 右边界 r ,左边界l | 下边界 b 减 1 | 是否 t > b |
从下向上 | 下边界 b ,上边界t | 左边界 l 加 1 | 是否 l > r |
复杂度分析:
时间复杂度 O(MN): M, N 分别为矩阵行数和列数。
空间复杂度 O(1): 四个边界 l , r , t , b 使用常数大小的 额外 空间( res 为必须使用的空间)。
class Solution {
public int[] spiralOrder(int[][] matrix) {
if(matrix.length == 0) return new int[0];
int l = 0, r = matrix[0].length - 1, t = 0, b = matrix.length - 1, x = 0;
int[] res = new int[(r + 1) * (b + 1)];
while(true) {
for(int i = l; i <= r; i++) res[x++] = matrix[t][i]; // left to right.
if(++t > b) break;
for(int i = t; i <= b; i++) res[x++] = matrix[i][r]; // top to bottom.
if(l > --r) break;
for(int i = r; i >= l; i--) res[x++] = matrix[b][i]; // right to left.
if(t > --b) break;
for(int i = b; i >= t; i--) res[x++] = matrix[i][l]; // bottom to top.
if(++l > r) break;
}
return res;
}
}
方法二:按层模拟
53-1、在排序数组中查找数字
统计一个数字在排序数组中出现的次数。
示例 1:输入: nums = [5,7,7,8,8,10], target = 8
输出: 2
示例 2:输入: nums = [5,7,7,8,8,10], target = 6
输出: 0
限制:0 <= 数组长度 <= 50000
方法一:算法解析:
初始化: 左边界 i = 0 ,右边界 j = len(nums) - 1。
循环二分: 当闭区间 [i, j]无元素时跳出;
计算中点 m = (i + j) / 2 (向下取整);
若 nums[m] < target ,则 target在闭区间 [m + 1, j] 中,因此执行 i = m + 1;
若 nums[m] > target ,则 target 在闭区间 [i, m - 1] 中,因此执行 j = m - 1;
若 nums[m] = targetn ,则右边界 right 在闭区间 [m+1, j]中;左边界 left在闭区间 [i, m-1] 中。因此分为以下两种情况:
若查找 右边界 right ,则执行 i = m + 1;(跳出时 i指向右边界)
若查找 左边界 left ,则执行 j = m - 1;(跳出时 j 指向左边界)
返回值: 应用两次二分,分别查找 right 和 left,最终返回 right - left - 1即可。
效率优化:
以下优化基于:查找完右边界 right = i后,则 nums[j] 指向最右边的 target(若存在)。
查找完右边界后,可用 nums[j] = j 判断数组中是否包含 target,若不包含则直接提前返回 0 ,无需后续查找左边界。
查找完右边界后,左边界 left 一定在闭区间 [0, j] 中,因此直接从此区间开始二分查找即可。
时间复杂度 O(log N): 二分法为对数级别复杂度。
空间复杂度 O(1): 几个变量使用常数大小的额外空间。
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;
// 搜索左边界 right
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 left = j;
return right - left - 1;
}
}
以上代码显得比较臃肿(两轮二分查找代码冗余)。为简化代码,可将二分查找右边界 rightright 的代码 封装至函数 helper() 。
本质上看, helper() 函数旨在查找数字 tartar 在数组 numsnums 中的 插入点 ,且若数组中存在值相同的元素,则插入到这些元素的右边。
class Solution:
def search(self, nums: [int], target: int) -> int:
def helper(tar):
i, j = 0, len(nums) - 1
while i <= j:
m = (i + j) // 2
if nums[m] <= tar: i = m + 1
else: j = m - 1
return i
return helper(target) - helper(target - 1)
53-2、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
方法一:遍历数组只要比较数组下标和该下标对应的值即可,再排除缺失0和缺失最后一项两个特殊情况。
时间复杂度: O(N)
class Solution {
public int missingNumber(int[] nums) {
if (nums[0]==1) return 0;
for (int i = 0;i<nums.length;i++){
if (nums[i]!=i) return i;
}
return nums.length;
}
}
方法二:(二分法)比较中间值与下标的相等关系,相等将更改左边界,不相等更改右边界。
时间复杂度 O(log N): 二分法为对数级别复杂度。
空间复杂度 O(1): 几个变量使用常数大小的额外空间。
class Solution {
public int missingNumber(int[] nums) {
int i = 0, j = nums.length - 1;
while(i <= j) {
int m = (i + j) / 2;
if(nums[m] == m) i = m + 1;
else j = m - 1;
}
return i;
}
}