leetcode
[hot] 215. 数组中的第K个最大元素
题目
在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
示例 1:
输入: [3,2,1,5,6,4] 和 k = 2
输出: 5
示例 2:
输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4
题解
堆排序解决问题。示例代码如下所示:
class Solution {
public:
void max_heapify(vector<int>& nums, int heap_size, int i) {
int left = i * 2 + 1;
int right = i * 2 + 2;
int largest = i;
// 不是i,是left,下同
if (left < heap_size && nums[left] > nums[largest]) {
largest = left;
}
if (right < heap_size && nums[right] > nums[largest]) {
largest = right;
}
if (largest != i) {
// 记得交换
swap(nums[i], nums[largest]);
max_heapify(nums, heap_size, largest);
}
}
void build_max_heap(vector<int>& nums, int heap_size) {
for (int i = heap_size / 2; i >= 0; --i) {
max_heapify(nums, heap_size, i);
}
}
int findKthLargest(vector<int>& nums, int k) {
int length = nums.size();
int result = 0;
int heap_size = length;
build_max_heap(nums, heap_size); // 第一个回溯构建最大堆
for (int i = length - 1; i >= length - k + 1; --i) { // 第二个回溯找到目标元素
swap(nums[0], nums[i]);
--heap_size;
// 从0开始重新维护大根堆
max_heapify(nums, heap_size, 0);
}
return nums[0];
}
};
复杂度
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),堆排序的时间复杂度
空间复杂度:
O
(
l
o
g
n
)
O(logn)
O(logn),堆排序的空间复杂度
题解2
快排解决问题,示例代码如下:
// 快排版本
class Solution {
public:
int quickselect(vector<int> &nums, int l, int r, int k) {
if (l == r) {
return nums[k];
}
int partition = nums[l], i = l - 1, j = r + 1;
while (i < j) {
do {
i++;
} while (nums[i] < partition);
do {
j--;
} while (nums[j] > partition);
if (i < j) {
swap(nums[i], nums[j]);
}
}
if (k <= j) {
return quickselect(nums, l, j, k);
} else {
return quickselect(nums, j + 1, r, k);
}
}
int findKthLargest(vector<int> &nums, int k) {
int n = nums.size();
// 相当于是找到数组中第n - k个元素(下标从0开始)
return quickselect(nums, 0, n - 1, n - k);
// 如果找第k小的元素,如下
// return quickselect(nums, 0, n - 1, k - 1);
}
};
复杂度
时间:
O
(
n
)
O(n)
O(n),等比数列求和公式,
n
+
n
/
2
+
n
/
4
+
.
.
.
+
n
/
n
=
n
−
1
/
2
1
−
1
/
2
=
2
n
−
1
n + n/2 + n/4 + ... + n/n = \frac{n-1/2}{1-1/2}=2n-1
n+n/2+n/4+...+n/n=1−1/2n−1/2=2n−1
空间:
O
(
l
o
g
n
)
O(logn)
O(logn)
1122. 数组的相对排序
题目
给你两个数组,arr1 和 arr2,arr2 中的元素各不相同,arr2 中的每个元素都出现在 arr1 中
对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。
示例:
输入:arr1 = [2,3,1,3,2,4,6,7,9,2,19], arr2 = [2,1,4,3,9,6]
输出:[2,2,2,1,4,3,3,9,6,7,19]
提示:
1 <= arr1.length, arr2.length <= 1000
0 <= arr1[i], arr2[i] <= 1000
arr2 中的元素 arr2[i] 各不相同
arr2 中的每个元素 arr2[i] 都出现在 arr1 中
题解
类似计数排序得到最终结果,示例代码如下所示:
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
map<int, int> freq;
for (auto& elem: arr1) {
if (freq.count(elem) > 0) {
++freq[elem];
} else {
freq[elem] = 1;
}
}
vector<int> ans;
for (auto& elem: arr2) {
for (int j = 0; j < freq[elem]; ++j) {
ans.emplace_back(elem);
}
freq[elem] = 0; // 避免arr2的元素被重复放入ans中
}
for (auto& elem: freq) {
auto& key = elem.first;
auto& value = elem.second;
for (int i = 0; i < value; ++i) {
ans.emplace_back(key);
}
}
return ans;
}
};
复杂度
时间复杂度:
O
(
m
l
o
g
m
+
n
)
O(mlogm + n)
O(mlogm+n),m为arr1的长度,n为arr2的长度
空间复杂度:
O
(
m
)
O(m)
O(m)
题解2
把map换为数组,示例代码如下所示:
#include <numeric>
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
int upper = *max_element(arr1.begin(), arr1.end());
vector<int> frequency(upper + 1);
for (int x: arr1) {
++frequency[x];
}
vector<int> ans;
for (int x: arr2) {
for (int i = 0; i < frequency[x]; ++i) {
ans.push_back(x);
}
frequency[x] = 0;
}
for (int x = 0; x <= upper; ++x) {
for (int i = 0; i < frequency[x]; ++i) {
ans.push_back(x);
}
}
return ans;
}
};
复杂度
时间:
O
(
m
+
n
+
u
p
p
e
r
)
O(m + n + upper)
O(m+n+upper),upper为数组元素最大取值
空间:
O
(
u
p
p
e
r
)
O(upper)
O(upper)
剑指offer
40. 最小的k个数
题目
输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。
示例 1:
输入:arr = [3,2,1], k = 2
输出:[1,2] 或者 [2,1]
示例 2:
输入:arr = [0,1,2,1], k = 1
输出:[0]
题解 快速排序思想
示例代码如下所示:
class Solution {
public:
int partition(vector<int>& arr, int start, int end) {
int length = arr.size();
if (start < 0 || end >= length || start > end) {
return -1;
}
int x = arr[end];
int i = start - 1;
for (int j = start; j < end; ++j) {
if (arr[j] <= x) {
swap(arr[j], arr[++i]);
}
}
swap(arr[i + 1], arr[end]);
return i + 1;
}
vector<int> getLeastNumbers(vector<int>& arr, int k) {
int length = arr.size();
int start = 0;
int end = length - 1;
int index = partition(arr, start, end);
while (index != k - 1) {
if (index > k - 1) {
end = index - 1;
index = partition(arr, start, end);
} else {
start = index + 1;
index = partition(arr, start, end);
}
}
vector<int> res;
res.assign(arr.begin(), arr.begin() + k);
return res;
}
};
复杂度
时间复杂度:理想情况下,每次遍历过程中只需要一半元素,因为我们是要找下标为k的元素,第一次切分的时候需要遍历整个数组 (0 ~ n) 找到了下标是 j 的元素,假如 k 比 j 小的话,那么我们下次切分只要遍历数组 (0~k-1)的元素就行啦,反之如果 k 比 j 大的话,那下次切分只要遍历数组 (k+1~n) 的元素就行啦,总之可以看作每次调用 partition 遍历的元素数目都是上一次遍历的 1/2,因此时间复杂度是 n + n / 2 + n / 4 + . . . + n / n = n ∗ ( 2 − 1 / ( 2 n ) ) = n n+ n/2 + n/4 + ... + n/n = n*(2-1/(2^n)) = n n+n/2+n/4+...+n/n=n∗(2−1/(2n))=n, 因此时间复杂度是 O ( n ) O(n) O(n)。
空间复杂度: O ( k ) O(k) O(k),最后存储结果的数组的长度为k。
51. 数组中的逆序对
题目
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
题解
归并排序过程中记录待合并的前面数组比后面数组大的情况,示例代码如下所示:
class Solution {
public:
int reversePairs(vector<int>& nums) {
int length = nums.size();
vector<int> tmp(length);
return m_s(nums, tmp, 0, length - 1);
}
int m_s(vector<int>& nums, vector<int>& tmp, int l, int r) {
if (l >= r) {
return 0;
}
int mid = (l + r) / 2;
int res = m_s(nums, tmp, l, mid) + m_s(nums, tmp, mid + 1, r);
int i = l;
int j = mid + 1;
int pos = l;
while (i <= mid && j <= r) {
// i开始移动时,需要记录的是上一次贡献的逆序对数
if (nums[i] <= nums[j]) {
tmp[pos] = nums[i++];
res += j - (mid + 1);
} else {
tmp[pos] = nums[j++];
}
++pos;
}
for (int k = i; k <= mid; ++k) {
tmp[pos++] = nums[k];
res += j - (mid + 1);
}
for (int k = j; k <= r; ++k) {
tmp[pos++] = nums[k];
}
copy(tmp.begin() + l, tmp.begin() + r + 1, nums.begin() + l);
return res;
}
};
复杂度
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn)归并排序时间复杂度
空间复杂度:
O
(
n
)
O(n)
O(n),辅助空间tmp
45. 把数组排成最小的数
题目
输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
示例 1:
输入: [10,2]
输出: "102"
示例 2:
输入: [3,30,34,5,9]
输出: "3033459"
题解
此题求拼接起来的最小数字,本质上是一个排序问题。设数组 nums 中任意两数字的字符串为 x 和 y,则规定 排序判断规则 为:
若拼接字符串 x + y > y + x,则 x “大于” y ;
反之,若 x + y < y + x,则 x “小于” y;
用上述规则进行排序即可得到结果,示例代码如下所示:
class Solution {
public:
void q_s(vector<string>& nums, int l, int r) {
if (l >= r) {
return;
}
int i = l;
int j = r;
while (i < j) {
// 必须先右后左,这样才能保证while循环跳出后,
// [l, i]之间的元素都是小于等于nums[l],因为后面需要swap
while (i < j && nums[j] + nums[l] >= nums[l] + nums[j]) {
--j;
}
while (i < j && nums[i] + nums[l] <= nums[l] + nums[i]) {
++i;
}
swap(nums[i], nums[j]);
}
swap(nums[i], nums[l]);
q_s(nums, l, i - 1);
q_s(nums, i + 1, r);
}
string minNumber(vector<int>& nums) {
string result;
int length = nums.size();
if (length == 0) {
return result;
}
vector<string> tmp;
for (auto& num: nums) {
tmp.emplace_back(to_string(num));
}
q_s(tmp, 0, length - 1);
for (auto& str: tmp) {
result.append(str);
}
return result;
}
};
复杂度
时间复杂度:
O
(
n
l
o
g
n
)
O(nlogn)
O(nlogn),快排时间复杂度
空间复杂度:
O
(
n
)
O(n)
O(n),字符串数组辅助空间大小
其他
10. 合并排序的数组
题目
给定两个排序后的数组 A 和 B,其中 A 的末端有足够的缓冲空间容纳 B。 编写一个方法,将 B 合并入 A 并排序。初始化 A 和 B 的元素数量分别为 m 和 n。
示例:
输入:
A = [1,2,3,0,0,0], m = 3
B = [2,5,6], n = 3
输出: [1,2,2,3,5,6]
题解
从后向前赋值,这样能够保证不占用额外的空间复杂度。示例代码如下所示:
class Solution {
public:
void merge(vector<int>& A, int m, vector<int>& B, int n) {
int pa = m - 1;
int pb = n - 1;
int tail = m + n - 1;
int cur;
while (pa >= 0 || pb >= 0) {
if (pa == -1) {
cur = B[pb--];
} else if (pb == -1) {
cur = A[pa--];
} else if (A[pa] > B[pb]) {
cur = A[pa--];
} else {
cur = B[pb--];
}
A[tail--] = cur;
}
}
};
复杂度
时间复杂度:
O
(
m
+
n
)
O(m + n)
O(m+n)
空间复杂度:
O
(
1
)
O(1)
O(1)