代码随想录day1 | LeetCode704. 二分查找、LeetCode27. 移除元素、LeetCode977.有序数组的平方

代码随想录day1 | LeetCode704. 二分查找、LeetCode27. 移除元素、LeetCode977.有序数组的平方

二分查找

题目链接:LeetCode704. 二分查找

自己敲

#include <iostream>
#include <vector>
using namespace std;

class Solution
{
public:
    static int search(vector<int> &nums, int target)
    {
        int low = 0, high = nums.size() - 1, mid;
        while (high >= low)
        {
            mid = (low + high) / 2;
            // cout << low << " " << high << " " << nums[mid] << endl;
            if (nums[mid] == target)
            {
                return mid;
            }
            else if (target > nums[mid])
            {
                low = mid + 1;
            }
            else
            {
                high = mid - 1;
            }
        }
        return -1;
    }
};

int main()
{
    vector<int> nums;
    nums.push_back(2);
    nums.push_back(3);
    nums.push_back(1);
    nums.push_back(2);
    nums.push_back(3);
    nums.push_back(4);
    int target = 7;
    int result = Solution::search(nums, target);
    cout << result << endl;

    return 0;
}

  • while循环判断条件是high>=low

    如果没有等于,当测试用例为[5],target=5 时,返回-1

问题:Solution’ has not been declaredgcc

看题解

链接

  • 二分法的前提条件:有序数组+无重复元素(一旦有重复元素,使用二分查找法返回的元素下标可能不是唯一的)

  • 二分查找的边界条件到底应该怎么写?

    • 循环不变量规则:区间的定义就是不变量。要在二分查找的过程中,保持不变量,就是在while寻找中每一次边界的处理都要坚持根据区间的定义来操作

    • 写二分法,区间的定义一般为两种,左闭右闭即[left, right],或者左闭右开即[left, right)。

      • 左闭右闭的区间里,也就是[left, right]

        • while (left <= right) 要使用 <= ,因为left == right是有意义的,所以使用 <=
        • if (nums[middle] > target) right 要赋值为 middle - 1,因为当前这个nums[middle]一定不是target,那么接下来要查找的左区间结束下标位置就是 middle - 1
        // 版本一
        class Solution {
        public:
            int search(vector<int>& nums, int target) {
                int left = 0;
                int right = nums.size() - 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; // 数组中找到目标值,直接返回下标
                    }
                }
                // 未找到目标值
                return -1;
            }
        };
        

        代码和我写的一样

      • 左闭右开的区间里,也就是[left, right)

        • while (left < right),这里使用 < ,因为left == right在区间[left, right)是没有意义的
        • if (nums[middle] > target) right 更新为 middle,因为当前nums[middle]不等于target,去左区间继续寻找,而寻找区间是左闭右开区间,所以right更新为middle,即:下一个查询区间不会去比较nums[middle]
        // 版本二
        class Solution {
        public:
            int search(vector<int>& nums, int target) {
                int left = 0;
                int right = nums.size(); // 定义target在左闭右开的区间里,即:[left, right)
                while (left < right) { // 因为left == right的时候,在[left, right)是无效的空间,所以使用 <
                    int middle = left + ((right - left) >> 1);
                    if (nums[middle] > target) {
                        right = middle; // target 在左区间,在[left, middle)中
                    } else if (nums[middle] < target) {
                        left = middle + 1; // target 在右区间,在[middle + 1, right)中
                    } else { // nums[middle] == target
                        return middle; // 数组中找到目标值,直接返回下标
                    }
                }
                // 未找到目标值
                return -1;
            }
        };
        

总结

  • 循环中始终坚持根据查找区间的定义来做边界处理

移除元素

题目链接:LeetCode27. 移除元素

自己敲

双指针

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int i=0,j=0;
        for(i=0;i<nums.size();i++){
            if(nums[i]!=val){
                nums[j]=nums[i];
                j++;
            }
        }
        return j;
    }
};

暴力

我不知道如何在原地操作的情况下使用暴力解法

看题解

链接

暴力解法

这个题目暴力的解法就是两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组。

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int size = nums.size();
        for (int i = 0; i < size; i++) {
            if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
                for (int j = i + 1; j < size; j++) {
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;

    }
};
双指针法

双指针法(快慢指针法): 通过一个快指针和慢指针在一个for循环下完成两个for循环的工作。

定义快慢指针

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
            if (val != nums[fastIndex]) {
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        return slowIndex;
    }
};

总结

  • 暴力解法数组集体向前移动一位后,因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
  • 双指针法可以在一个for循环完成两个for循环的工作

有序数组的平方

题目链接:LeetCode977.有序数组的平方

自己敲

如果采用一个排序算法,给数组每个元素平方后再排序,则没有利用题中数组有序这个条件,结果不完美

采用双指针

#include <iostream>
#include <vector>
using namespace std;

class Solution {
public:
    static vector<int> sortedSquares(vector<int>& nums) {
        vector<int> result;
        int i,j,k=0;//i为负数指针,从右往左,j为正数0指针,从左往右
        for(j=0;j<nums.size();j++){
            if(nums[j]>=0){
                break;
            }
        }
        i=j-1;
        cout<<i<<"*"<<j<<"*"<<endl;
        while(i>=0&&j<nums.size()){
        	cout<<i<<" "<<j<<" "<<endl;
            if(nums[i]*nums[i]<=nums[j]*nums[j]){
            	cout<<"hri"<<endl;
                result[k++]=nums[i]*nums[i];
                i--;
            }else{
            	cout<<"hrj"<<endl;
                result[k++]=nums[j]*nums[j];
                j++;
                cout<<result[k-1]<<endl;//并没有输出这行程序就结束了
            }
        }
        cout<<i<<"*"<<j<<"*"<<endl;
        while(i>=0){
            result[k++]=nums[i--];
        }
        while(j<nums.size()){
            result[k++]=nums[j++];
        }
        cout<<i<<"*"<<j<<"*"<<endl;
        return result;
    }
};

