算法与数据结构|数组力扣刷题|不简单的数组(leetcode35、88、896)

78 阅读5分钟

leetcode35 搜索插入位置

leetcode.cn/problems/se…

思路:

思路1:暴力枚举

从前往后扫描整个数组,对于每一个数字,判断它是否比目标值大。如果是,则返回当前位置;如果不是,则继续扫描。

优点:思路简单易懂,容易实现。

缺点:时间复杂度为O(n),效率不高,特别是当数组比较大时。而且本题要求时间复杂度为O(log(n)),暴力枚举很显然不符合。

思路2:二分查找

二分查找是这道题最优秀的解法。它采用分治的思想,将每次查找的区间缩小一半。由于数组已经有序,因此在每次比较后可以根据结果确定下一次查找的区间。最终可以找到目标值的位置。

优点:时间复杂度为O(log(n)),效率非常高。

缺点:代码实现可能需要一些技巧,特别是对于边界条件的处理需要注意。

方法:二分法

class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
    int n=nums.size();
    int l=0,r=n-1;
    while(l<=r){
        int mid=(r+l)/2;
        if(nums[mid]<target)
            l=mid+1;
        else r=mid-1;
    }
    return l;
}
};

leetcode88 合并两个有序数组

leetcode.cn/problems/me…

给你两个按非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 应忽略。nums2 的长度为 n 。

思路:

这道题是要求合并两个有序数组,可以有多种思路:

  1. 双指针法:定义两个指针分别指向两个数组的首元素,比较大小后依次合并到一个新的数组中。时间复杂度为O(m+n),空间复杂度为O(m+n)。这种方法代码较简单,但需要额外的空间。
  1. 逆序双指针法:从两个数组的末尾开始进行比较,每次将较大的数放入一个新数组的末尾。时间复杂度为O(m+n),空间复杂度为O(1)。这种方法不需要额外空间,但是需要注意边界情况。
  1. 插入排序法:从第二个数组中每个元素依次插入第一个数组中正确的位置。时间复杂度为O(mn),空间复杂度为O(1)。这种方法易于理解,但时间复杂度较高。
  1. 直接使用合并函数:类似于Python中的merge函数和Java中的System.arraycopy方法,直接将两个数组合并。时间复杂度为O((m+n)log(m+n)),空间复杂度为O(m+n)。这种方法需要额外的空间,但是可以使用Java中的内部方法优化效率。

方法一:直接合并后排序

直接将两个数组合并,然后排序。

这种方法是直接用别人写好的函数,没什么意思,没有技术含量,不讨面试官喜欢。

下面是CPP的实现代码:

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        for(int i=0;i<n;++i){
            nums1[m+i]=nums2[i];
        }
        sort(nums1.begin(),nums1.end());//Java中用的是Arrays.sort(nums1);
    }
};

下面是Java的实现代码:

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        for(int i=0;i<n;i++)
        nums1[m+i]=nums2[i];
        Arrays.sort(nums1);
    }
}

方法二:双指针

这个问题的关键是将B合并到A的仍然要保证有序。因为A是数组不能强行插入,如果从前向后插入,数组A后面的元素会多次移动,代价比较高。此时可以借助一个新数组C来做,先将选择好的放入到C中,最后再返回。

方法一没有利用数组 nums1与 nums2已经被排序的性质。为了利用这一性质,我们可以使用双指针方法。这一方法将两个数组看作队列,每次从两个数组头部取出比较小的数字放到结果中。

下面是CPP的实现代码:

class Solution {
public:
    void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
        int temp[m+n];
        int p1=0,p2=0;//下标指示器
        for(int i=0;i<m+n;++i){
            if(p1>=m){
                temp[i]=nums2[p2++];
            }else if(p2>=n){
                temp[i]=nums1[p1++];
            }else if(nums1[p1]<nums2[p2]){
                temp[i]=nums1[p1++];
            }else{
                temp[i]=nums2[p2++];
            }
        }
        for(int i=0;i<m+n;i++)
            nums1[i]=temp[i];
    }
};

方法三:逆向双指针

