二分搜索1

这篇博客探讨了二分搜索算法在多种编程问题中的应用,包括查找山脉数组中的目标值、寻找乘法表中的第k个数字、找到两个正序数组的中位数等。文章详细介绍了如何利用二分搜索的原理来解决这些问题,同时提供了具体的代码实现,如lower_bound和upper_bound函数的模拟。此外,还讨论了在线查找子数组中占绝大多数的元素的策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

0 二分搜索以及实现

我们把普通人的实现:在递增数组里面,搜索target:
最后一定是通过st+=1,使得st == ed然后退出循环,所以我们知道,下面的二分找到的一定是第一个 >= target的值,这也是lower_bound的实现

        ed = peakIdx, st = 0; 
        int mid;
        while(st < ed) {
            mid = (st + ed) >> 1;
            int h = mountainArr.get(mid);
            if(h < target) { // 注意最后的收敛一定是st += 1去收敛的
                st = mid + 1;
            } else {
                ed = mid;
            }
        }
        if(mountainArr.get(st) == target) {
            return st;
        }
// lower_bound函数的实现: ------------------------------------------
template<class ForwardIt, class T>
ForwardIt lower_bound(ForwardIt first, ForwardIt last, const T& value)
{
    ForwardIt it;
    typename std::iterator_traits<ForwardIt>::difference_type count, step;
    count = std::distance(first, last);
 
    while (count > 0) {
        it = first; // st 
        step = count / 2; // mid 
        std::advance(it, step); // vec[mid]
        if (*it < value) { // vec[mid] < value
            first = ++it;  // st = mid + 1
            count -= step + 1; // ed mot move, but st move forward,so move count backward
        }
        else
            count = step; // ed = mid
    }
    return first;
}

// upper_bound函数的实现: ------------------------------------------
// 看到!(value < *it) 也就是 value >= *it 
// 这就是说,当*it小于等于目标,那么st++,也就是找到的数为第一个大于value的值
template<class ForwardIt, class T>
ForwardIt upper_bound(ForwardIt first, ForwardIt last, const T& value)
{
    ForwardIt it;
    typename std::iterator_traits<ForwardIt>::difference_type count, step;
    count = std::distance(first, last);
 
    while (count > 0) {
        it = first; 
        step = count / 2; 
        std::advance(it, step);
        if (!(value < *it)) {
            first = ++it;
            count -= step + 1;
        } 
        else
            count = step;
    }
    return first;
}

1 例题

1157majorityChecker 子数组中占绝大多数的元素

1 题目

https://leetcode.cn/problems/online-majority-element-in-subarray/

2 解题思路

  • 1 解题思路:
    • 1.1 首先统计所有元素的下标
    • 1.2 对于l,r,thres的一个搜索,对所有元素遍历,找出第一个满足要求的元素返回即可:
      • 1.2.1 首先该元素的个数要大于等于thres
      • 1.2.2 在该元素的出现下标的列表中二分搜索,找到第一个大于等于l的下标,和第一个大于r的下标,分别对应到lower_bound和upper_bound
class MajorityChecker {
public:
    vector<int> vec;
    unordered_map<int, vector<int>> toPos;
    MajorityChecker(vector<int>& arr) {
        int pos = 0;
        for(auto& num : arr) {
            toPos[num].push_back(pos++);;
        }
    }
    
    int query(int left, int right, int threshold) {
        int res = -1;
        for(auto& [num, positions] : toPos) {
            if(positions.size() < threshold) {
                continue;
            }
            // for num : find the min and max pos in [left, right]
            auto minPos = lower_bound(positions.begin(), positions.end(), left);
            auto maxPos = upper_bound(positions.begin(), positions.end(), right);
            if (maxPos - minPos >= threshold) {
                return num;
            }
        }

        return res;
        
    }
};

1095findInMountainArray 山脉数组中查找目标值

1 题目

https://leetcode.cn/problems/find-in-mountain-array/