int main()
{
    vector<int> nums;
    nums.push_back(-4);
    nums.push_back(-1);
    nums.push_back(0);
    nums.push_back(3);
    nums.push_back(10);
    //int target = 7;
    vector<int> result = Solution::sortedSquares(nums);
    for(int i=0;i<result.size();i++){
    	cout << result[i] << endl;
	}

    return 0;
}

在这里插入图片描述

问题可能出在vector< int > result的使用上

看题解

链接

暴力排序

最直观的想法,莫过于:每个数平方之后,排个序,代码如下:

class Solution {
public:
    vector<int> sortedSquares(vector<int>& A) {
        for (int i = 0; i < A.size(); i++) {
            A[i] *= A[i];
        }
        sort(A.begin(), A.end()); // 快速排序
        return A;
    }
};
双指针法
  • 数组其实是有序的, 只不过负数平方之后可能成为最大数了。

  • 那么数组平方的最大值就在数组的两端,不是最左边就是最右边,不可能是中间。

  • 此时可以考虑双指针法了,i指向起始位置,j指向终止位置。

定义一个新数组result,和A数组一样的大小,让k指向result数组终止位置

如果A[i] * A[i] < A[j] * A[j] 那么result[k--] = A[j] * A[j];

如果A[i] * A[i] >= A[j] * A[j] 那么result[k--] = A[i] * A[i];

class Solution {
public:
    vector<int> sortedSquares(vector<int>& A) {
        int k = A.size() - 1;
        vector<int> result(A.size(), 0);
        for (int i = 0, j = A.size() - 1; i <= j;) { // 注意这里要i <= j,因为最后要处理两个元素
            if (A[i] * A[i] < A[j] * A[j])  {
                result[k--] = A[j] * A[j];
                j--;
            }
            else {
                result[k--] = A[i] * A[i];
                i++;
            }
        }
        return result;
    }
};

这个双指针比我的还要好

修改自己代码

发现题解中自己定义的vector要初始化

vector<int> result(A.size(), 0);

修改自己代码后发现,数组最后一个元素没有平方
在这里插入图片描述

发现是在第二个while循环时,写的是result[k++]=nums[j++]

修改成result[k++]=nums[j]*nums[j] j++

#include <iostream>
#include <vector>
using namespace std;

class Solution {
public:
    static vector<int> sortedSquares(vector<int>& nums) {
        vector<int> result(nums.size(),0);
        int i,j,k=0;//i为负数指针,从右往左,j为正数0指针,从左往右
        for(j=0;j<nums.size();j++){
            if(nums[j]>=0){
                break;
            }
        }
        i=j-1;
        //cout<<i<<"*"<<j<<"*"<<endl;
        while(i>=0&&j<nums.size()){
        	cout<<i<<" "<<j<<" "<<endl;
            if(nums[i]*nums[i]<=nums[j]*nums[j]){
                result[k++]=nums[i]*nums[i];
                i--;
                //cout<<"i="<<i<<endl;
            }else{
                result[k++]=nums[j]*nums[j];
                j++;
                //cout<<"j="<<j<<endl;
            }
        }
        //cout<<"----------------------------"<<endl;
        //cout<<i<<"*"<<j<<"*"<<endl;
        while(i>=0){
            result[k++]=nums[i]*nums[i];
            i--; 
        }
        while(j<nums.size()){
            result[k++]=nums[j]*nums[j];
            j++;
        }
        //cout<<i<<"*"<<j<<"*"<<endl;
        return result;
    }
};

int main()
{
    vector<int> nums;
    nums.push_back(-4);
    nums.push_back(-1);
    nums.push_back(0);
    nums.push_back(3);
    nums.push_back(10);
    //int target = 7;
    vector<int> res(nums.size(),0);
	res = Solution::sortedSquares(nums);
    for(int i=0;i<res.size();i++){
    	cout << res[i] << endl;
	}

    return 0;
}

细节:

result[k++]=nums[j++]*nums[j++];

j++在同一个表达式中被使用了两次,这会导致j的值在第一个j++之后立即增加,然后第二个j++也会在增加后的值上再次增加,这并不是我们想要的结果。

所以要想简化代码,可以如下这么写

result[k++]=nums[j]*nums[j++];

这与下面这行代码等价

result[k++]=nums[j]*nums[j];
j++;

总结

  • 暴力排序中c++库函数的使用

    sort(A.begin(), A.end()); // 快速排序
    
  • 双指针法中,注意自己创建的vector要初始化

    vector<int> result(A.size(), 0);
    
  • 同一个表达式出现两个j++会出现什么结果,j变化过程如何?

result[k++]=nums[j++]*nums[j++];


`j++`在同一个表达式中被使用了两次,这会导致`j`的值在第一个`j++`之后立即增加,然后第二个`j++`也会在增加后的值上再次增加,这并不是我们想要的结果。

所以要想简化代码,可以如下这么写

```c++
result[k++]=nums[j]*nums[j++];

这与下面这行代码等价

result[k++]=nums[j]*nums[j];
j++;

总结

  • 暴力排序中c++库函数的使用

    sort(A.begin(), A.end()); // 快速排序
    
  • 双指针法中,注意自己创建的vector要初始化

    vector<int> result(A.size(), 0);
    
  • 同一个表达式出现两个j++会出现什么结果,j变化过程如何?

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值