leetcode35 搜索插入位置
思路:
思路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 合并两个有序数组
给你两个按非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。请你合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 应忽略。nums2 的长度为 n 。
思路:
这道题是要求合并两个有序数组,可以有多种思路:
- 双指针法:定义两个指针分别指向两个数组的首元素,比较大小后依次合并到一个新的数组中。时间复杂度为O(m+n),空间复杂度为O(m+n)。这种方法代码较简单,但需要额外的空间。
- 逆序双指针法:从两个数组的末尾开始进行比较,每次将较大的数放入一个新数组的末尾。时间复杂度为O(m+n),空间复杂度为O(1)。这种方法不需要额外空间,但是需要注意边界情况。
- 插入排序法:从第二个数组中每个元素依次插入第一个数组中正确的位置。时间复杂度为O(mn),空间复杂度为O(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 单调数列
如果数组是单调递增或单调递减的,那么它是 单调 的。
如果对于所有 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;
}
总结
关于数组的做题,总结一下以下几点:
- 理解数组的基本概念:数组是一组存储相同类型数据的集合,可以通过下标快速访问元素。
- 熟悉数组的常见操作:如创建数组、插入、删除、查找元素等。
- 掌握数组常用的算法:如冒泡排序、快速排序等,可以提高对数组操作的熟练度和效率。
- 多实践,多思考:做题时要不断练习,多思考如何使用数组解决问题。
- 学会运用其他数据结构:有些问题不适合使用数组来解决,这时需要运用到其他数据结构,如链表、栈、队列等。