LeetCode_1【数组】

【数组】文章链接:代码随想录

目录

目录

二分法

704. 二分查找

35.搜索插入位置 

34. 在排序数组中查找元素的第一个和最后一个位置【不好懂】

69.x的平方根

367.有效的完全平方数

移除元素

27.移除元素

26.删除有序数组中的重复项

283.移动零

844. 比较含退格的字符串

其他

977.有序数组的平方

209.长度最小的子数组

59.螺旋矩阵II

 总结 


二分法

  • 时间复杂度:O(log n)
  • 空间复杂度:O(1)

704. 二分查找

文章讲解:代码随想录

视频讲解:手把手带你撕出正确的二分法 | 二分查找法 | 二分搜索法 | LeetCode:704. 二分查找_哔哩哔哩_bilibili

【左闭右闭】的写法

移位符号:向右是除,向左是乘。注意移位符号的优先级很低,要加括号。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size()-1;
        while (left <= right){
            //int middle = (left + right)/2;//.5会抹掉
            //int middle = left + (right-left)/2;
            int middle = left + ((right - left)>>1);//防止溢出,注意括号
            if(target < nums[middle]){
                right = middle - 1;//一定要写-1
            }
            else if(target > nums[middle]){
                left = middle + 1;
            }
            else{
                return middle;
            }
        }
        return -1;
    }
};

35.搜索插入位置 

题目:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

文章:代码随想录

如果没找到那个数,那么最后会走完while,right在left左边,这个值介于nums[right]和nums[left]之间,插入位置对应left。

class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        int middle = 0;
        while (left <= right){
            int middle = left + ((right-left)>>1);
            if (target < nums[middle]){
                right = middle - 1;
            }else if(target > nums[middle]){
                left = middle + 1;
            }else return middle;// case 1 : in
        }
       // cout <<  left << right << endl; //Q : 怎么用leetcode debug?
       // printf("%d",left);
        return left;//case 2 : not in
       // 或者right + 1 ; 但是不能含有middle,因为如果nums为空,middle会没有被定义
    }
};

34. 在排序数组中查找元素的第一个和最后一个位置【不好懂】

题目:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

文章:代码随想录

我的方法还挺简单,用的双指针法,可以通过

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int left = 0 , right = nums.size() - 1;
        if(nums.size() == 0) return {-1, -1};
        while(left <= right && left < nums.size() && right >= 0){            
            if(nums[right] == target && nums[left] == target){
                return {left, right};
            }
            if(nums[left] < target) left ++;
            if(nums[right] > target) right --;
        }
        return {-1, -1};
    }
};

代码随想录提供的方法

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        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};
    }
private:
     int getRightBorder(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        int rightBorder = -2; // 记录一下rightBorder没有被赋值的情况
        while (left <= right) {
            int middle = left + ((right - left) / 2);
            if (nums[middle] > target) {
                right = middle - 1;
            } else { // 寻找右边界,nums[middle] == target的时候更新left
                left = middle + 1;
                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;
    }
};

其他道友提供的方法

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
    int left = 0;
    int right = nums.size() - 1;
    int first = -1;
    int last = -1;
    // 找第一个等于target的位置
    while (left <= right) {
      int middle = (left + right) / 2;
      if (nums[middle] == target) {
        first = middle;
        right = middle - 1; //重点
      } else if (nums[middle] > target) {
        right = middle - 1;
      } else {
        left = middle + 1;
      }
    }

    // 最后一个等于target的位置
    left = 0;
    right = nums.size() - 1;
    while (left <= right) {
      int middle = (left + right) / 2;
      if (nums[middle] == target) {
        last = middle;
        left = middle + 1; //重点
      } else if (nums[middle] > target) {
        right = middle - 1;
      } else {
        left = middle + 1;
      }
    }
    return {first, last};
    }
};

 思路:假设我们有一个数组 nums = [1,2,3,3,3,3,4,5,9],我们想要找到目标值 target = 3 的第一个和最后一个位置。

首先,我们找到第一个等于 target 的位置:

