一、数组的遍历
1、最大连续1的个数(简单)
自己的想法
方法一:遍历数组,计算每个区间 1 的个数,求出最大的即可。
- 执行用时:2 ms, 在所有 Java 提交中击败了94.09%的用户
- 内存消耗:40.3 MB, 在所有 Java 提交中击败了68.99%的用户
class Solution {
public int findMaxConsecutiveOnes(int[] nums) {
int max = 0;
int tmp = 0;
for (int i : nums) {
if (i == 1) {
tmp++;
} else {
max = Math.max(max, tmp);
tmp = 0;
}
}
return Math.max(max, tmp);
}
}
看题解后
方法二:通过每一段 1 的起始和结束下标,计算 1 的个数。
- 执行用时:1 ms, 在所有 Java 提交中击败了100.00%的用户
- 内存消耗:40.3 MB, 在所有 Java 提交中击败了63.84%的用户
class Solution {
public int findMaxConsecutiveOnes(int[] nums) {
int left = 0;
int right = 0;
int max = 0;
for (; right < nums.length; right++) {
if (nums[right] == 0) {
max = Math.max(max, right - left);
left = right + 1;
}
}
return Math.max(max, right - left);
}
}
2、提莫攻击(中等)
自己的想法
方法一:每次中毒后,直接加上当前中毒的时间;下次中毒后,判断上次中毒是否结束,从而进行相应的累加。
- 执行用时:2 ms, 在所有 Java 提交中击败了98.73%的用户
- 内存消耗:40.2 MB, 在所有 Java 提交中击败了90.08%的用户
class Solution {
public int findPoisonedDuration(int[] timeSeries, int duration) {
// 中毒时间
int a = 0;
// 当前的中毒结束时间
int b = 0;
for (int i : timeSeries) {
if (i >= b) {
// 如果之前的中毒已经结束,则直接累加
a += duration;
} else {
// 如果直接的中毒未结束,则只累加续上的时间
a += duration - (b - i);
}
// 计算当前中毒的结束时间
b = i + duration;
}
return a;
}
}
看题解后
方法二:使用下次中毒的起始时间减去当前中毒起始时间得到 x,如果 x < 中毒持续时间,则表示上一次中毒没结束下次中毒就开始了,所以只需要累加上 x 即可;最后需要再加一个最后一次中毒的中毒持续时间。
- 执行用时:2 ms, 在所有 Java 提交中击败了98.73%的用户
- 内存消耗:39.9 MB, 在所有 Java 提交中击败了96.55%的用户
class Solution {
public int findPoisonedDuration(int[] timeSeries, int duration) {
int n = timeSeries.length;
// 如果没有攻击直接结束
if (n == 0) return 0;
int total = 0;
for(int i = 0; i < n - 1; i++)
// 每次只加最短的持续时间
total += Math.min(timeSeries[i + 1] - timeSeries[i], duration);
return total + duration;
}
}
3、第三大的数(简单)
自己的想法
方法一:遍历数组依次求第一、第二、第三大的值,用 Integer.MIN_VALUE - 1
表示当前数有没有被赋值,最后判断 a3 有没有被赋值,从而返回相应的值。(这道题我竟然写了一个小时,掉类型转换的坑里了)
- 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
- 内存消耗:38.4 MB, 在所有 Java 提交中击败了62.66%的用户
class Solution {
public int thirdMax(int[] nums) {
long a1 = (long) Integer.MIN_VALUE - 1;
long a2 = (long) Integer.MIN_VALUE - 1;
long a3 = (long) Integer.MIN_VALUE - 1;
for (int num : nums) {
if (num > a3 && num != a2 && num != a1) {
if (num > a2) {
if (num > a1) {
a3 = a2;
a2 = a1;
a1 = num;
} else {
a3 = a2;
a2 = num;
}
} else {
a3 = num;
}
}
}
// 如果a3没变,则返回a1
return (int) (a3 != (long) Integer.MIN_VALUE - 1 ? a3 : a1);
}
}
看题解后
方法二:内存优化,不使用 long。该方法和上个方法的区别在于,它用一个变量记录 Integer.MIN_VALUE
有没有出现,从而判断 a3 有没有被赋值。
- 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
- 内存消耗:38.2 MB, 在所有 Java 提交中击败了85.76%的用户
class Solution {
public int thirdMax(int[] nums) {
int a1, a2, a3;
boolean containMin = false;
a1 = a2 = a3 = Integer.MIN_VALUE;
for (int num : nums) {
if (num == Integer.MIN_VALUE) {
containMin = true;
}
if (num > a3 && num != a2) {
if (num != a1) {
if (num > a2) {
if (num > a1) {
a3 = a2;
a2 = a1;
a1 = num;
} else {
a3 = a2;
a2 = num;
}
} else {
a3 = num;
}
}
}
}
if (containMin) {
// 最小值出现,且 t2 == t3 ,则表示:第三大的值没有出现
return a2 == a3 ? a1 : a3;
} else {
// 最小值没出现,如果 t3 还未初值,则表示:第三大的值没有出现
return a3 == Integer.MIN_VALUE ? a1 : a3;
}
}
}
4、三个数的最大乘积(简单)
自己的想法
方法一:因为数组中有负数,所以需要求出三个最大的数和两个最小的数(一次遍历),然后求出三个数最大的乘积。
- 执行用时:2 ms, 在所有 Java 提交中击败了99.50%的用户
- 内存消耗:39.7 MB, 在所有 Java 提交中击败了88.08%的用户
class Solution {
public int maximumProduct(int[] nums) {
// 求出三个最大
int a1, a2, a3;
a1 = a2 = a3 = -1001;
// 求出二个最小
int b1, b2;
b1 = b2 = 1001;
for (int i = 0; i < nums.length; i++) {
if (nums[i] < b2) {
if (nums[i] < b1) {
b2 = b1;
b1 = nums[i];
} else {
b2 = nums[i];
}
}
if (nums[i] > a3) {
if (nums[i] > a2) {
if (nums[i] > a1) {
a3 = a2;
a2 = a1;
a1 = nums[i];
} else {
a3 = a2;
a2 = nums[i];
}
} else {
a3 = nums[i];
}
}
}
return Math.max(a1 * a2 * a3, a1 * b1 * b2);
}
}
方法二:利用排序得出三个最大的数和两个最小的数。
- 执行用时:12 ms, 在所有 Java 提交中击败了69.41%的用户
- 内存消耗:39.9 MB, 在所有 Java 提交中击败了71.20%的用户
public class Solution {
public int maximumProduct(int[] nums) {
Arrays.sort(nums);
return Math.max(nums[0] * nums[1] * nums[nums.length - 1], nums[nums.length - 1] * nums[nums.length - 2] * nums[nums.length - 3]);
}
}
5、早餐组合(简单)
自己的想法
方法一:排序+二分查找
- 执行用时:160 ms, 在所有 Java 提交中击败了5.47%的用户
- 内存消耗:58.2 MB, 在所有 Java 提交中击败了35.40%的用户
class Solution {
public int breakfastNumber(int[] staple, int[] drinks, int x) {
Arrays.sort(drinks);
int sum = 0;
for (int i : staple) {
sum = (sum + getRight(drinks,x - i)) % 1000000007;
}
return sum;
}
private int getRight(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left < right) {
int mid = left + (right - left + 1) / 2;
// 注意这样写,可以从右边收缩待搜索区间的范围,进而找到最后一次出现的位置
if (nums[mid] > target) {
// mid 以及 mid 右边都不是,下一轮搜索区间在 [left, mid - 1]
right = mid - 1;
} else {
left = mid;
}
}
// 需要对 0 特殊处理
if (left == 0 && nums[0] > target) {
return 0;
}
return left + 1;
}
}
看题解后
方法二:排序+双指针
- 执行用时:88 ms, 在所有 Java 提交中击败了37.46%的用户
- 内存消耗:57.9 MB, 在所有 Java 提交中击败了77.24%的用户
class Solution {
public int breakfastNumber(int[] staple, int[] drinks, int x) {
Arrays.sort(staple);
Arrays.sort(drinks);
int sum = 0, left = 0, right = drinks.length - 1;
while (left < staple.length && right >= 0) {
// 如果钱不够,则减少饮料的价格
if (staple[left] + drinks[right] > x) {
right--;
} else {
// 计算当前主食可以匹配的饮料个数
sum = (sum + right + 1) % 1000000007;
// 换下一种主食
left++;
}
}
return sum;
}
}
方法三:计数排序+前缀和
- 执行用时:6 ms, 在所有 Java 提交中击败了97.58%的用户
- 内存消耗:48.8 MB, 在所有 Java 提交中击败了92.47%的用户
class Solution {
public int breakfastNumber(int[] staple, int[] drinks, int x) {
int count = 0;
// 对 staple 进行计数排序
int[] array = new int[x + 1];
for (int i : staple) {
if (i <= x) {
array[i]++;
}
}
// 计算前缀和,得到 staple 中价格低于等于 i 的方案数量
for (int i = 1; i < x + 1; i++) {
array[i] += array[i - 1];
}
// 根据前缀和,返回 array 中价格低于等于 x-i 可行的方案数量
for (int i : drinks) {
if (i <= x) {
count += array[x - i];
//减少取余次数小技巧
if (count >= 1000000007) {
count -= 1000000007;
}
// count %= 1000000007;
}
}
return count;
}
}
6、采购方案(简单)
自己的想法
方法一:排序+二分查找
- 执行用时:110 ms, 在所有 Java 提交中击败了100.00%的用户
- 内存消耗:48 MB, 在所有 Java 提交中击败了100.00%的用户
class Solution {
public int purchasePlans(int[] nums, int target) {
Arrays.sort(nums);
long count = 0;
for (int i = 0; i < nums.length; i++) {
int index = getRight(nums, i, target - nums[i]);
count += (index - i);
}
return (int) (count % 1000000007);
}
private int getRight(int[] nums, int i, int target) {
int left = i, right = nums.length - 1;
while (left < right) {
int mid = left + (right - left + 1) / 2;
// 注意这样写,可以从右边收缩待搜索区间的范围,进而找到最后一次出现的位置
if (nums[mid] > target) {
// mid 以及 mid 右边都不是,下一轮搜索区间在 [left, mid - 1]
right = mid - 1;
} else {
left = mid;
}
}
return left;
}
}
方法二:排序+双指针
- 执行用时:38 ms, 在所有 Java 提交中击败了100.00%的用户
- 内存消耗:48 MB, 在所有 Java 提交中击败了100.00%的用户
class Solution {
public int purchasePlans(int[] nums, int target) {
int mod = 1_000_000_007;
int result = 0;
// 排序
Arrays.sort(nums);
// 双指针,left 从前往后找,right 从后往前
int left = 0, right = nums.length - 1;
while (left < right) {
// 如果当前左右之和大于了目标值,说明偏大了,就把右指针往左移动
if (nums[left] + nums[right] > target) {
right--;
} else {
// 否则的话,说明找到了合适的,需要把两者中间的元素个数都累加起来
result += right - left;
result %= mod;
// 然后再向右移动左指针
left++;
}
}
return result;
}
}
方法三:计数排序+前缀和
- 执行用时:9 ms, 在所有 Java 提交中击败了100.00%的用户
- 内存消耗:48.3 MB, 在所有 Java 提交中击败了100.00%的用户
class Solution {
public int purchasePlans(int[] nums, int target) {
int[] price = new int[target];
// 计数排序
for (int i : nums) {
if (i < target) {
price[i]++;
}
}
// 前缀和
for (int i = 1; i < target; i++) {
price[i] += price[i - 1];
}
long count = 0;
for (int i : nums) {
if (i < target) {
count += price[target - i];
// 排除统计到自身的情况
if (2 * i <= target) {
count--;
}
}
}
// 排除元素之间重复统计的数量
return (int) (count / 2 % 1000000007);
}
}
二、统计数组中的元素
1、错误的集合(简单)
自己的想法
方法一:计数排序
使用计数排序的思想,创建一个等长的数组,用来记录原数组中的所有数出现的次数。
- 执行用时:1 ms, 在所有 Java 提交中击败了100.00%的用户
- 内存消耗:40.4 MB, 在所有 Java 提交中击败了25.71%的用户
public class Solution {
public int[] findErrorNums(int[] nums) {
// 0 存储重复出现的数,1 存储丢失的数
int[] result = new int[2];
int[] array = new int[nums.length + 1];
for (int i : nums) {
// 如果不为 0,则表示该数已经出现过,进行记录
if (array[i] != 0) {
result[0] = i;
}
array[i] = 1;
}
for (int i = 1; i < array.length; i++) {
// 如果为 0 则表示该数没有出现过,进行记录
if (array[i] == 0) {
result[1] = i;
break;
}
}
return result;
}
}
看题解后
方法二:原地修改(方法一的空间优化)
方法一创建了新的数组,如果我们可以在原数组上对值进行标记,就可以不使用额外空间。我们用 nums[nums[i]-1]
来判断 nums[i]
是否出现过(减1是为了防止下标越界,数组下标从0开始,而数值从1开始),如果 nums[i]
第一次出现则将 nums[nums[i]-1] * -1
,使用这样的方法就可以实现在原数组上对值进行标记。(注意下标越界的问题)
- 执行用时:3 ms, 在所有 Java 提交中击败了70.43%的用户
- 内存消耗:40.1 MB, 在所有 Java 提交中击败了48.54%的用户
public class Solution {
public int[] findErrorNums(int[] nums) {
int[] result = new int[2];
for (int i = 0; i < nums.length; i++) {
// 数组中的数可能是负数,需要其绝对值
int tmp = Math.abs(nums[i]) - 1;
if (nums[tmp] < 0) {
result[0] = tmp + 1;
} else {
nums[tmp] = -nums[tmp];
}
}
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) {
result[1] = i + 1;
break;
}
}
return result;
}
}
方法三:位运算
首先需要知道一个概念,一个数和它本身进行异或运算结果为 0;对数组中的所有数和 1~nums.length 的数进行异或运算,就能得到丢失的数和重复的数的异或结果(xor);然后求 xor 的最右比特位,(1001 0100) 的最右比特位就是 (0000 0100),使用公式 int right = xor & (xor ^ (xor - 1))
即可。
因为 xor 是丢失的数和重复的数的异或结果,而异或运算是不同位为 1 相同位为 0,所以根据最右比特位可以将数组中的数和 1~nums.length 的数划分为两类,分别进行异或运算即可得到丢失的数和重复的数。最后再遍历原数组判断哪个是重复的数哪个是丢失的数即可。
- 执行用时:2 ms, 在所有 Java 提交中击败了92.45%的用户
- 内存消耗:40.1 MB, 在所有 Java 提交中击败了50.02%的用户
public class Solution {
public int[] findErrorNums(int[] nums) {
// 首先将数组中的所有数和1~nums.length的数进行异或运算
int xor = 0;
for (int i = 0; i < nums.length; i++) {
xor ^= i;
xor ^= nums[i];
}
// 补上最后一个数,得到的 xor 是丢失的数和重复的数的异或结果
xor ^= nums.length;
int a = 0;
int b = 0;
// 求 xor 的最右比特位
int right = xor & (xor ^ (xor - 1));
// 求最右比特位的作用:异或运算是不同为 1 相同为 0,
// 所以根据该比特位即可区分丢失的数和重复的数,分别进行异或运算即可得到丢失的数和重复的数
for (int i = 0; i < nums.length; i++) {
if ((nums[i] & right) == 0) {
a ^= nums[i];
} else {
b ^= nums[i];
}
if ((i & right) == 0) {
a ^= i;
} else {
b ^= i;
}
}
// 补上最后一个数
if ((nums.length & right) == 0) {
a ^= nums.length;
} else {
b ^= nums.length;
}
for (int i : nums) {
// 如果存在则 a 为重复的数
if (i == a) {
return new int[]{a, b};
}
}
return new int[]{b, a};
}
}
其他方法:使用排序和 Map 的方法比较简单就不再进行编码了。
2、数组的度(简单)
自己的想法
方法一:自定义数据结构
题目要求找到相同大小的度的最短连续子数组,也是查找一个出现次数最多且所在的子数组最短。所在子数组最短,我们可以使用(该数出现的起始下标-该数出现的结束下标+1)得到。所以我们可以定义一个数据结构,来存储每个数出现的次数以及起始和结束下标。
定义一个 HashMap 集合,key 表示数组中指定的值,value 为自定义的数据结构。然后遍历数组将每个数添加到集合中。最后找到出现次数最多且(起始下标-结束下标)最小的值,起始下标-结束下标+1 就是最终答案。
- 执行用时:17 ms, 在所有 Java 提交中击败了88.76%的用户
- 内存消耗:42.8 MB, 在所有 Java 提交中击败了38.15%的用户
class Solution {
public int findShortestSubArray(int[] nums) {
// 键表示指定值
Map<Integer, Degree> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
// 存在则加次数更新结束下标,否则则添加
if (map.containsKey(nums[i])) {
Degree d = map.get(nums[i]);
d.end = i;
d.count++;
map.put(nums[i], d);
} else {
map.put(nums[i], new Degree(1, i, i));
}
}
Degree tmp = Collections.max(map.values());
return tmp.end - tmp.start + 1;
}
private class Degree implements Comparable {
// 当前数出现的次数
int count;
// 起始下标和结束下标
int start;
int end;
public Degree(int count, int start, int end) {
this.count = count;
this.start = start;
this.end = end;
}
@Override
public int compareTo(Object o) {
Degree tmp = (Degree) o;
if (count == tmp.count) {
// 如果度相同,end-start 越小越大
//return (tmp.end-tmp.start) - (end-start) ;
return tmp.end - tmp.start - end + start;
}
return count - tmp.count;
}
}
}
看题解后
方法二:计数排序
题目上说数据范围为 [0 ~ 49,999] 的整数,所以可以使用计数排序的思想,将每个出现的数记录在指定 count 数组中。在初始化 count 数组时,可以顺便统计一下数组的度。如果数组的度为 1,则结果必为 1,直接返回 1 即可。如果数组度不为 1,则遍历 count 数组,如果数组的度和 count 数组中记录值相同,则表示该数可能为结果,查找该数在原数组的起始和结束下标,求出其子数组的长度。由于该数可能为多个,则求出最短子数组返回即可。
- 执行用时:5 ms, 在所有 Java 提交中击败了98.69%的用户
- 内存消耗:40.1 MB, 在所有 Java 提交中击败了99.11%的用户
class Solution {
public int findShortestSubArray(int[] nums) {
// 使用计数排序的思想
int[] count = new int[50000];
// 记录出现最高的次数是多少次数
int degree = 0;
for (int i : nums) {
degree = Math.max(degree, ++count[i]);
}
// 如果数组的度为 1,则结果必为 1
if (degree == 1) {
return 1;
}
int result = nums.length;
for (int i = 0; i < count.length; i++) {
if (degree != count[i]) {
continue;
}
// 求该数出现的起始和结束的下标
int start = 0, end = nums.length - 1;
while (start < end && i != nums[start]) {
start++;
}
while (start < end && i != nums[end]) {
end--;
}
result = Math.min(result, end - start + 1);
}
return result;
}
}
方法三:计数排序(优化)
在方法二中直接根据题目的数据,定义一个长度为 50,000 的数组,其实数组中的所有数并没有都利用。可以通过查找数组中的最大值和最小值,从而定义一个长度为 Max-Min+1 的数组。其它的思路不变...
- 执行用时:2 ms, 在所有 Java 提交中击败了100.00%的用户
- 内存消耗:43.5 MB, 在所有 Java 提交中击败了19.20%的用户
class Solution {
public int findShortestSubArray(int[] nums) {
// 找到数组中的最小数和最大数
int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
for (int i : nums) {
max = Math.max(max, i);
min = Math.min(min, i);
}
// 使用计数排序的思想
int[] count = new int[max - min + 1];
// 记录出现最高的次数是多少次数
int degree = 0;
for (int i : nums) {
degree = Math.max(degree, ++count[i - min]);
}
// 如果数组的度为 1,则结果必为 1
if (degree == 1) {
return 1;
}
int result = nums.length;
for (int i = 0; i < count.length; i++) {
if (degree != count[i]) {
continue;
}
// 还原原数的值
int tmp = min + i;
// 求该数出现的起始和结束的下标
int start = 0, end = nums.length - 1;
while (start < end && tmp != nums[start]) {
start++;
}
while (start < end && tmp != nums[end]) {
end--;
}
result = Math.min(result, end - start + 1);
}
return result;
}
}
3、找到所有数组中消失的数字(简单)
自己的想法
方法一:原地修改
不使用额外空间,在原数组上对值进行标记。我们用 nums[nums[i]-1]
来判断 nums[i]
是否出现过(减1是为了防止下标越界,数组下标从0开始,而数值从1开始),如果 nums[i]
第一次出现则将 nums[nums[i]-1] * -1
,使用这样的方法就可以实现在原数组上对值进行标记。(注意下标越界的问题)
- 执行用时:6 ms, 在所有 Java 提交中击败了89.18%的用户
- 内存消耗:47.5 MB, 在所有 Java 提交中击败了45.44%的用户
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
int tmp = Math.abs(nums[i]) - 1;
if (nums[tmp] > 0) {
nums[tmp] = -nums[tmp];
}
}
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) {
list.add(i + 1);
}
}
return list;
}
}
方法二:计数排序
使用计数排序的思想进行求解。违背题意,使用了额外存储空间,但是满足了我追求 100% 的强迫症。
- 执行用时:3 ms, 在所有 Java 提交中击败了100.00%的用户
- 内存消耗:47.6 MB, 在所有 Java 提交中击败了35.13%的用户
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
boolean[] exist = new boolean[nums.length + 1];
for (int i : nums) {
exist[i] = true;
}
List<Integer> list = new ArrayList<>();
for (int i = 1; i <= nums.length; i++) {
if (!exist[i]) {
list.add(i);
}
}
return list;
}
}
4、数组中重复的数据(中等)
自己的想法
方法一:原地修改
不使用额外空间,在原数组上对值进行标记。我们用 nums[nums[i]-1]
来判断 nums[i]
是否出现过(减1是为了防止下标越界,数组下标从0开始,而数值从1开始),如果 nums[i]
第一次出现则将 nums[nums[i]-1] * -1
,使用这样的方法就可以实现在原数组上对值进行标记。(注意下标越界的问题)
- 执行用时:6 ms, 在所有 Java 提交中击败了92.28%的用户
- 内存消耗:47.3 MB, 在所有 Java 提交中击败了76.31%的用户
class Solution {
public List<Integer> findDuplicates(int[] nums) {
List<Integer> list = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
int tmp = Math.abs(nums[i]) - 1;
// 如果 nums[tmp] < 0 则表示该数已出现过进行记录
if (nums[tmp] > 0) {
nums[tmp] = -nums[tmp];
} else {
list.add(tmp + 1);
}
}
return list;
}
}
方法二:计数排序
使用计数排序的思想进行求解,创建一个新的数组来记录数组中的值出现的次数。违背题意,使用了额外存储空间,但是满足了我追求 100% 的强迫症。
- 执行用时:4 ms, 在所有 Java 提交中击败了100.00%的用户
- 内存消耗:47.3 MB, 在所有 Java 提交中击败了76.65%的用户
class Solution {
public List<Integer> findDuplicates(int[] nums) {
int[] count = new int[nums.length + 1];
for (int i : nums) {
count[i]++;
}
List<Integer> list = new ArrayList<>();
for (int i = 1; i <= nums.length; i++) {
if (count[i] == 2) {
list.add(i);
}
}
return list;
}
}
5、缺失的第一个正数(困难)
看题解后
方法一:原地修改
数组长度为 n,所以未出现的最小正整数,最小值是 1 最大值是 n+1。题目说只能使用常数级别的额外空间,所以不能使用计数排序的方法,使用原地修改的方法(两种方法如何应用可以参考上面的题)。但使用原地修改的方法,需要将数组中的数转为负数,从而判断当前下标的数是否出现过。而数组中有负数和零,显然这已经影响到我们的判断,所以我们需要先对数组中的数做预处理,将 <=0
的数改为 n+1
(因为题解的最大值 n+1
所以这并不影响题解)。接着使用原地修改的方法进行操作即可,注意:不需要对 < 0 和 > n
进行操作。
- 执行用时:1 ms, 在所有 Java 提交中击败了79.13%的用户
- 内存消耗:36 MB, 在所有 Java 提交中击败了91.48%的用户
class Solution {
public int firstMissingPositive(int[] nums) {
for (int i = 0; i < nums.length; i++) {
if (nums[i] <= 0) {
nums[i] = nums.length + 1;
}
}
for (int i = 0; i < nums.length; i++) {
int tmp = Math.abs(nums[i]) - 1;
if (tmp < nums.length && nums[tmp] > 0) {
nums[tmp] = -nums[tmp];
}
}
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) {
return i + 1;
}
}
return nums.length + 1;
}
}
方法二:置换
数组长度为 n,所以未出现的最小正整数,最小值是 1 最大值是 n+1。所以对于一个 0 < nums[i] <= n
的数,我们可以将它移动到它应在的下标 nums[i]-1
。进行这样的移动后原数组:[3, 4, -1, 1, 10]
,移动后为:[1, -1, 3, 4, 10]
,再次遍历数组就可以查找到缺失的数。
- 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
- 内存消耗:36.3 MB, 在所有 Java 提交中击败了59.04%的用户
class Solution {
public static int firstMissingPositive(int[] nums) {
for (int i = 0; i < nums.length; i++) {
// 将当前数交换到它应在的位置。如果当前数已在它应在的位置,则不进行交换 nums[nums[i] - 1] != nums[i]
while (nums[i] > 0 && nums[i] <= nums.length && nums[nums[i] - 1] != nums[i]) {
int tmp = nums[nums[i] - 1];
nums[nums[i] - 1] = nums[i];
nums[i] = tmp;
}
}
for (int i = 0; i < nums.length; i++) {
if (nums[i] != i + 1) {
return i + 1;
}
}
return nums.length + 1;
}
}
6、H 指数(中等)
自己的想法
方法一:排序
这各题用大白话说就是,求一个最大数 n,满足至少有 n 个数大于等于 n。所以我们可以对数组进行升序排序,然后逆序查找最大数 n 即可。
- 执行用时:1 ms, 在所有 Java 提交中击败了84.47%的用户
- 内存消耗:36.1 MB, 在所有 Java 提交中击败了92.40%的用户
class Solution {
public int hIndex(int[] citations) {
Arrays.sort(citations);
int result = 0;
for (int i = citations.length - 1; i >= 0; i--) {
// 因为数组是升序排序,倒序遍历。如果当前数已经小于或等于 n 值,则后面的数肯定不满足条件。
if (citations[i] <= result) {
return result;
}
result++;
}
return result;
}
}
看题解后
方法二:计数排序
该题的最大解为 citations.length
,所以大于 citations.length
的值都可以视为 citations.length
。创建新的数组,对原数组进行计数排序。然后对新数组进行逆序查找满足条件的 n 即可。
- 执行用时:0 ms, 在所有 Java 提交中击败了100.00%的用户
- 内存消耗:36.2 MB, 在所有 Java 提交中击败了83.99%的用户
class Solution {
public int hIndex(int[] citations) {
int[] array = new int[citations.length + 1];
for (int i : citations) {
if (i > citations.length) {
array[citations.length]++;
} else {
array[i]++;
}
}
int count = 0;
int i = citations.length;
while (i > 0) {
// 统计 >=i 数的个数
count += array[i];
// 如果有 count 个 >=i 的数,则直接返回
if (i <= count) {
return i;
}
i--;
}
return i;
}
}
代码简化:
class Solution {
public int hIndex(int[] citations) {
int n = citations.length;
int[] papers = new int[n + 1];
// 计数
for (int c : citations){
papers[Math.min(n, c)]++;
}
// 找出最大的 i
int i = n;
for (int count = papers[n]; i > count; count += papers[i]){
i--;
}
return i;
}
}
标题:数组的遍历、统计数组中的元素——LeetCode
作者:Yi-Xing
地址:http://47.94.239.232/articles/2020/12/16/1608127615667.html
博客中若有不恰当的地方,请您一定要告诉我。前路崎岖,望我们可以互相帮助,并肩前行!