1. 刷题——数组

数组刷题

主要参考《代码随想录》,次要参考leetcode上面各种好的题解思路

基础知识

  1. 定义:数组是存放在连续内存空间上的相同类型的数据集合
  2. 特点:
    • 数组下标从0开始的
    • 数组在内存的地址空间是连续的
    • 随机存取,但是增删元素需要移动其他元素,比较慢
  3. 数组在内存空间的地址时连续的,需要注意的时是
    • 数组下标是从0开始的
    • 数组内存空间是连续的
  4. vector是容器,底层是用array实现的
  5. java在二维数组的内存空间是由row个具有column元素的连续空间组成的,而不是row*column组成的

一、二分查找

基本二分法
  1. letcode网址https://leetcode.cn/problems/binary-search/submissions/

  2. 算法特点

    • 有序无重复的数组,有重复元素返回的下标可能不唯一
    • 逻辑简单,但是确定边界比较繁琐
  3. 区间定义方式

    • 左闭右闭 [left, right]
    • 左闭右开 [left, right)
    • 边界的处理就是根据区间的定义进行的
  4. 左闭右闭代码

    class Solution {
    public:
        int search(vector<int>& nums, int target) {
            int left = 0;
            int right = nums.size() - 1; // 定义target在左闭右闭的区间里
            
            while (left <= right) { // 前闭后闭用<=
            // 当left==right,区间[left, right]依然有效,所以用 <=
                int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
                if (nums[middle] > target) {// 结果在左区间
                    right = middle - 1; 
                    // target A在左区间,所以[left, middle - 1]
                } else if (nums[middle] < target) {// 结果在右区间
                    left = middle + 1; 
                    // target 在右区间,所以[middle + 1, right]
                } else { // 相等找到目标值
                    return middle; // 数组中找到目标值,直接返回下标
                }
            }
            return -1;// 未找到目标值
        }
    };
    
搜索插入位置
  1. letcode网址https://leetcode.cn/problems/search-insert-position/

  2. 代码

    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;// 取整总是取的前一个
        }
    };
    
