数组OJ思路


        一道题解法有很多种,了解了算法的时间复杂度和空间复杂度,我们的解题思路不仅要能解题,还要尽量做到最优。


一、轮转数组

链接:189. 轮转数组 - 力扣(LeetCode)

思路:

  1. 观察输入与输出的结果。
  2. 元素向右轮转 k 个位置,就是将数组末尾 k 个元素与数组前 n-k 个元素调换
  3. 这种调换可以通过三次逆置实现:
  4. 需要注意轮转次数 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);
}


二、移除元素

链接27. 移除元素 - 力扣(LeetCode)

在该题基础上,我们要求时间复杂度为O(n),空间复杂度为O(1)

思路:

  1. 双指针法:定义两个指向下标的变量 dst 和 src。
  2. 原理就是当 src 遇到 val 时,dst 会停下来记住 val 开始的下标,直到 src 遇到非 val 元素时,将其覆盖 dst,达到删除的效果。
  3. 题目要求返回 nums 中与 val 不同的元素的数量,那么当 src 不等于 val 时,dst就会++,因此直接返回 dst 即可。
  4. 题目仅关系返回的前 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)的限制:

思路:

  1. 我们依旧可以采用双指针法:dst、src
  2. dst 用来记录重复项开始的位置,src 进行遍历,遇到与 dst 相等的值则继续遍历,遇到不相等的,我们先让 dst++,走到下一位保证重复项有一项被保留,然后堆 dst 进行覆盖删除。
  3. 因为 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)

思路:

  1. 看到这题时我第一个思路就是归并排序,给出的函数名也是 merge
  2. 因此我没过多思考就将归并排序思路用上了
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);
}
  1. 直接套用归并排序的思路唯一不好就是空间复杂度为 O(n+m)
  2. 其实在这道题中我们可以将空间复杂度优化成 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)

猜你喜欢

转载自blog.csdn.net/x_p96484685433/article/details/143419770
OJ
今日推荐