2 解题思路

  • 1 解题思路:
    • 1.1 二分法:首先找到山顶,接着在两边分别二分找target就行
class Solution {
public:
    int findInMountainArray(int target, MountainArray &mountainArr) {
        int len = mountainArr.length();
        int ed = len-1, st = 0; 
        int peakIdx;
        while(st < ed) {
            peakIdx = (st + ed) >> 1;
            // cout << "st/ed/mid: " << st << " " << ed << " " << peakIdx << endl;
            int h = mountainArr.get(peakIdx);
            int lh = mountainArr.get(peakIdx - 1);
            int rh = mountainArr.get(peakIdx + 1);
            if(lh < h && h < rh) {
                st = peakIdx + 1;
            } else if(lh > h && h > rh){
                ed = peakIdx;
            } else {
                st = peakIdx;
                break;
            }
        }
        peakIdx = st;
        // cout << "peak is : " << st << "  " << mountainArr.get(st) <<endl;
        
        ed = peakIdx, st = 0; 
        int mid;
        while(st < ed) {
            mid = (st + ed) >> 1;
            int h = mountainArr.get(mid);
            if(h < target) {
                st = mid + 1;
            } else {
                ed = mid;
            }
        }
        if(mountainArr.get(st) == target) {
            return st;
        }

        ed = len-1, st = peakIdx; 
        while(st < ed) {
            mid = (st + ed) >> 1;
            int h = mountainArr.get(mid);
            if(h > target) {
                st = mid + 1;
            } else {
                ed = mid;
            }
        }
        if(mountainArr.get(st) == target) {
            return st;
        }

        return -1;
    }
};

0878nthMagicalNumber 第 N 个神奇数字

1 题目

https://leetcode.cn/problems/nth-magical-number/

2 解题思路

  • 1 解题思路:
    • 1.1 经典二分:并不是遍历去搜索,而是在答案的范围内,去二分,对于每各数字判断它和最终要的结果的大小,决定下一个二分的范围
    • 1.2 具体来说,比如这道题:f(x)表示数字x是第几个magicnumber,很容易知道f(x)单调递增,则可以二分
    • 1.3 f(x) = x/a + x/b - x/LCM(a,b)
class Solution {
public:
    long long getLCM(long long x, long long y) {
        return x * y / getGCD(x, y);
    }
    
    long long getGCD(long long x, long long y) {
        long long l = min(x, y);
        long long g = max(x, y);
        long long mod = g % l;
        while(mod != 0) {
            g = l;
            l = mod;
            mod = g % l;
        }
        return l;
    }
    
    constexpr static int MOD = 1'000'000'007;

    int nthMagicalNumber(int n, int a, int b) {
        long long lcm = getLCM(a, b);
        auto getMagicRank = [&](long long x) {
            return x / a + x / b - x / lcm;
        };
        long long st = 0;
        long long ed = 1e15;

        long long mid = -1;
        while(st != ed) {
            mid = (st + ed) >> 1;
            
            long long rank = getMagicRank(mid);
            // cout << "st/ed/mid: " << st << "/" << ed << "/" << mid << " | " << rank << endl;
            if(rank >= n) {
                ed = mid;
            } else {
                st = mid + 1;
            }
        }

        return st % MOD;
    }
};

0668findKthNumber 乘法表中第k小的数

1 题目

https://leetcode.cn/problems/kth-smallest-number-in-multiplication-table/

2 解题思路

  • 1 解题思路:
    • 1.1 找乘法表中的第k个数字,应该考虑在1到m*n中,找一个最小的数字mid,小于等于mid的数字,刚好有k个
    • 1.2 为何能够保证,比如对于3*3的乘法表展开:(12233 4669),找的是第7个或者第8个数字,那么二分搜索的过程如下:
    • 1.3 从过程中可以发现,当目前的mid满足了大于等于k个的时候,每次ed = mid,下一个mid就会减小,比如从st/ed的6,9变成6,7,最后搜索到6,也就是,找一个最小的数字,乘法表中有k个小于等于他,最后一定会搜索到它,我们可以反证法:
    • 1.4 假设从1到m*n,找到一个数字有k个小于等于他,记作x,他出现在这个乘法表中,假设它不是最小的,那么也就是说存在一个数字y,y < x出现在乘法表中,且有k个数字小于等于他,很显然是错的,因为这样x,就至少是有k+1个数字小于等于他,和前提相反!