在排序数组中查找元素的第一个和最后一个位置
  1. letcode网址https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/

  2. 代码

    class Solution {
    public:
        vector<int> searchRange(vector<int>& nums, int target) {
            int rightBorder = getRightBorder(nums, target);
            int leftBorder = getLeftBorder(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 mid;
            int rightBorder = -2;
    
            while (left <= right) {
                mid = (left + right) / 2;
                if (target < nums[mid]) {
                    right = mid - 1;
                }else{
                    left = mid + 1;
                    rightBorder = left;
                }
            }
            return rightBorder;
        }
        
        int getLeftBorder(vector<int>& nums, int target) {
            int left = 0;
            int right = nums.size() - 1;
            int mid;
            int leftBorder = -2;
             
            while (left <= right) {
                mid = (left + right) / 2;
                if (target > nums[mid]){
                    left = mid + 1;
                }else{
                    right = mid - 1;
                    leftBorder = right;
                }
            }
            return leftBorder;
    
        }
    };
    
x 的平方根
  1. letcode网址https://leetcode.cn/problems/sqrtx/

  2. 代码

    class Solution {
    public:
        int mySqrt(int x) {
            // x的平方根一定在[1, x/2+1]内
            int left = 1;
            int right = (x >> 1) + 1; // 除以2的倍数可以使用位运算
            // 区间使用方式:前闭后闭
            while (left <= right) {
                int mid = left + ((right - left) >> 1); // 防止溢出,使用位运算加快速度
                if (mid > x / mid) {  // 尽量使用除法和减法防止数值太大而内存溢出
                    right = mid - 1;
                } else if (mid < x / mid) {
                    left = mid + 1;
                } else {
                    return mid;
                }
            }
            return right; // 终止条件left > right,因为是向下取整,所以返回的是right     
        }
    };
    
    
    
有效的完全平方数
  1. letcode网址https://leetcode.cn/problems/valid-perfect-square/submissions/

  2. 代码

    class Solution {
    public:
        bool isPerfectSquare(int num) {
            int left = 1;
            int right = (num >> 1) + 1;
            long mid;
    
            while (left <= right) {
                mid = left + ((right - left) >> 1);
                if (num < mid*mid) {// 不能使用num / mid 和mid进行比较,会向下取整导致错误
                    right = mid - 1;
                }else if (num > mid*mid){
                    left = mid + 1;
                }else
                    return true;
            }
            return false;
        }
    };
    
    
    // 完全平方数可以使用从1开始的连续奇数相加得到,不能得到的就是非完全平方数
    
    class Solution 
    {
    public:
        bool isPerfectSquare(int num) 
        {
            int num1 = 1;
            while(num > 0) 
            {
                num -= num1;
                num1 += 2;
            }
            return num == 0;
        }
    };
    
    

二、移除元素

erase函数的实现
  1. 数组 中的元素不能删除,只能覆盖,删除中间元素需要后面的元素都向前移动进行覆盖

  2. 使用双指针:快指针寻找所需要的元素赋值给慢指针所指向的位置

  3. 代码实现https://leetcode.cn/problems/remove-element/submissions/

    class Solution {
    public:
        int removeElement(vector<int>& nums, int val) {
            int slow = 0;
            // 快指针寻找所需元素,慢指针获取赋值位置
            for(int fast = 1; fast < nums.size(); ++fast){
                if (nums[fast] != val){
                    nums[slow] = nums[fast];
                    slow++; 
                }
            }
            return slow;// 数组的新长度
        }
    };
    
26.删除排序数组中的重复项
  1. 代码
    int removeDuplicates(vector<int>& nums) {
           int slow = 0;
           for (int fast = 1; fast  < nums.size() ; ++fast){
               if (nums[fast] == nums[slow]){
                   continue;
               }else{
               // 不相等就覆盖下一个slow的位置
               ++slow;
               nums[slow] = nums[fast];
               }
           }   
           return slow + 1;
       }
    
283.移动零
  1. 代码

    void moveZeroes(vector<int>& nums) {
            int slow = 0;
            for(int fast = 0; fast < nums.size(); ++fast){
                if (nums[fast] != 0) 
                    nums[slow++] = nums[fast];
            }
        // 慢指针最后停下的位置是判断条件符合的最后一个
            while (slow < nums.size()) 
                nums[slow++] = 0;
        }
    
844.比较含退格的字符串
  1. 使用双指针,将字符串的处理封装成函数,时间100%,但是空间因为堆栈消耗可能大一些,只是超过60%

    class Solution {
    public:
        bool backspaceCompare(string s, string t) {
            int sLength = 0;
            int tLength = 0;
            int ptr = 0;
            // 对两个串都进行消减处理
            handleString(s, sLength);
            handleString(t, tLength);
            // 长度不等,直接否定
            if (sLength != tLength)
                return false;
            // 比较每个元素是否相同
            while(ptr < sLength) {
                if (s[ptr] != t[ptr])
                    return false;
                ptr++;
            }
            return true;
        }
    
    private:
        // 对每个字符串进行消减
        void handleString(string &str, int &length) {
            int slow = 0;
            int fast = 0;
            while (fast < str.size()) {
                if (str[fast] != '#') {
                    str[slow] = str[fast];
                    slow++;
                    fast++;
                } else {
                    fast++;
                    if (slow != 0) slow--;// 退格到0将无法继续退格
                }
            }
            length = slow;
        }
    };
    
977.有序数组的平方
  1. 寻找多种情况中,固定的哪一点。例如:有序(最值一定在端点处)

  2. 代码

    class Solution {
    public:
        vector<int> sortedSquares(vector<int>& nums) {
            int p = nums.size() - 1;
            vector<int> result(nums.size(), 0); // 初始化结果数组
            int left = 0;
            int right = nums.size() - 1;
            
            while (left <= right) {
                if ((nums[left] * nums[left]) > (nums[right] * nums[right])) {
                    result[p--] = nums[left] * nums[left];
                    left++;
                } else {
                    result[p--] =   nums[right] * nums[right];
                    right--;// 左边界++,右边界--
                }
            }
            return result;
        }
    };
    

三、滑动窗口

长度最小的子数组
  1. 滑动窗口的核心:根据当前子序列和大小的情况,不断调节子序列的起始位置。从而将O(n^2)暴力解法降为O(n)。

  2. 代码

     int minSubArrayLen(int target, vector<int>& nums) {
            // 双指针定义
            int slow = 0;
            int fast = 0;
            // 过程变量记录
            int minLength = 0;
            int sum = 0;
            int result = INT32_MAX;// int的最大值
         
            for(;fast < nums.size(); ++fast) {
                sum += nums[fast];
                while (sum >= target) {// 核心就是if改为while
                    minLength = fast - slow + 1;// 取连续子序列的长度
                    result = result < minLength ? result : minLength;
                    // 比较记录最小长度
                    sum -= nums[slow++];// 慢指针滑动
                }
            }
         
            return result == INT32_MAX ? 0 : result;
        }
    
904.水果成篮
  1. 题目地址https://leetcode.cn/problems/fruit-into-baskets/

  2. 代码

     int totalFruit(vector<int>& fruits) {
            // 设置窗口边界
            int slow = 0;
            int fast = 0;
            // 设置条件判定值
            unordered_map<int, int> basket; //创建篮子
            int fruit_counter = 0; //篮子中的水果种类数
            int length = 0;
    
            while (fast < fruits.size()) {
                // 快指针前进的条件
                if (basket[fruits[fast]] == 0)// 水果种类fruits[fast]是键,basket[fruits[fast]]是值
                    fruit_counter++;
                basket[fruits[fast]]++; // 插入新水果 
                // 慢指针前进的条件
                while (fruit_counter > 2) {
                    basket[fruits[slow]]--;
                    if (basket[fruits[slow]] == 0) {
                        fruit_counter--;
                    }
                    slow++;
                }
                // 善后处理
                length = max(length, fast - slow + 1);
                fast++;
            }
            
            return length;
        }
    
    • 代码的开始先考虑边界值和正确性验证
  3. 什么时候使用哈希法? 查询集合中的一个元素是否存在

  4. map是一种key value的存储结构,key保存要查询的键值,value保存对应其他信息

    1. std::map使用红黑树实现,key有序且不可重复,查询和增删效率均为O(log N)
    2. std::multimap使用红黑树实现,key有序且可重复,查询和增删效率均为O(log N)
    3. std::unordered_map使用哈希表,key无序且不可重复,查询和增删效率均为O(1)
  5. map类型容器操作方法

    unordered_map<int, string> mp;
    // 容器的插入
    // 1. 直接使用[] 符号
    mp[key] = "value";// mp[key]整体表示值
    // 2. 使用make_pair构造对象进行插入
    mp.insert(make_pair(key, "value"));
    // 3. 使用pair方法构造键值对插入
    mp.insert(pair<int, string>(key, "d"));
    
    // 查找方法
    if (mp.find(key) != mp.end())
        function;// 找到返回迭代器,找不到返回mp.end()
        
    // 统计方法
    mp.count(key);// 统计key在map中出现的次数,没有返回0
    
    // 获取key的值
    // 1. 使用迭代器
    string val = mp.find(key)->second;
    
    // 2. at方法,不存在会报异常
    string val = mp.at(2);
    
    // 3. 使用[]操作符,不存在会插入
    string val = mp[key];
    
    // 删除元素
    // 1. 根据键删除
    mp.erase(key);
    // 2.根据值删除
    for(iter = mp.begin(); iter!=mp.end();) {
        if (iter->second=="value") {
            mymap.erase(iter++);
        } else {// 防止删除后迭代器失效
            iter++;
        }
    }
    
    // 遍历map
    map<int, string>::iterator iter;
    for(iter = mp.begin(); iter != mp.end(); iter++) {
        cout<<iter->first<<": "<<iter->second<<endl;
    }
    
    
    
  6. 最小滑窗模板:给定数组 nums,定义滑窗的左右边界 i, j,求满足某个条件的滑窗的最小长度。

    while j < len(nums):
        判断[i, j]是否满足条件
        while 满足条件:
            不断更新结果(注意在while内更新!)
            i += 1 (最大程度的压缩i,使得滑窗尽可能的小)
        j += 1
    
  7. 最大滑窗模板:给定数组 nums,定义滑窗的左右边界 i, j,求满足某个条件的滑窗的最大长度。

    while j < len(nums):
        判断[i, j]是否满足条件
        while 不满足条件:
            i += 1(一旦满足条件了就退出压缩i的过程,使得滑窗尽可能的大)
        不断更新结果(注意在while外更新!)
        j += 1
    
76.最小覆盖子串
  1. 代码

    class Solution {
    public:
        string minWindow(string s, string t) {
            unordered_map<char, int> sMap;// s的哈希表
            unordered_map<char, int> tMap;// t的哈希表
            // 初始化t的哈希表
            for (auto c: t) // c是临时变量
                tMap[c]++ ;
            
            string res;
            int cnt = 0;// 主串中必需字符的数量
            for (int fast = 0, slow = 0; fast < s.size(); fast++ ) {
                // 
                sMap[s[fast]]++ ;// 主串哈希表的插值
                if (sMap[s[fast]] <= tMap[s[fast]]) 
                    cnt++ ;
                // 剔除不符合的字符,进行窗口的缩紧
                while (sMap[s[slow]] > tMap[s[slow]]) 
                    sMap[s[slow++]]-- ;
                // 符合条件的子串,并迭代记录最小的
                if (cnt == t.size()) {
                    if (res.empty() || fast - slow + 1 < res.size())
                        res = s.substr(slow, fast - slow + 1);
                }
            }
            return res;
        }
    };
    
    
    • 记录最小窗口或最大窗口在外部的遍历循环结束,进行最值的记录
    • str.substr(开始下标,下标后的位数),返回相应的子串

四、循环不变量

螺旋矩阵
  1. 代码

    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;
        }
    };
    
    
    • 寻找统一化的规则
      • 特例:一般是开始的0值或者结束时出现特殊情况
      • 细节:每一圈的长度均-1,每一圈的位置均加一
      • 整体:总共遍历n/2圈,奇数要额外加(n/2, n/2)圈
