代数思维训练之一:二分查找

代数思维要点

identify变量
确定变量之间的关系
状态如何转移,驱动变量(自变量),因变量
循环不变式
有界,收敛

二分问题

输入是一个区间和一个值,要收敛到区间一个具体位置

1 标准二分
  • 变量:区间[l, r], 查找值x。衍生变量mid
  • 循环不变式:如果有解,必在[l, r]中
  • 状态转移:
    • 进行状态转移的条件:[l,r]不为空: l <= r
    • 驱动变量: x, A[mid]
    • 规则:x和A[mid]比较大小
    • 收敛:[l, r]每次都变小
int search(vector<int> &array, int target) {
    int l = 0, r = array.size() - 1;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        if (target == array[mid]) return mid;
        else if (target < array[mid]) r = mid - 1;
        else  l = mid + 1;
    }
    return -1;
}
2 lower_bound

定义:x添加到有序数组A中的位置,如果有多个位置满足条件(数组中有和x相同的值),取第一个位置

  1. 变量:[l, r],查找值x。

  2. 转移规则:

    1. 如果x > A[mid],插入位置必然落在(mid, r], l = mid + 1
    2. 如果x < A[mid],插入位置必然落在[l, mid]
    3. 如果x == A[mid], 由于lower_bound是取第一个位置,这个位置必然也落在[l, mid]
  3. 边界收敛:
    2.2 和2.3收敛的方式右侧都是mid,在单元素区间下其实没有收敛,会造成死循环。所以要避免收敛到单元素,也就是l == r 的情况下就要退出。仔细推敲循环不变式,并不是说x必须在[l, r]内,而是递归的思想,原问题的解(x应该放到原数组的位置)等价于一个小规模的相同的问题的解,即x应该放到当前数组的位置。这样理解,2.2和2.3的转移方式都应该是 [l, mid -1]。最后一次收敛是一个单元素区间,l = mid = r, 若x > A[mid], l = mid + 1即l移到右边一个位置;若x <= A[mi],r = mid - 1, 即r移到左边一个位置。所求的位置两种情况下都是l。

int lower_bound(vector<int> &array, int target) {
    int l = 0, r = array.size() - 1;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        if (target <= array[mid]) r = mid - 1;
        else l = mid + 1;
    }
    return l;
}
3 upper_bound

定义:x添加到有序数组A中的位置,如果有多个位置满足条件(数组中有和x相同的值),取最后一个位置
转移规则:
1. 当x > A[mid], 问题递归到[mid + 1, r]
2. 当x == A[mid], 问题同样递归到[mid + 1]
3. 当 x < A[mid], 问题递归到[l, mid - 1],原理和lower_bound相同

最后一次收敛:l = r = mid,若x >= A[mid], l = mid + 1,解为l;若x < A[mid], r = mid - 1, 解还是l。

int upper_bound(vector<int> &array, int target) {
    int l = 0, r = array.size() - 1;
    while (l <= r) {
        int mid = l + (r - l) / 2;
        if (target < array[mid]) r = mid - 1;
        else l = mid + 1;
    }
    return l;
}
4 有序数组中找和给定值最接近的值的位置

有两种思路

  1. 循环不变式:解必然是当前区间的解和目前得到的解中的最优者。
int findClosest(vector<int> &A,  int target)
{
    int minDiff = 10000000, minIndex = -1;
    int begin = 0, end = A.size() - 1;
    while (begin <= end) {
        int mid = (begin+end)/2;
        if(abs(A[mid]-target)<minDiff){
            minDiff = abs(A[mid]-target);
            minIndex = mid;
        }
        if(A[mid]<target) begin = mid+1;
        else end = mid-1;
    }
    return minIndex;
}
  1. 舍去区间的时候保留mid到剩下的区间内,这样循环不变式的一致性比较好:解必然在当前区间内。
int findClosest2(vector<int> &A, int target) {
    int l = 0, r = A.size() -1;
    while (l < r - 1) {
        int mid = l + (r - l) / 2;
        if (target == A[mid]) return mid;
        if (target < A[mid]) r = mid;
        else l = mid;
    }
    if (abs(A[l] - target) <= abs(A[r] - target)) return l;
    else return r;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值