题目描述
找出数组中重复的数字。
在一个长度为 n 的数组 nums 里的所有数字都在 0~n-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; // 不存在重复的数字
}
}
思路:使用Map统计nums中每个元素的个数,取 次数大于1的,即可。
简单,但是带来很多不必要的统计,而且 Map 消耗空间较大
class Solution {
public int findRepeatNumber(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
for (int i : nums) {
map.put(i, map.getOrDefault(i, 0) + 1);
}
for (int i : map.keySet()) {
if (map.get(i) > 1) {
return i;
}
}
return -1; // 没有重复的数
}
}
本题是只用找到一个重复的数字,即可。所以,使用 Set 比 Map 更友好一些。但是,如果要找所有重复的数字 以及 次数,那还是得用 Map。
思路:
Note
:二者顺序 不可颠倒…
class Solution {
public int findRepeatNumber(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int i : nums) {
if (set.contains(i)) return i; // !!! 二者顺序 不可颠倒...
set.add(i);
}
return -1; // 没有重复的数
}
}
class Solution {
public int findRepeatNumber(int[] nums) {
int n = nums.length;
int[] hash = new int[n];
for (int i : nums) {
if (hash[i] > 0) {
return i;
}
hash[i]++;
}
return -1; // 无
}
}
题目说了:
所有数字都在 0~n-1 的范围内
。如果这个数组中没有重复数字,则数组排序后数字 i 应该出现在下标 i 的位置;否则,有些可能存在多个数字,同时有些位置没有数字。
思路:
栗子:
以 n u m s = 2 , 3 , 1 , 0 , 2 , 4 , , 3 nums = {2, 3, 1, 0, 2, 4, ,3} nums=2,3,1,0,2,4,,3 为例,初始化 i = 0。
i = 1
i = 4
2
class Solution {
public int findRepeatNumber(int[] nums) {
for (int i = 0; i < nums.length; i++) {
// 若当前位置元素和下标不相等,则交换之(相当于把萝卜放到正确的坑中)
while (nums[i] != i) {
// 交换前查看一下,若当前元素和要交换的元素相等(两个萝卜一个坑),则说明遇到重复元素。
if (nums[i] == nums[nums[i]]) return nums[i];
swap(nums, i, nums[i]);
}
}
return -1; // 不存在重复的数字
}
public void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
}
题目描述
统计一个数字在排序数组中出现的次数。
提示:
思路:纯暴力,一个一个找
class Solution {
public int search(int[] nums, int target) {
int res = 0;
for (int i = 0; i < nums.length; i++){
if (nums[i] == target) {
res++;
}
}
return res;
}
}
有序数组,一般可以联想到使用二分求解
思路:
排序数组
,所以可以使用 二分查找
。class Solution {
public int search(int[] nums, int target) {
int res = 0;
// 二分查找,找到一个为target的下标
int index = binarySearch(nums, target);
if (index == -1) { // 不存在 target
return 0;
}
System.out.println("index = " + index);
// 向前滑动
int left = index; // target 左端点
while (left > 0 && nums[left] == nums[left - 1]) {
left--;
}
System.out.println("left = " + left);
// 向后滑动
int right = index; // target 右端点
while (right + 1 < nums.length && nums[right] == nums[right + 1]) {
right++;
}
return right - left + 1;
}
int binarySearch(int[] nums, int target) {
int left = 0;
int right = nums.length - 1; // 左闭右闭区间 [left, right]
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target) {
right = mid - 1;
} else if (nums[mid] < target) {
left = mid + 1;
} else {
return mid;
}
}
return -1; // 不存在
}
}
参考 K佬题解
上面一次 二分查找 的最坏时间复杂度为 O ( n ) O(n) O(n),所以可以 通过 2次 二分查找:分别找 n u m s [ i ] = = t a r g e t nums[i] == target nums[i]==target 的 left 和 right。
思路:
right
(nums中最右的target 右边第一个元素下标);left
(nums中最左的target 左边第一个元素下标);right - left - 1
class Solution {
public int search(int[] nums, int target) {
if (nums.length == 0) return 0;
// 搜索右边界(nums中最右的target 右边第一个元素下标)
int i = 0;
int j = nums.length - 1; // 左闭右闭区间
while (i <= j) {
int mid = i + (j - i) / 2;
if (nums[mid] <= target) { // 相等,说明右边界(最右的target右边第一个元素下标)一定在mid右边
i = mid + 1;
} else {
j = mid - 1;
}
}
int right = i; // 右边界为退出时候的i
System.out.println("right = " + right);
// 搜索左边界(nums中最左的target 左边第一个元素下标)
i = 0;
j = nums.length - 1;
while (i <= j) {
int mid = i + (j - i) / 2;
if (nums[mid] < target) {
i = mid + 1;
} else { // // 相等,说明左边界(最左的target左边第一个元素下标)一定在mid左边
j = mid - 1;
}
}
int left = j;
System.out.println("left = " + left);
return right - left - 1;
}
}
本题中数组也是有序的,所以也可以采用 “两次二分查找”,分别查找 左边界、右边界
class Solution {
public int[] searchRange(int[] nums, int target) {
if (nums.length == 0) {
return new int[] {-1, -1};
}
int i = 0;
int j = nums.length - 1;
// 找target右边界
while (i <= j) {
int mid = i + (j - i) / 2;
if (nums[mid] <= target) { // 相等,说明右边界在mid右边
i = mid + 1;
} else {
j = mid - 1;
}
}
int right = j;
System.out.println("right = " + right);
// nums中不存在target
if (right == -1 || nums[right] != target) {
return new int[] {-1, -1};
}
// 找target左边界
i = 0;
j = nums.length - 1;
while (i <= j) {
int mid = i + (j - i) / 2;
if (nums[mid] < target) {
i = mid + 1;
} else { // 相等,说明左边界在mid左边
j = mid - 1;
}
}
int left = i;
System.out.println("left = " + left);
System.out.println("right = " + right);
return new int[] {left, right};
}
}
题目描述
一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。
限制:
思路:
for
时,说明“缺失元素为 n n n”,返回“数组长度 n n n即可”class Solution {
public int missingNumber(int[] nums) {
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
if (i != nums[i]) return i;
}
return nums.length; // 数组中没有n
}
}
思路:使用哈希表(范围不大,这里用数组就 ok)标记 n u m s nums nums 每个元素出现的情况,最后再一遍扫描 arr 中为 0 的即为 缺失元素。
class Solution {
public int missingNumber(int[] nums) {
int[] arr = new int[nums.length + 1]; // 这里是 n + 1,而不是 n
for (int i = 0; i < nums.length; i++) {
arr[nums[i]]++;
}
for (int i = 0; i < arr.length; i++) {
if (arr[i] == 0) {
return i;
}
}
return -1; // 无
}
}
参考 K佬题解
Note:题目中说了 给定的数组为 排序数组
,有序数组常会用到 二分法
或 双指针
。
本题采用 二分法 是一种更优的解法…
思路:
class Solution {
public int missingNumber(int[] nums) {
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == mid) { // 仍然在左子数组中,缺失数字(即 右子数组的第一元素对应下标)在 [mid + 1, len - 1] 中
left = mid + 1;
} else { // 在右子数组中了,但是需要找右子数组的第一个元素,缺失数字在 [left, mid - 1] 中
right = mid - 1;
}
}
return left; // 右子数组 第一个元素对应的下标
}
}