顺时针打印矩阵
  1. 代码

    class Solution {
    public:
        vector<int> spiralOrder(vector<vector<int>>& matrix) {
            if (matrix.size() == 0 || matrix[0].size() == 0) 
                return {};
            vector<int> res;
            int top = 0;                      //上边界
            int bottom = matrix.size() - 1;   //下边界
            int left = 0;                     //左边界
            int right = matrix[0].size() - 1; //右边界
            
            while (true) {
                // 从左到右
                for (int i = left; i <= right; ++ i) 
                    res.push_back(matrix[top][i]);
                // 每次从左往右执行一次,则要往下移一层
                if (++ top > bottom) break;
                // 从上到下
                for (int i = top; i <= bottom; ++ i) 
                    res.push_back(matrix[i][right]);
                // 每次从上到下执行一次,则要往左移一列
                if (-- right < left) break;
                // 从右到左
                for (int i = right; i >= left; -- i) 
                    res.push_back(matrix[bottom][i]);
                // 每次从右到左执行一次,则要往上移一行
                if (-- bottom < top) break;
                // 从下到上
                for (int i = bottom; i >= top; -- i) 
                    res.push_back(matrix[i][left]);
                // 每次从下到上执行一次,则要往右移一列
                if (++ left > right) break;
            }
            return res;
        }
    };
    
    
    

c

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逆羽飘扬

如果有用,请支持一下。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值