初始化 left = 0 和 right = 8。计算 middle = (0 + 8) / 2 = 4,nums[4] = 3 等于 target,所以 first = 4,然后 right = 4 - 1 = 3。

更新 middle = (0 + 3) / 2 = 1,nums[1] = 2 小于 target,所以 left = 1 + 1 = 2。

更新 middle = (2 + 3) / 2 = 2,nums[2] = 3 等于 target,所以 first = 2,然后 right = 2 - 1 = 1。

现在 left = 2 大于 right = 1,所以循环结束。我们找到了第一个等于 target 的位置,first = 2。

然后,我们找到最后一个等于 target 的位置:

重新初始化 left = 0 和 right = 8。计算 middle = (0 + 8) / 2 = 4,nums[4] = 3 等于 target,所以 last = 4,然后 left = 4 + 1 = 5。

更新 middle = (5 + 8) / 2 = 6,nums[6] = 4 大于 target,所以 right = 6 - 1 = 5。

更新 middle = (5 + 5) / 2 = 5,nums[5] = 3 等于 target,所以 last = 5,然后 left = 5 + 1 = 6。

现在 left = 6 大于 right = 5,所以循环结束。我们找到了最后一个等于 target 的位置,last = 5。

所以,target = 3 的第一个和最后一个位置分别是 2 和 5,返回结果为 [2, 5]。

69.x的平方根

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

我的暴力解法也不知道对不对:

class Solution {
public:
    int mySqrt(int x) {
        int i = 0;
        for(; i <= x; i++){
            if((long long)i * i > x){
                break;
            }
        }
        return i - 1;
    }
};

MoonLight提供了三种方法,空间复杂度都是O(1)

牛顿迭代法(时间复杂度O(logn)):

首先,判断输入的数字是否为0,如果是则直接返回0;
定义一个误差值epsilon,用于判断迭代是否达到精度要求;
初始化变量c为输入的数字x,x0也为x;
使用牛顿迭代法的公式x = 1/2(x0 + c/x0)来更新x0,直到x0与xi之间的差值小于epsilon为止;
返回x0的整数部分作为结果

袖珍计算器算法(时间复杂度O(1)):

判断输入的数字是否为0,如果是则直接返回0;
使用指数函数exp和对数函数log来计算平方根;
将x的平方根计算结果ans转换为整型后返回;
如果(ans+1)的平方小于等于x,则将ans加1作为结果;否则,直接返回ans

二分查找(时间复杂度O(logn)):

判断输入的数字是否为0,如果是则直接返回0;
初始化左边界left为0,右边界right为x,结果ans初始化为-1;
进行二分查找,每次取中间值mid,如果mid的平方小于等于x,则更新ans为mid,并将左边界left更新为mid+1;否则,将右边界right更新为mid-1;
当左边界大于右边界时,结束查找,返回ans作为结果

class Solution {
public:
    int mySqrt(int x) {
        if(x == 0)
            return 0;
        int left = 0, right = x, ans = -1;
        while(left <= right){
            int mid = (left + right) / 2;
            if((long long)mid * mid <= x){//0 <= x <= 231 - 1
                ans = mid;
                left = mid + 1;
            }else{
                right = mid - 1;
            }
        }
        return ans;
    }
};

TIP:

1、在二分法的while中的每一种if下,除非直接return,都得至少有个left = middle +1 或者 right = middle -1要不就会成死循环。

2、题目暗示了0 <= x <= 2^31 - 1,mid*mid有可能大于x,因此限制其为双长整型long long,如果不加这个限制会报错超出时间限制。

int:int 是最常用的整数类型,通常占用4个字节(32位),可以表示大多数整数值。范围大约为 -2^31 到 2^31 - 1。

short:short 是短整型,占用2个字节(16位),适合表示较小的整数。范围大约为 -2^15 到 2^15 - 1。

long:long 是长整型,通常占用4或8个字节(32位或64位),用于表示较大的整数。范围大约为 -2^31 到 2^31 - 1 或 -2^63 到 2^63 - 1,具体取决于编译器和操作系统。

