【刷题之路】LeetCode 1539. 第 k 个缺失的正整数

【刷题之路】LeetCode 1539. 第 k 个缺失的正整数

  • 一、题目描述
  • 二、解题
    • 1、方法1——暴力法
      • 1.1、思路分析
      • 1.2、代码实现
    • 2、方法2——双指针
      • 2.1、思路分析
      • 2.2、代码实现
    • 3、方法3——二分查找
      • 3.1、思路分析
      • 3.2、代码实现

一、题目描述

原题连接: 1539. 第 k 个缺失的正整数
题目描述:
给你一个 严格升序排列 的正整数数组 arr 和一个整数 k 。
请你找到这个数组里第 k 个缺失的正整数。

示例 1:

输入: arr = [2,3,4,7,11], k = 5
输出: 9
解释:缺失的正整数包括 [1,5,6,8,9,10,12,13,…] 。第 5 个缺失的正整数为 9 。

示例 2:

输入: arr = [1,2,3,4], k = 2
输出: 6
解释:缺失的正整数包括 [5,6,7,…] 。第 2 个缺失的正整数为 6 。

提示:
1 <= arr.length <= 1000
1 <= arr[i] <= 1000
1 <= k <= 1000
对于所有 1 <= i < j <= arr.length 的 i 和 j 满足 arr[i] < arr[j]

进阶:
你可以设计一个时间复杂度小于 O(n) 的算法解决此问题吗?

二、解题

1、方法1——暴力法

1.1、思路分析

直接枚举从1到arr[arrsize-1]的所有数字,对于每个数字,都在arr数组中寻找其是否存在
如果存在则跳到下一个数字,如果不存在则先让k–后再跳到下一个数字,当k减到0时,返回对应的数字即可。
若将从1到arr[arrSize - 1]的数字全都遍历完时k还未等于0,则返回arr[arrSize-1] + k。

1.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

int findKthPositive1(int* arr, int arrSize, int k) {
	assert(arr);
	int i = 0;
	int j = 0;
	int max = arr[arrSize - 1];
	for (i = 1; i <= max; i++) {
		for (j = 0; j < arrSize; j++) {
			if (arr[j] == i) {
				break;
			}
		}
		if (j == arrSize) {
			k--;
			if (0 == k) {
				return i;
			}
		}
	}
	return max + k;
}

时间复杂度:O(n^2),n为数组长度。
空间复杂度:O(1),我们只需要用到常数级的额外空间。

2、方法2——双指针

2.1、思路分析

起初我们让两个指针p1和p2分别指向数组arr和数组arr2的起始位置,这里的arr2所存储的是从1到arr[arrSize - 1]的所有数字。
如果arr[p1] == arr2[p2],则让p1和p2都向后移动一位:
【刷题之路】LeetCode 1539. 第 k 个缺失的正整数_第1张图片
如果arr[p1] != arr2[p2],则先让k–,再让p2向后移动一位(因为在这种情况下,只有可能是arr2[p2] < arr[p1[):
【刷题之路】LeetCode 1539. 第 k 个缺失的正整数_第2张图片
当k为0时,直接返回对应的数字即可。
若将从1到arr[arrSize - 1]的数字全都遍历完时k还未等于0,则直接返回arr[arrSize-1] + k即可。

2.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

int findKthPositive2(int* arr, int arrSize, int k) {
	assert(arr);
	int p1 = 0;
	int p2 = 0;
	int max = arr[arrSize - 1];
	while ((p1 < arrSize) && (p2 <= max)) {
		if (arr[p1] == p2) {
			p1++;
			p2++;
		}
		else {
			k--;
			if (0 == k) {
				return p2;
			}
			p2++;
		}
	}
	return max + k;
}

时间复杂度:O(n),n为数组的长度。
空间复杂度:O(1),我们只需要用到常数级的额外空间。

3、方法3——二分查找

3.1、思路分析

由于数组时严格升序的,所以对于每个arr[i],我们都可以唯一确定到第i个元素为止缺少的数字个数为arr[i] - (i + 1) = arr[i] - i - 1。
可能有的朋友还不知道这个结论是怎么得来的,那就由我来给大家推导一下:
我们知道如果下标从1开始,并且数组中并没有缺少任何一个数字,那么arr[i]的值即为从arr[1]开始到arr[i]的元素个数:
【刷题之路】LeetCode 1539. 第 k 个缺失的正整数_第3张图片
那么如果有缺失数字的话,arr[i]的值就一定大于i,俺么此时用arr[i] - i不就恰好等于缺失的数字的个数了吗?
而数组的下标是从0开始的,那我们就只需要将下标加上1即可,所以就得出了结论:arr[i] - (i + 1) = arr[i] - i - 1。

而且随着i的递增,arr[i] - i - 1只有可能增大或者不变,所以如果要把arr[i] - i - 1也当成一个序列的话,这个序列也是有序的。
所以我们就可以使用二分法来查找,目标是找到第一个满足arr[i] - i - 1 >= k的元素。
而如果arr[arrSize - 1] - (arrSize - 1 + 1) < k的话,说明数组arr内丢失的数字还不够k个,此时就可以直接返回arr[arrSize - 1] + (k - (arr[arrSize - 1] - (arrSize - 1 + 1) ) )。
而对于需要返回的那个丢失的第k个数字,我们只需要算出arr[mid - 1] + (k - (arr[mid - 1] - (mid - 1 + 1)))即可。

3.2、代码实现

有了以上思路,那我们写起代码来也就水到渠成了:

int findKthPositive3(int* arr, int arrSize, int k) {
	assert(arr);
	// 特殊情况特殊处理
	if (arr[0] > k) {
		return k;
	}
	if (arr[arrSize - 1] - (arrSize - 1 + 1) < k) {
		return arr[arrSize - 1] + (k - (arr[arrSize - 1] - (arrSize - 1 + 1)));
	}
	int left = 0;
	int right = arrSize - 1;
	int mid = 0;
	while (left < right) {
		mid = left + (right - left) / 2;
		if (arr[mid] - mid - 1 < k) {
			left = mid + 1;
		}
		else {
			right = mid;
		}
	}
	return arr[left - 1] + (k - (arr[left - 1] - (left - 1 + 1)));
}

时间复杂度:O(logn),n为数组长度。
空间复杂度:O(1),我们只需要用到常数级的额外空间。

你可能感兴趣的:(刷题之路——简单篇,leetcode,算法,c语言,开发语言,后端)