随机存取,连续,相同数据类型
代码随想录数组理论基础
第一遍代码,递归
class Solution {
public:
int start = 0;
int search(vector<int>& nums, int target) {
int ii = nums.size()/2;
if(nums.size() == 0) {
return -1;
}
while(nums[ii] > target) {//大于还是小于想清楚
vector<int> a;
for(int j = 0; j < ii; j++) {
a.push_back(nums[j]);
}
return search(a, target);
}
while(nums[ii] < target) {
start += ii + 1;
vector<int> a;
for(int j = ii+1; j < nums.size(); j++) {
a.push_back(nums[j]);//a中没有值不能直接a[ii-j-1]
}
return search(a, target);
}
if(nums[ii] == target) {
return start + ii;
}
return -1;
}
};
通过下标对数组操作即可不需要反复递归
class Solution {
public:
int search(vector<int>& nums, int target) {
int left = 0;
int right = nums.size();
int middle = (left+right)/2;//使用下标避免递归传入新数组,通过下标对数组操作;如果left和right数值都很大的话,left+right相加会溢出,建议使用left + ((right - left) / 2
while(left<right)
{
middle = (left+right)/2;
if(nums[middle] == target)
{
return middle;
}
else if(nums[middle]>target)
{
right = middle;
}
else
{
left = middle + 1;
}
}
return -1;
}
};
1、强调数组中无重复元素(重复也可考虑,转化为求第一个/最后一个;例如34),因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的
2、另一个前提是数组是有序数组,这也是使用二分查找的基础条件
这些都是使用二分法的前提条件,当看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了
while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right),复杂度为 O(log n)
第一遍代码,使用二分法解决
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int start = 0;
int end = nums.size() - 1;
int middle = (start + end)/2;
while(start <= end) {
middle = (start + end)/2;
if(nums[middle] < target) {
start = middle + 1;
}
else if(nums[middle] > target) {
end = middle - 1;
}
else {
return middle;
}
}
return start;//使用【2,4】,3去想应插入位置就是start的位置
}
};
也可以使用暴力解决
暴力解题 不一定时间消耗就非常高,关键看实现的方式,就像是二分查找时间消耗不一定就很低,是一样的。
for (int i = 0; i < nums.size(); i++) {
// 分别处理如下三种情况
// 目标值在数组所有元素之前
// 目标值等于数组中某一个元素
// 目标值插入数组中的位置
if (nums[i] >= target) { // 一旦发现大于或者等于target的num[i],那么i就是我们要的结果
return i;
}
}
具体实现:
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int n = nums.size();
int left = 0;
int right = n - 1; // 定义target在左闭右闭的区间里,[left, right]
while (left <= right) { // 当left==right,区间[left, right]依然有效
int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else if (nums[middle] < target) {
left = middle + 1; // target 在右区间,所以[middle + 1, right]
} else { // nums[middle] == target
return middle;
}
}
// 分别处理如下四种情况
// 目标值在数组所有元素之前 [0, -1]
// 目标值等于数组中某一个元素 return middle;
// 目标值插入数组中的位置 [left, right],return right + 1
// 目标值在数组所有元素之后的情况 [left, right], 因为是右闭区间,所以 return right + 1
return right + 1;
}
};
第一次代码,多种提交出现情况后多次加补丁,没有以正确方式有条理地考虑问题
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
vector<int> result;
int start = 0;
int end = nums.size() - 1;//前闭后闭
int middle = start + (end - start)/2;
while(start <= end) {
middle = start + (end - start)/2;
if(nums[middle] > target) {
end = middle - 1;
}
else if(nums[middle] < target) {
start = middle + 1;
}
else {
int i;
int judge = 0;//如果有三个连续的如[3,3,3],target=3只记录一头一尾
for(i = start; i <= end; i++) {
if(nums[i] == target) {
if(judge == 0) {
result.push_back(i);
judge = 1;
}
}
else if(result.size() != 0){//判断nums[i] != target时要已经之前有过等的才跳出
break;//如果不等了要及时跳出,因为下面判断一个时要用到i
}
}
if(result.size() == 1) {
result.push_back(i-1);//补充特殊情况只有一个时,如nums=[1],target=1; nums=[1,3], target=1,注意i加了1了,要减掉
}
return result;
}
}
result.push_back(-1);
result.push_back(-1);
return result;
}
正确的思路:
以左右边界为考虑对象,需要将情况考虑仔细了:
把所有情况都讨论一下。
寻找target在数组里的左右边界,有如下三种情况:
情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
情况二:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}
情况三:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
二分查找,寻找target的右边界(不包括target)
接下来,在去寻找左边界,和右边界了。
采用二分法来去分别寻找左右边界
寻找右边界:
// 如果rightBorder为没有被赋值(即target在数组范围的左边,例如数组[3,3],target为2),为了处理情况一
int getRightBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
while (left <= right) { // 当left==right,区间[left, right]依然有效
int middle = left + ((right - left) / 2);
if (nums[middle] > target) {
right = middle - 1; // target 在左区间,所以[left, middle - 1]
} else { // 当nums[middle] == target的时候,更新left,这样才能得到target的右边界(如果有target的话最后一定过了)
left = middle + 1;//只要小于等于target就往右移,直到left超过right,这样一定在右端,二分法只有不断对等于的往右推才能覆盖有重复的情况
rightBorder = left;
}
}
return rightBorder;
}
左边界同理:
int getLeftBorder(vector<int>& nums, int target) {
int left = 0;
int right = nums.size() - 1;
int leftBorder = -2; // 记录一下leftBorder没有被赋值的情况
while (left <= right) {
int middle = left + ((right - left) / 2);
if (nums[middle] >= target) { // 寻找左边界,nums[middle] == target的时候更新right
right = middle - 1;
leftBorder = right;
} else {
left = middle + 1;
}
}
return leftBorder;
}
对三种情况的处理:
int leftBorder = getLeftBorder(nums, target);
int rightBorder = getRightBorder(nums, target);
// 情况一
if (leftBorder == -2 || rightBorder == -2) return {-1, -1};
// 情况三
if (rightBorder - leftBorder > 1) return {leftBorder + 1, rightBorder - 1};
// 情况二
return {-1, -1};
第一遍代码(使用同向一快一慢两个指针)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int j = 0;
for(int i = 0; i < nums.size(); i++) {//双指针
if(nums[i] != val) {
nums[j++] = nums[i];
}
}
nums.resize(j);
return j;
}
};
另一种写法(左右相向而行的两个指针)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int leftIndex = 0;
int rightIndex = nums.size() - 1;
while (leftIndex <= rightIndex) {
// 找左边等于val的元素,一定要确保leftindex<=rightindex
while (leftIndex <= rightIndex && nums[leftIndex] != val){
++leftIndex;
}
// 找右边不等于val的元素
while (leftIndex <= rightIndex && nums[rightIndex] == val) {
-- rightIndex;
}
// 将右边不等于val的元素覆盖左边等于val的元素
if (leftIndex < rightIndex) {
nums[leftIndex++] = nums[rightIndex--];
}
}
return leftIndex; // leftIndex一定指向了最终数组末尾的下一个元素
}
};
第一遍代码:
class Solution {
public:
bool backspaceCompare(string s, string t) {
int j1 = 0;
for(int i1 = 0; i1 < s.size(); i1++) {//一定要从0开始,从1开始的话c#d#会有问题
if(s[i1] == '#' && j1 == 0) {//对特殊情况进行处理,对空#仍为空
continue;
}
if(s[i1] == '#') {
j1--;
}
else {
s[j1++] = s[i1];
}
}
s.resize(j1);
int j2 = 0;
for(int i2 = 0; i2 < t.size(); i2++) {
if(t[i2] == '#' && j2 == 0) {
continue;
}
if(t[i2] == '#') {
j2--;//目标位置进行回退(最后resize一下相当于删除了)
}
else {
t[j2++] = t[i2];
}
}
t.resize(j2);
return s==t;//compare:若参与比较的两个串值相同,则函数返回 0;若字符串 S 按字典顺序要先于 S2,则返回负值;反之,则返回正值。下面举例说明如何使用 string 类的 compare() 函数。
}
};
为什么不能用compare(compare用法):
若参与比较的两个串值相同,则函数返回 0;若字符串 S 按字典顺序要先于 S2,则返回负值;反之,则返回正值。下面举例说明如何使用 string 类的 compare() 函数。
采用前后双指针(因为在待放入结果的数列里面只有左右两边的数可能是最大的,所以从大到小排序)
class Solution {
public:
vector<int> sortedSquares(vector<int>& nums) {
vector<int> result(nums.size());
int z = nums.size() - 1;
int j;
for(int i = 0, j = nums.size() - 1; i <= j; ){
if(nums[i] * nums[i] >= nums[j] * nums[j]){
result[z--] = nums[i] * nums[i];
i++;
}
else{
result[z--] = nums[j] * nums[j];
j--;
}
}
return result;
}
};
第一遍提交代码:
class Solution {
public:
int mySqrt(int x) {
vector<int> a;
for(int i = 0; i <= x; i++) {
a.push_back(i);
}
int start = 0;
int end = x;
int middle = start + (end - start)/2;
while(start < end) {
middle = start + (end - start)/2;
if(a[middle]*a[middle] > x) {//if的前后与结果是偏大偏小无关,middle的取值才有关,middle位置的值决定了start/end谁动
end = middle - 1;
}
else if(a[middle]*a[middle] < x) {
start = start + 1;
}
else {
return middle;
}
}
if(a[start]*a[start] > x) {//由于无法控制没找到情况下偏大偏小做一步判断
start -= 1;
}
return start;
}
};
出现Memory Limit Exceeded情况
没必要做成一个数组,直接用middle平方比,注意middle平方可以用long long数据类型防止越界。
注意不可以写成(long long)(middle*middle),因为这样会先算middle*middle,int显然可能超范围
对于无法控制二分法查找偏大偏小问题,只记录left侧的答案即可,无论小于等于都动left,因为left对应记录一次答案所以等于条件也要放入其中。
class Solution {
public:
int mySqrt(int x) {
int start = 0;
int end = x;
int middle;
int ans;//记录结果
while(start <= end) {//一定加等号因为可能end等于x
middle = start + (end - start)/2;
if((long long)middle*middle <= x) {//注意不可以写成(long long)(middle*middle),因为这样会先算middle*middle,int显然可能超范围
ans = middle;
start = middle + 1;
}
else {
end = middle - 1;
}
}
return ans;
}
};
数组过大,不要用数组