虽然方法二解决问题了,但是面试官可能会问你能否再优化一下,或者不申请新数组就能做呢?更专业的问法是:上面算法的空间复杂度为O(n),能否有O(1)的方法?这时候就要用到逆向思维。

下面是Java的实现代码:

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i=m+n-1;
        int l1=m-1;       
        int l2=n-1;
        while(l1>=0&&l2>=0){
            if(nums1[l1]<=nums2[l2])
                nums1[i--]=nums2[l2--];
            else{
                nums1[i--]=nums1[l1--];
            }         
        }
        while (l2 != -1) nums1[i--] = nums2[l2--];
        while (l1 != -1) nums1[i--] = nums1[l1--];
    }
}

leetcode896 单调数列

leetcode.cn/problems/mo…

如果数组是单调递增或单调递减的,那么它是 单调

如果对于所有 i <= j,nums[i] <= nums[j],那么数组 nums 是单调递增的。 如果对于所有 i <= j,nums[i]> = nums[j],那么数组 nums 是单调递减的。

当给定的数组 nums 是单调数组时返回 true,否则返回 false。

思路:

对于这道题,我们可以有几种不同的思路和方法来解决。

一种常见的方法是,我们可以遍历整个数组,检查每个相邻元素之间的关系,如果发现不符合单调递增或单调递减的条件,就返回 false。如果遍历结束后都符合条件,就返回 true。

另外一种方法是,我们可以使用两个标志位来记录数组的递增和递减状态。当发现一个递增的趋势时,将递增标志位设置为 true,并检查是否出现了递减趋势;当发现一个递减的趋势时,将递减标志位设置为 true,并检查是否出现了递增趋势。如果同时出现递增和递减的情况,就返回 false;如果只出现递增或递减的情况,就返回 true。

当然,还有其他更优化的方法,比如使用双指针来检查数组的递增或递减性质。

方法一:一次遍历

巧用函数。

下面是CPP的实现代码:

class Solution {
public:
bool isMonotonic(vector<int>& nums) {
    return  is_sorted(nums.begin(),nums.end())||is_sorted(nums.rbegin(),nums.rend());
}
};

java、c中没有类似的升序函数,golang中有。

下面是Java的实现代码:

public  boolean isMonotonic(int[] nums) {
        return isSorted(nums, true) || isSorted(nums, false);
    }

public  boolean isSorted(int[] nums, boolean increasing) {
          int n = nums.length;
           for (int i = 0; i < n - 1; ++i) {
               if(increasing){
                  if (nums[i] > nums[i + 1]) {
                    return false;
                  }
                }else{
                    if (nums[i] < nums[i + 1]) {
                    return false;
                  } 
                }          
          }
       return true;
  }

方法二:一次遍历

下面是Java的实现代码:

class Solution {
    public boolean isMonotonic(int[] nums) {
        int size=nums.length;
        boolean inc=true,dec=true;
        
        for(int i=0;i<size-1;i++){
            if(nums[i]>nums[i+1])
            inc=false;
            if(nums[i]<nums[i+1])
            dec=false; 
        }
        return inc||dec;
    }
}

下面是C的实现代码:

bool isMonotonic(int* nums, int numsSize) {
    bool inc = true, dec = true;
    for (int i = 0; i < numsSize - 1; ++i) {
        if (nums[i] > nums[i + 1]) {
            inc = false;
        }
        if (nums[i] < nums[i + 1]) {
            dec = false;
        }
    }
    return inc || dec;
}

总结

关于数组的做题,总结一下以下几点:

  1. 理解数组的基本概念:数组是一组存储相同类型数据的集合,可以通过下标快速访问元素。
  2. 熟悉数组的常见操作:如创建数组、插入、删除、查找元素等。
  3. 掌握数组常用的算法:如冒泡排序、快速排序等,可以提高对数组操作的熟练度和效率。
  4. 多实践,多思考:做题时要不断练习,多思考如何使用数组解决问题。
  5. 学会运用其他数据结构:有些问题不适合使用数组来解决,这时需要运用到其他数据结构,如链表、栈、队列等。