long long: long long 是更大的整数类型,通常占用8个字节(64位),用于表示更大范围的整数。范围大约为 -2^63 到 2^63 - 1。

unsigned int: unsigned int 是无符号整数类型,与 int 类型具有相同的大小,但仅表示非负整数,范围为 0 到 2^32 - 1 或 0 到 2^64 - 1。

unsigned short、unsigned long、unsigned long long: 这些是无符号的短整型、长整型和更大的整数类型,用于表示非负整数。

367.有效的完全平方数

题目: 力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

记得移位操作的优先级很低,注意加括号!

class Solution {
public:
    bool isPerfectSquare(int num) {
        int left = 1;
        int right = num;
        while(left <= right){
            int middle = left +((right-left)>>1);
            if((long long) middle * middle < num){
                left = middle + 1;
            }else if((long long) middle * middle > num){
                right = middle - 1;
            }else{
                return true;
            }
        }
        return false;

    }
};

移除元素

27.移除元素

题目:题目:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

文章:代码随想录

class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        // int fast = 0;
        int slow = 0;
        int n = nums.size()-1;
        for (int fast = 0; fast<=n; fast++){
            if (val != nums[fast]){
                nums[slow++]=nums[fast];
//                 slow = slow + 1;
            }
        }
        return slow;
    }
};

26.删除有序数组中的重复项

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

class Solution {
public:
    int removeDuplicates(vector<int>& nums) {
        int n = nums.size();
        if (n == 0) {
            return 0;
        }
        int slow = 1;
        int fast = 1;
        for (; fast < n ;fast ++){
            if (nums[fast] != nums[slow-1])
            nums[slow ++] = nums[fast];
        }
        return slow;
    }
};

283.移动零

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        int slow = 0;
        int fast = 0;
        int i = 0;
        int n = nums.size();
        for( ; fast < n ; fast++){
            if(nums[fast] != 0){
                nums[slow++] = nums[fast];
            }
            //不能左右一起变,因为是同一个数组
            // else{
            //     nums[n-i-1] = 0;
            //     i++;
            // }
        }
        while( slow < n){
            nums[slow++] = 0;
        }
    }
};

844. 比较含退格的字符串

力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

class Solution {
public:
    bool backspaceCompare(string s, string t) {
        return res(s)==res(t);//c++中字符串可以直接比较是否相同
    }
    string res(string str){
        int slow = 0;
        int fast = 0;
        for(; fast<str.size(); fast++){
            if(str[fast] != '#'){
                str[slow++]=str[fast];
            }
            else if (slow > 0){//第一个是#的话,无效
                slow--;
            }
        }
        return str.substr(0,slow);//记住字符串的这个函数
    }
};

其他

977.有序数组的平方

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

文章讲解:代码随想录

视频讲解: 双指针法经典题目 | LeetCode:977.有序数组的平方_哔哩哔哩_bilibili 

class Solution {
public:
    vector<int> sortedSquares(vector<int>& nums) {
        int n = nums.size();
        int right = n - 1;
        int left = 0;
        int i = 0;
        vector<int> result(n , 0);
        while(left <= right){
            if((long)nums[left] * nums[left] >= (long)nums[right] * nums[right]){
                result[n - 1 - i] = nums[left] * nums[left];
                left ++ ;
            }
            else{
                result[n -1 - i] = nums[right] * nums[right];
                right -- ;
            }
            i++;
        }
        return result;
    }
};

209.长度最小的子数组【滑动窗口】

题目建议: 本题关键在于理解滑动窗口,这个滑动窗口看文字讲解 还挺难理解的,先看视频讲解。 拓展题目。 

题目链接:https://leetcode.cn/problems/minimum-size-subarray-sum/

文章讲解:代码随想录

视频讲解:拿下滑动窗口! | LeetCode 209 长度最小的子数组_哔哩哔哩_bilibili

这种方法更好一些~!

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int res =INT32_MAX;
        int left = 0;
        int right = 0;
        int sum = 0;
        int subLength = 0;
        while(right<nums.size()){
            sum += nums[right];
            while(sum >= target){
                sum -= nums[left];
                subLength = right - left + 1;
                res = res < subLength? res : subLength; 
                left ++;
            }
            right++;
        }
        // return res < INT32_MAX? res : INT_MAX;
        return res == INT32_MAX? 0 : res;
    }
};

