代码随想录——数组

一、二分法查找

采用二分法查找的前提是数组是有序数组,二分法设计很多边界条件,如果对于边界区间定义不清楚,则很容易写乱。二分法查找的时间复杂度为 O(log n)

现在统一二分法查找的写法如下:

  • 定义 target 是在一个在左闭右闭的区间里,也就是[left, right]
  • if (nums[middle] > target) right = middle - 1因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1;
  • if (nums[middle] < target) left = middle + 1因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle + 1;

1、二分法查找

class Solution {
public:
    int search(vector<int>& nums, int target) {
    int start=0;//查找区间开始位置
    int end=nums.size()-1;//查找区间结束位置
    int middle=0;//中间点
    while(end>=start)
    {
        middle=(end+start)/2;//获取中间点数值
        if(nums[middle]>target)
        {
            end=middle-1;//取左区间
        }
        else if(nums[middle]<target)
        {
            start=middle+1;//取右区间
        }
        else//找到数则立即返回
        return middle;
    }
    return -1;
    }
};

2、在排序数组中查找元素的第一个和最后一个位置

与直接查找目标数不同的是:

  • 在寻找第一个target的时候,如果nums[i]==target还要保证nums[i-1]小于target,否则区间还得左移。
  • 在寻找最后一个target的时候,如果nums[i]==target还要保证nums[i+1]大于target,否则区间还得右移。
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
    vector<int> result(-1,-1);
    //查找连续相同目标数的第一个
    int start=0;
    int end=nums.size()-1;
    int middel_L=0;
    while(end>=start)
    {
        middel_L=(end+start)/2;
        if(nums[middel_L]>target||nums[middel_L]==target&&middel_L-1>=0&&nums[middel_L-1]==target)
        {
            end=middel_L-1;
        }
        else if(nums[middel_L]<target)
        {
            start=middel_L+1;
        }
        else
        {
            result[0]=middel_L;
            break;
        }  
    }
    //查找连续相同目标数的最后一个
    int start1=0;
    int end1=nums.size()-1;
    int middel_R=0;
    while(end1>=start1)
    {
        middel_R=(end1+start1)/2;
        if(nums[middel_R]>target)
        {
            end1=middel_R-1;
        }
        else if(nums[middel_R]<target||nums[middel_R]==target&&middel_R+1<nums.size()&&nums[middel_R+1]==target)
        {
            start1=middel_R+1;
        }
        else
        {
            result[1]=middel_R;
            break;
        }  
    }
    return result;
    }
};

 3.搜索插入位置

找出二分法中最接近target的数值坐标
如果target>num[mid]说明应该插入该值之后,应返回mid+1;
如果target<num[mid]说明应该取代改值的位置,返回mid,而不是mid-1;

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
    int left=0;
    int right=nums.size()-1;
    int mid;
    while(left<=right)
    {
        mid=left+(right-left)/2;
        if(nums[mid]<target)
        left=mid+1;
        else if(nums[mid]>target)
        right=mid-1;
        else
        return mid;  
    }
    //找出二分法中最接近target的数值坐标
    //如果target>num[mid]说明应该插入该值之后,应返回mid+1
    //如果target<num[mid]说明应该取代改值的位置,返回mid,而不是mid-1
    if(nums[mid]>target)
    return mid;
    return mid+1;
    }
};

二、滑动窗口

欲要实现滑动窗口,主要确定如下三点

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

1、长度最小的子数组

在本题中:
窗口就是满足其和 ≥ s 的长度最小的 连续 子数组。
窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(即该缩小了)。
窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

