一、二分法查找
采用二分法查找的前提是数组是有序数组,二分法设计很多边界条件,如果对于边界区间定义不清楚,则很容易写乱。二分法查找的时间复杂度为 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如下:
- 初始化,查找左右区间{start,end}={1,nums.size()-1}; mid=(start+end)/2;重复元素初始化为temp= -1;
- 采用二分法查找,遍历整个数组nums[i],统计小于等于mid的元素个数为count。
- 当count<=mid,则mid一定不可能是重复的数;重复的数应在mid右半部分中,此时start=mid+1;
- 当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表示键和值,注意不用带括号。