代码随想录第一天 | 数组理论基础:二分法(leetcode 704, 35, 34);双指针(leetcode 27, 844, 977, 69)

1、数组

随机存取,连续,相同数据类型
代码随想录数组理论基础

2、二分法

2.1 leetcode 704

第一遍代码,递归

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;
    }
};

2.2 使用二分法特征/前提:

1、强调数组中无重复元素(重复也可考虑,转化为求第一个/最后一个;例如34),因为一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的
2、另一个前提是数组是有序数组,这也是使用二分查找的基础条件
这些都是使用二分法的前提条件,当看到题目描述满足如上条件的时候,可要想一想是不是可以用二分法了

2.3 使用二分法注意点:

while寻找中每一次边界的处理都要坚持根据区间的定义来操作,这就是循环不变量规则,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right),复杂度为 O(log n)

2.4 leetcode 35

第一遍代码,使用二分法解决

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;
    }
};

2.5 leetcode 34(多次提交)

第一次代码,多种提交出现情况后多次加补丁,没有以正确方式有条理地考虑问题

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};

3、双指针

3.1 leetcode 27

第一遍代码(使用同向一快一慢两个指针)

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一定指向了最终数组末尾的下一个元素
    }
};

3.2 leetcode 844

第一遍代码:

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() 函数。

3.3 leetcode 977(不会)

采用前后双指针(因为在待放入结果的数列里面只有左右两边的数可能是最大的,所以从大到小排序)

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;
    }
};

3.4 leetcode 69(未能提交正确代码)

第一遍提交代码:

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;
    }
};

3.5 Memory Limit Exceeded情况

数组过大,不要用数组

你可能感兴趣的:(leetcode,c++,leetcode,算法,c++,数据结构)