【笔记】数组

本系列总计六篇文章,是 基于STL实现的笔试题常考七大基本数据结构 该文章在《代码随想录》和《labuladong的算法笔记》题目中的具体实践,每篇的布局是这样的:开头是该数据结构的总结,然后是在不同场景的应用,以及不同的算法技巧。本文是系列第一篇,介绍了数组的相关题目,重点是要掌握双指针算法、二分查找算法。

下面文章是在《代码随想录》和《labuladong的算法笔记》题目中的具体实践:
【笔记】数组
【笔记】链表
【笔记】哈希表
【笔记】字符串
【笔记】栈与队列
【笔记】二叉树

0、总结

  • 双指针,快慢双指针(用于数组覆盖),左右双指针(二分查找),滑动窗口(长度最小子数组)
  • 有序数组+无重复,必然想到 二分查找 及其变体,找target,找左边界,找右边界,需要坚持闭区间
  • 用循环不变量的模拟(螺旋数组)

1、二分查找-左右双指针

算法框架

注意,…号出现的四处位置,根据是否闭区间/左闭右开,是否返回一个索引/索引边界,而不同,下面我都坚持闭区间的写法

int binarySearch(int[] nums, int target) {
   
    int left = 0, right = 1...;

    while(2...) {
   
        int mid = left + (right - left) / 2;
        if (nums[mid] == target) {
   
            3...
        } else if (nums[mid] < target) {
   
            left = 3...
        } else if (nums[mid] > target) {
   
            right = 3...
        }
    }
    return 4...;
}

704. 二分查找 - 力扣(LeetCode)

思路:二分查找最基础版本,采用左闭右闭区间,因此注意这四处

1、high初始化指向最末尾元素

2、while中的判断是 <= ,因为当 left == right 时区间依然有意义

3、mid每次必须加减1,相等即返回

4、未找到返回-1

另外,写成 if - else if 形式,将逻辑分支判断展开,更有助于理解

【不足】若target有多个值,该解法只能返回其中一个值

class Solution {
   
public:
    int search(vector<int>& nums, int target) {
   
        int low = 0;
        int high = nums.size() - 1; // 1
        // 当left == right,区间[left, right]依然有效,所以用 <=
        while (low <= high) {
    // 2
            int mid = low + (high - low) / 2;
            if (nums[mid] > target) high = mid - 1; // 3
            if (nums[mid] < target) low = mid + 1; // 3
            if (nums[mid] == target) return mid; // 3
        }
        return -1; // 4
    }
};

35. 搜索插入位置 - 力扣(LeetCode)

思路:二分法的变体,能处理如下四种情况:

  • 目标值在数组所有元素之前
  • 目标值等于数组中某一个元素
  • 目标值插入数组中的位置
  • 目标值在数组所有元素之后
class Solution {
   
public:
    int searchInsert(vector<int>& nums, int target) {
   
        int low = 0;
        int high = nums.size() - 1;
        while (low <= high) {
   
            int mid = low + (high - low) / 2;
            if (nums[mid] > target) high = mid - 1;
            if (nums[mid] < target) low = mid + 1;
            if (nums[mid] == target) return mid;
        }
        return high + 1;
    }
};

!!!以上两种情况,只适合无重复元素+数组升序的版本

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

思路:二分查找变体,能找到target的左右边界

寻找target在数组里的左右边界,有如下三种情况:

  • 情况一:target 在数组范围的右边或者左边,例如数组{3, 4, 5},target为2或者数组{3, 4, 5},target为6,此时应该返回{-1, -1}
  • 情况二:target 在数组范围中,且数组中存在target,例如数组{3,6,7},target为6,此时应该返回{1, 1}
  • 情况三:target 在数组范围中,且数组中不存在target,例如数组{3,6,7},target为5,此时应该返回{-1, -1}

注意:寻找右边界时,nums[mid] > target时,直接减小right = mid - 1,**当 nums[mid] == target 时,不要立即返回,而是增大「搜索区间」的左边界 left,通过left = mid + 1,rightBorder = left,使得区间不断向右靠拢,达到锁定右侧边界的目的,nums[mid] < target时,left = mid + 1,因此小于和等于target的情况合并为一种。**左边界同理。

class Solution {
   
public:
    vector<int> searchRange(vector<int>& nums, int target) {
   
        int leftBorder = getLeftBorder(nums, target);
        int rightBorder = getRightBorder(nums, target);
        cout << leftBorder << endl;
        cout << rightBorder << endl;
        // 情况一,边界值从未被更新,说明 target 在数组范围的右边或者左边
        if (leftBorder == -2 || rightBorder == -2) return {
   -1, -1};
        // 情况二,边界值相差大于1,说明中间至少夹着一个target
        if (rightBorder - leftBorder > 1) return {
   leftBorder + 1, rightBorder - 1};
        // 情况三,边界值相邻,target 在数组范围中,但是数组中不存在target
        return {
   -1, -1};
    }
    // 寻找右边界,不包括target
    int getRightBorder(vector<int>& nums, int target) {
   
        int left = 0;
        int right = nums.size() - 1;
        // 记录初始值
        int rightBorder = -2;
        while (left <= right) {
   
            int mid = left + (right - left) / 2;
            if (nums[mid] > target) {
   
                right = mid - 1;
            } else if (nums[mid] <= target) {
    // 寻找右边界,找到target时先别返回,继续更新left
                left = mid + 1;
                rightBorder = left;
            }
        }
        return rightBorder;
    }
    int getLeftBorder(vector<int>& nums, int target) {
   
        int left = 0;
        int right = nums.size() - 1;
        // 记录初始值
        int leftBorder = -2;
        while (left <= right) {
   
            int mid = left + (right - left) / 2;
            if (nums[mid] < target) {
   
                left = mid + 1;
            } else if (nums[mid] >= target) {
    // 寻找左边界,用 right 来逼近
                right = mid - 1;
                leftBorder = right;
            }
        }
        return leftBorder;
    }
};

69. x 的平方根 - 力扣(LeetCode)

思路:二分查找变体,在1到x中,用二分查找,寻找x的平方根。避免平方运算溢出,应用除法代替。

由于返回类型是整数,结果只保留 整数部分 ,小数部分将被 舍去 。

class Solution {
   
public:
    int mySqrt(int x) {
   
        if (x == 0 || x == 1) return x;
        int left = 1;
        int right = x;
        while (left <= right) {
   
            int mid = left + (right - left) / 2;
            // 用除法判断,避免溢出
            int temp = x / mid;
            if (temp > mid) {
    // mid平方小于x,增大左边界
                left = mid + 1;
            } else if (temp < mid) {
    // mid平方大于x,减小右边界
                right = mid - 1;
            } else if (temp == mid) 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值