颜色分类
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:输入:nums = [2,0,1]
输出:[0,1,2]
提示:
n == nums.length
1 <= n <= 300
nums[i] 为 0、1 或 2
进阶:
你能想出一个仅使用常数空间的一趟扫描算法吗?
作者:LeetCode
链接:https://leetcode.cn/leetbook/read/all-about-array/x9wv2h/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
他强任他强,遇到库还得趴地上
class Solution {
public void sortColors(int[] nums) {
Arrays.sort(nums);
}
}
时间复杂度:O(nlogn)//java当中Arrays.sort()采用的是归并排序
空间复杂度:O(n)
仔细观察,我们可以发现,数组中只有三种元素0,1,2,那么只需排序好两种元素,剩下一种自然就好了。那么先排序哪两种元素呢?在实现过程可以发现先排序0,2更加容易,因为0,2分别在首尾,剩余中间的就全是1。
方法:
1.设置三个指针i,j,k。i指向新数组第一个元素,j指向最后一个元素,k遍历原数组。
2.当nums[k]==0,则赋值给新数组的首部;当nums[k] == 2,则赋值给新数组的尾部;
3.当k遍历结束,新数组剩余的元素填充1
4.最后把新数组的元素赋值给旧数组
void sortColors(int* nums, int numsSize){
int i = 0;
int j = numsSize - 1;
int newNums[numsSize];
for(int k = 0; k < numsSize; k++){
if(nums[k] == 0){
newNums[i++] = 0;
}
if(nums[k] == 2){
newNums[j--] = 2;
}
}
while(i <= j){
newNums[i] = 1;
i++;
}
for(int k = 0; k < numsSize; k++){
nums[k] = newNums[k];
}
}
时间复杂度:O(n)
空间复杂度:O(n)
显然,思路1有些繁琐,需要把元素放到新的数组,然后再放到就数组中。那么可以不可以直接在原数组中操作呢?
我们把思路2的操作,直接在原数组中实现。可以发现当nums[k] == 0或2时,直接赋值给nums[i]或nums[j]时会导致数组中未排好序的元素被覆盖了。这该如何解决呢?可以交换这两个元素是否可行呢?
void sortColors(int* nums, int numsSize){
int newArray[numsSize];
int head = 0;
int tail = numsSize - 1;
int res = 0;
for(int i = 0; i < numsSize; i++){
if(head > tail || i > tail)break;
if(nums[i] == 0){
// newArray[head++] = 0;
res = nums[head];
nums[head] = nums[i];
nums[i] = res;
head++;
}else if(nums[i] == 2){
// newArray[tail--] = 2;
res = nums[tail];
nums[tail] = nums[i];
nums[i] = res;
tail--;
i--;//避免遗漏,当nums[i] = 0 && nums[tail]=2时,交换后i++,会导致0元素遗漏
}else{
continue;
}
}
while(head <= tail){
newArray[head] = 1;
head++;
}
}
实践证明是可行的,但会出现一个问题:
如,当nums[j]中就是2或者当nums[i]中本就是0时,而nums[k]也是0或者2,此时交换会导致原有的排好序的元素被交换出来。此时,当我们交换后,再次检查交换后nums[k]元素是否为2或者0,防止遗漏,那在代码上,由于交换后k会自动下移,此时只需要k--回到上一个位置即可。
数组中的第K个最大元素
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。
示例 1:
输入: [3,2,1,5,6,4], k = 2
输出: 5
示例 2:输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4
提示:
1 <= k <= nums.length <= 105
-104 <= nums[i] <= 104
相关标签C
作者:LeetCode
链接:https://leetcode.cn/leetbook/read/all-about-array/x90rpm/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution {
public int findKthLargest(int[] nums, int k) {
Arrays.sort(nums);
return nums[nums.length - k];
}
}
时间复杂度:O(nlogn)
空间复杂度:O(1)
一般思路,排序 + 返回第k个最大的数字,但本题要求时间复杂度为O(n),可是排序算法最快的也要O(nlogn),看来不能简单的采用"排序+返回第k个“方法。那是否是排序的变种呢?与O(n)最接近的为O(nlogn),那试试是否可以采用快速排序的变种?
回顾快速排序算法的思想:
1.找一个基准数k
2.把小于基准数k的元素放到基准数的左边,大于基准数k的元素放到基准数的右边
采用对撞指针,i指向左边,不断的i++,j指向右边,不断的j--,当nums[i] >k时停止,
当nums[j] < k时停止,然后交换两个元素;
直至i==j时,把nums[i] 与 k交换,
此时,确定了k的位置。
3.对于基准数k左右两边的数组分别重复步骤1,2
4.直至每个部分中只有一个元素,排序结束
我们可以发现快排每次确定一个数k的位置,假设k的索引为index,那么index+ 1不就是第index+1大的元素么?如k在nums[0]上,k为第1大的元素。假设要找第target大的元素,如果tartget < index + 1 ,则说明第target大的元素在k的左侧,即只需要排序k左侧的元素即可;同理,target > index + 1,则只需排序k的右侧数组;若taget == index + 1,则第target大的元素就是k。
同时,可以发现,每次都只需排序一个部分,而不向原快排,每次递归排序两个部分,则此时时间复杂度接近O(n)。
int findKthLargest(int* nums, int numsSize, int k){
return QuickSort(nums,0,numsSize - 1,k);;
}
void swap(int *nums, int i ,int j){
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
int QuickSort(int *nums,int low,int high, int k){
int base = nums[low];
int i = low;
int j = high;
if(i >= j){//如果指针相遇说明只有一个元素,不需要排序
return nums[i];
}
while(i < j){
//快速排序(从小到大),一定要先从右边开始,举例:6,1,1,2,8,9
//如果从左边开始,则nums[i] = 8,nums[j] = 8,然后交换 6 和 8,导致基准数6的位置错误
//右边的指针向左走,遇到小于base就停下
while(nums[j] <= base && i < j){
j--;
}
//左边的指针向右走,遇到大于base的就停下
while(nums[i] >= base && i < j){
i++;
}
//交换两个数
swap(nums,i,j);
}
//确定基准数的位置
swap(nums,low,i);
//由于快速排序(从大到小)每趟都能确定一个数的位置,我们可以根据这个来找出第k大的数字
//若index >= k ,则说明第k大的数字在左边继续排序左边的;若index < K,则第k大的数字在右边继续排序右边的
if(i > k - 1){
return QuickSort(nums,low,i - 1, k);//排序左边
}
if (i < k - 1){
//把分下来的两个数组继续排序
return QuickSort(nums,i+1,high,k);//排序右边
}
return nums[i];
}
时间复杂度:O(n)
空间复杂度:O(1)
合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
提示:
nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-109 <= nums1[i], nums2[j] <= 109
进阶:你可以设计实现一个时间复杂度为 O(m + n) 的算法解决此问题吗?
作者:LeetCode
链接:https://leetcode.cn/leetbook/read/all-about-array/x9lhe7/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
1.i指向nums1,j指向nums2
2.依次判断,nums1[i] 与 nums2[j] 的大小
3.由于数组为非递减,所以,不会出现,后一个比前一个数小的情况
4.选择两者中较小的元素放置到新数组中
5.把新数组的元素放置到nums1中
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
//双指针问题
//i指向nums1,j指向nums2
//依次判断,nums1[i] 与 nums2[j] 的大小
//由于数组为非递减,所以,不会出现,后一个比前一个数小的情况
//选择两者中较小的元素放置
int newNums[nums1Size];
int i = 0;
int j = 0;
int k = 0;
//把两个数组的元素排序放到新数组中,时间复杂度O(m+n)
for(i = 0,j = 0,k = 0; i < m && j < n && k < nums1Size;k++){
if(nums1[i] <= nums2[j]){
newNums[k] = nums1[i++];
}else{
newNums[k] = nums2[j++];
}
}
while(i < m && k < nums1Size){
newNums[k++] = nums1[i++];
}
while(j < n && k < nums1Size){
newNums[k++] = nums2[j++];
}
//把新数组中的元素放到nums1中,时间复杂度O(m+n)
for(i = 0; i < nums1Size; i++){
nums1[i] = newNums[i];
}
}
时间复杂度:O(m + n + n)
空间复杂度:O(m + n)
是否可以直接在原数组中操作?使得空间复杂度为O(1),时间复杂度为O(m+n)?由于nums1数组的尾部有空间,可以直接从尾部开始插入,因此在比较的时候选择大的元素插入尾部。
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n){
//从后向前,找较大的元素放到nums1中
int i = m - 1;
int j = n -1;
int k = nums1Size - 1;
for(;i >= 0 && j >= 0 &&k >= 0; k--){
if(nums1[i] >= nums2[j]){
nums1[k] = nums1[i--];
}else{
nums1[k] = nums2[j--];
}
}
while(i>=0 && k >= 0){
nums1[k--] = nums1[i--];
}
while(j >=0 && k>=0){
nums1[k--] = nums2[j--];
}
}
时间复杂度:O(n)
空间复杂度:O(1)
相比较双指针(一),本次几个双指针题目更加灵活一点,尤其是例题二,需要对已学过的算法的改良。这需要我们对于一些基础算法有较深理解,以及在此基础上做出适当的修改,这对编码能力有较高的要求。