st/ed/cnt: 1/9/6 | 5
st/ed/cnt: 6/9/8 | 7
st/ed/cnt: 6/7/8 | 6

class Solution {
public:
    int findKthNumber(int m, int n, int k) {
        // 对于每一个数字x,他在乘法表中是多大的?
        // 那么求解方案就变成了:
        // 找到最小的数字,这个数字有k个小于等于他
        int st = 1, ed = m*n, mid;
        while(st != ed) {
            // 小于等于mid的个数为cnt
            mid = (st + ed) >> 1;
            int cnt = 0;
            for(int line = 1; line <= m; ++line) {
                cnt += min(mid / line, n);
            }
            
            cout << "st/ed/cnt: " << st << "/" << ed << "/" << cnt << " | " << mid  << endl;
            if(cnt >= k) {
                ed = mid;
            } else {
                st = mid + 1;
            }
        }
        return st;

    }
};

0004findMedianSortedArrays 寻找两个正序数组的中位数

1 题目

https://leetcode.cn/problems/median-of-two-sorted-arrays/

2 解题思路

  • 1 解题思路:
    • 1.1 将直接二分找目标数的思路转换一下,找两个数组中第k大的数字
    • 1.2 那么通过比较 A[k/2 - 1]和B[k/2 - 1]这两个数字,我们能发现,在这两个数字左侧(不包含这两个数字)最多有k - 2个,这样最答案一定不在前面的AB段里面,可以去掉A的或者B的[:k/2 - 1]这个部分的数字,然后更新k(k代表着我们还要去掉多少个数字),同时更新stA和stB标记AB的起点
    • 1.3 退出的过程:当 1 = k的时候,意味着从AB当前的起点,仅需要找一个数字即可,那么自然是两个的最小值
    • 1.4 考虑,肯定会出现A或者B的下标溢出情况,那么由于我们知道还有多少个数字要数,那么直接在另一个不溢出的数组里返回结果即可!
class Solution {
public:
    int getKthElement(vector<int>& nums1, vector<int>& nums2, int k) {
        // cout <<"------------" << endl;
        int idx1 = k / 2 - 1;
        int m = nums1.size();
        int n = nums2.size();
        int st1 = 0;
        int st2 = 0;

        while(true) {
            // 当k消耗殆尽,则直接返回结果
            // cout << "m/n" << m << " " << n << endl;
            if(st1 == m) {
                // cout <<"aa" << endl;
                return nums2[st2 + k - 1];
            }
            if(st2 == n) {
                return nums1[st1 + k - 1];
            }
            if(k == 1) {
                // cout << st1 << " | " << st2 << endl;
                return min(nums1[st1], nums2[st2]);
            }

            int nextSt1 = min(st1 + k / 2 - 1, m - 1);
            int nextSt2 = min(st2 + k / 2 - 1, n - 1);
            if(nums1[nextSt1] > nums2[nextSt2]) {
                k = k - (nextSt2 + 1 - st2); // 排除了这么些个数字
                st2 = nextSt2 + 1;
                // cout << "st2: " << st2 << endl;
            } else {
                k = k - (nextSt1 + 1 - st1);
                st1 = nextSt1 + 1;
                // cout << "st1: " << st1 << endl;
            }
        }

        return -1;
    }


    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int len = nums1.size() + nums2.size();
        if(len % 2 == 1) {
            return getKthElement(nums1, nums2, len / 2 + 1);
        }
        return static_cast<double>(getKthElement(nums1, nums2, len / 2 + 1) + getKthElement(nums1, nums2, len / 2)) / 2;

    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值