一道题解法有很多种,了解了算法的时间复杂度和空间复杂度,我们的解题思路不仅要能解题,还要尽量做到最优。
一、轮转数组
思路:
- 观察输入与输出的结果。
- 元素向右轮转 k 个位置,就是将数组末尾 k 个元素与数组前 n-k 个元素调换
- 这种调换可以通过三次逆置实现:
- 需要注意轮转次数 k ,如果大于数组长度相当于重新轮转,对数组长度取模即可。
OJ代码:
void reverse(int* nums, int begin, int end)
{
while (begin < end)
{
int tmp = nums[begin];
nums[begin] = nums[end];
nums[end] = tmp;
begin++;
end--;
}
}
void rotate(int* nums, int numsSize, int k)
{
k = k % numsSize;
reverse(nums, 0, numsSize - k - 1);
reverse(nums, numsSize - k, numsSize - 1);
reverse(nums, 0, numsSize - 1);
}
二、移除元素
在该题基础上,我们要求时间复杂度为O(n),空间复杂度为O(1)
思路:
- 双指针法:定义两个指向下标的变量 dst 和 src。
- 原理就是当 src 遇到 val 时,dst 会停下来记住 val 开始的下标,直到 src 遇到非 val 元素时,将其覆盖 dst,达到删除的效果。
- 题目要求返回
nums
中与val
不同的元素的数量,那么当 src 不等于 val 时,dst就会++,因此直接返回 dst 即可。 - 题目仅关系返回的前 dst 个数据,因此后面有 val 未删除不重要
OJ代码:
int removeElement(int* nums, int numsSize, int val)
{
int dst=0, src=0;
while(src < numsSize)
{
if(nums[src]==val)
{
src++;
}
else
{
nums[dst++]=nums[src++];
}
}
return dst;
}
三、删除有序数组中的重复项
链接:26. 删除有序数组中的重复项 - 力扣(LeetCode)
我们依旧加上时间复杂度O(n)与空间复杂度O(1)的限制:
思路:
- 我们依旧可以采用双指针法:dst、src
- dst 用来记录重复项开始的位置,src 进行遍历,遇到与 dst 相等的值则继续遍历,遇到不相等的,我们先让 dst++,走到下一位保证重复项有一项被保留,然后堆 dst 进行覆盖删除。
- 因为 dst 为下标,因此返回时 +1
OJ代码:
int removeDuplicates(int* nums, int numsSize)
{
int dst=0,src=0;
while(src<numsSize)
{
if(nums[src]==nums[dst])
{
++src;
}
else
{
nums[++dst]=nums[src++];
}
}
return dst+1;
}
另一种写法:count 相当于 dst,i 相当于 src。
int removeDuplicates(int* nums, int numsSize)
{
int count = 0;
for(int i = 1; i < numsSize; ++i)
{
if(nums[i] != nums[count])
nums[++count] = nums[i];
}
++count;
return count;
}
四、合并两个有序数组
链接:88. 合并两个有序数组 - 力扣(LeetCode)
思路:
- 看到这题时我第一个思路就是归并排序,给出的函数名也是 merge
- 因此我没过多思考就将归并排序思路用上了
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
int *tmp = (int*)malloc(sizeof(int)*(m+n));
int begin1 = 0;
int end1 = m-1;
int begin2 = 0;
int end2 = n-1;
int index = 0;
while(begin1<=end1 && begin2<=end2)
{
if(nums1[begin1]<nums2[begin2])
{
tmp[index++]=nums1[begin1++];
}
else
{
tmp[index++]=nums2[begin2++];
}
}
while(begin1<=end1)
{
tmp[index++]=nums1[begin1++];
}
while(begin2<=end2)
{
tmp[index++]=nums2[begin2++];
}
for(int i=0;i<m+n;i++)
{
nums1[i]=tmp[i];
}
free(tmp);
}
- 直接套用归并排序的思路唯一不好就是空间复杂度为 O(n+m)
- 其实在这道题中我们可以将空间复杂度优化成 O(1),即直接在 nums1 数组中进行合并排序,甚至减少参数简化代码
优化后:
void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n)
{
int end1 = m-1;
int end2 = n-1;
int index = m+n-1;
while(end1 >= 0 && end2 >= 0)
{
if(nums1[end1] > nums2[end2])
{
nums1[index--] = nums1[end1--];
}
else
{
nums1[index--] = nums2[end2--];
}
}
// num2中的元素可能没有搬移完,将剩余的元素继续往nums1中搬移
while(end2 >= 0)
{
nums1[index--] = nums2[end2--];
}
// num1中剩余元素没有搬移完 ---不用管了,因为num1中剩余的元素本来就在num1中
}
时间复杂度为 O(m+n)