注意:

+= 中间不能有空格

right++别忘了写了

另一种方法:

注意:如果条件设置为 while (left < nums.size() && right < nums.size()),那么当 right 指针达到数组的末尾时,即使 left 指针还没有遍历完整个数组,循环也会停止。这会导致算法错过在数组后半部分可能存在的更短的子数组。

class Solution {  
public:  
    int minSubArrayLen(int target, std::vector<int>& nums) {  
        int left = 0, right = 0;  
        int sum = 0, length = INT_MAX;  
        while (left < nums.size()) {  
            if (sum < target) {  
                if (right == nums.size()) break; // 防止越界  
                sum += nums[right++];  
            } else {  
                length = std::min(length, right - left);  //题干:大于等于
                sum -= nums[left++];  
            }  
        }  
        return length == INT_MAX ? 0 : length;  
    }  
};

59.螺旋矩阵II【难】

题目建议:  本题关键还是在转圈的逻辑,在二分搜索中提到的区间定义,在这里又用上了。 

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

文章讲解:代码随想录

视频讲解:一入循环深似海 | LeetCode:59.螺旋矩阵II_哔哩哔哩_bilibili

class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n, vector<int>(n, 0)); // 使用vector定义一个二维数组
        int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
        int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
        int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
        int count = 1; // 用来给矩阵中每一个空格赋值
        int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
        int i,j;
        while (loop --) {
            i = startx;
            j = starty;

            // 下面开始的四个for就是模拟转了一圈
            // 模拟填充上行从左到右(左闭右开)
            for (j = starty; j < n - offset; j++) {
                res[startx][j] = count++;
            }
            // 模拟填充右列从上到下(左闭右开)
            for (i = startx; i < n - offset; i++) {
                res[i][j] = count++;
            }
            // 模拟填充下行从右到左(左闭右开)
            for (; j > starty; j--) {
                res[i][j] = count++;
            }
            // 模拟填充左列从下到上(左闭右开)
            for (; i > startx; i--) {
                res[i][j] = count++;
            }

            // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
            startx++;
            starty++;

            // offset 控制每一圈里每一条边遍历的长度
            offset += 1;
        }

        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        if (n % 2) {
            res[mid][mid] = count;
        }
        return res;
    }
};
//error
class Solution {
public:
    vector<vector<int>> generateMatrix(int n) {
        vector<vector<int>> res(n,vector<int>(n));
        int loop = 0, num = 1;
        while(loop < n/2){
            int x0 = loop, y0 = loop, xd = n - loop, yd = n - loop -1;
            int i = x0;
            int j = y0;
            for( ; i == x0 && j <= yd - 1; j++){
                res[i][j] = num ++;
            }
            for( ; j == yd && i <= xd - 1; i++){
                res[i][j] = num ++;
            }
            for(; i == xd && j >= y0 ; j--){
                res[i][j] = num ++;
            }
            for(; j == y0 && i <= x0; i--){
                res[i][j] = num ++;
            }
            loop ++;
        }
        return res;
    }
};

54.螺旋矩阵【矩阵不是正方形的情况】:

class Solution {
public:
    vector<int> spiralArray(vector<vector<int>>& array) {
        if(array.size() == 0) return {};
        vector<int> vec;
        int l = 0, r = array[0].size() - 1, t = 0, b = array.size() - 1;
        while(true){
            for(int i = l; i <= r; i++) vec.push_back(array[t][i]);
            t++;
            if(t > b) break;
            for(int j = t; j <= b; j++) vec.push_back(array[j][r]);
            r--;
            if(r < l) break;
            for(int i = r; i >= l; i--) vec.push_back(array[b][i]);
            b--;
            if(b < t) break;
            for(int j = b; j >= t; j--) vec.push_back(array[j][l]);
            l++;
            if(l > r) break;
        }
        return vec;
    }
};

 

总结 

文章链接:代码随想录

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值