class Solution {
public:
    int minSubArrayLen(int s, vector<int>& nums) {
        int result = INT32_MAX;
        int sum = 0; // 滑动窗口数值之和
        int i = 0; // 滑动窗口起始位置
        int subLength = 0; // 滑动窗口的长度
        for (int j = 0; j < nums.size(); j++) {
            sum += nums[j];
            // 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
            while (sum >= s) {
                subLength = (j - i + 1); // 取子序列的长度
                result = result < subLength ? result : subLength;//取长度最小
                sum -= nums[i++]; // 这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == INT32_MAX ? 0 : result;
    }
};

三、找出数组中元素出现次数为奇数的元素

1、数组数字出现的次数|

一个数组中有一种数出现了奇数次,其他数都出现了偶数次,怎么找到这个数?
例如,该数组为 a[]={1, 2, 2, 3, 3}
将所有的数全部异或运算,运算结果就是出现了奇数次的数。
一个数组中有两种出现了奇数次,其他数都出现了偶数次,怎么找到这两个数?

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
    //第一步:先通过将数组中所有元素进行异或,将两两相同的元素都除去
    int temp=nums[0];
    for(int i=1;i<nums.size();i++)
    {
        temp^=nums[i];
    }
    //此时temp为剩余的两个不同的数的异或
    //寻找temp中为1的位j,其中两数异或不为0的位即为两数之间差异的位。
    int j=1;
    int c=j&temp;
    while(c==0)
    {
        j=j*2;
        c=j&temp;
    }
    //将原数组划分为两组:一组在j处二进制为1,一组在j处二进制为0;
    vector<int> temp1;
    vector<int> temp2;
    for(int i=0;i<nums.size();i++)
    {
        int c=j&nums[i];
        if(c==0)
        temp1.push_back(nums[i]);
        else
        temp2.push_back(nums[i]);
    }
    //将这两组内部分别异或求结果
    //最终可以分别得到两个被异或的结果元素
    vector<int> result={temp1[0],temp2[0]};
    for(int i=1;i<temp1.size();i++)
        result[0]^=temp1[i];

    for(int i=1;i<temp2.size();i++)
        result[1]^=temp2[i];
    //获得最终所需结果
    result[0]=0^result[0];
    result[1]=0^result[1];
return result;
    }
};

异或运算法则:如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。

2、数组数字出现的次数||

  • 方法一:采用哈希表;
  • 方法二:将数组中的所有元素每个二进制位相加,并将每个元素对应二进制位的1相加之和%3,得到那个单个元素的对应二进制位的值;

四、寻找数组中重复的数

eg:当N+1个元素,元素大小为【0,N】,因此必然有某个元素的重复次数>=2。

当重复的元素值<=i时,数组中小于等于i的元素个数必然>i个;
当重复元素值>i时,数组中小于等于i的元素个数必然<=i;

寻找重复数

以示例1为例,列出每个数组的count与小于等于mid的元素个数cnt如下:

  1. 初始化,查找左右区间{start,end}={1,nums.size()-1};  mid=(start+end)/2;重复元素初始化为temp= -1;
  2. 采用二分法查找,遍历整个数组nums[i],统计小于等于mid的元素个数为count
  3. count<=mid,则mid一定不可能是重复的数重复的数应在mid右半部分中,此时start=mid+1;
  4. count>mid,则mid可能是这个重复的数更新重复元素temp为mid重复的数可能在mid左半部分中,此时end=mid-1
class Solution {
public:  
    int findDuplicate(vector<int>& nums) {
    int start=1;
    int end=nums.size()-1;
    int mid;
    int temp=0;
    while(start<=end)
    {
        int count=0;
        mid=start+(end-start)/2;
        //统计数组中小于等于mid的元素个数
        for(int i=0;i<nums.size();i++)
        {
            if(nums[i]<=mid)
            count++;
        }
        //此时重复元素必在mid右侧
        if(count<=mid)
        {
            start=mid+1;
            continue;
        }
        //此时重复元素可能为mid,也可能在mid左侧
        //因此临时保存可能重复的元素值mid为temp
        else if(count>mid)
        {
            end=mid-1;
            temp=mid;
            continue;
        }
    }
    return temp;
    }
};

快慢指针

快慢指针用于原地修改数组元素时,在决定当前元素是否保留时,应该比较的是fast指针和slow指针的关系,而不是fast指针与fast ± n的关系;

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
    if(nums.size()<2)
    return nums.size();
    int slow=2;
    int fast=2;
    while(fast<nums.size())
    {
        //注意此处,在进行快慢指针的时候,当前数值是否需要保留是与慢指针slow-2比较,而不是与fast-2比较
        if(nums[fast]==nums[slow-2])
        fast++;
        else
        {
            nums[slow]=nums[fast];
            slow++;
            fast++;
        }
    }
    return slow;
    }
};

四、大根堆/小根堆

1.大根堆小根堆的实现主要是借助优先队列priority_queue。push进元素后队列内自动排序,
排序规则由自定义的()重载函数表示。

2.pair<int,int> V 键值对使用V.first和V.second表示键和值,注意不用带括号。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值