手撕力扣之二分(下篇):x 的平方根、寻找峰值、寻找两个正序数组的中位数、山脉数组中查找目标值、最长递增子序列、俄罗斯套娃信封问题、分割数组的最大值

本文详细介绍了如何使用二分查找算法解决力扣上的几道经典问题,包括计算平方根、寻找峰值、寻找两个正序数组的中位数、在山脉数组中查找目标值、找到最长递增子序列以及解决俄罗斯套娃信封问题和分割数组最大值的问题。通过对不同场景的分析,深入理解二分查找的运用技巧。

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

力扣069. x 的平方根
实现 int sqrt(int x) 函数。
计算并返回 x 的平方根,其中 x 是非负整数。
由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。

class Solution {
public:
    int mySqrt(int x) {
        //注:在中间过程计算平方的时候可能出现溢出,所以用long long。
        long long i=0;
        long long j=x/2+1;//对于一个非负数n,它的平方根不会大于(n/2+1)
        while(i<=j)
        {
            long long mid=(i+j)/2;
            long long res=mid*mid;
            if(res==x) return mid;
            else if(res<x) i=mid+1;
            else j=mid-1;
        }
        return j;
    }
};

作者:chenlele
链接:https://leetcode-cn.com/problems/sqrtx/solution/x-de-ping-fang-gen-by-gpe3dbjds1/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

求x的平方根,精确到6位小数

#include <iostream>
using namespace std;
 
double sqrtCount(double x, double precision) {
	if (x < 0)
		return -1;
	double low = 1, up = x;
	if (x > 0 && x < 1)
		low = x, up = 1;
	while (low <= up) {
		double mid = low + (up - low) / 2.0;
		if (abs(mid*mid - x) <= precision)
			return mid;
		else if (mid*mid > x)
			up = mid;
		else if (mid*mid < x)
			low = mid;
	}
	return -1;
}
 
int main() {
	double x = 11, precision = 0.000001;
	double res = sqrtCount(x, precision);
	cout << res << endl;
	cin.get();
	return 0;
}

力扣162. 寻找峰值
峰值元素是指其值大于左右相邻值的元素。
给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。
数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞。
思路:为什么二分查找大的那一半一定会有峰值呢?(即nums[mid]<nums[mid+1]时,mid+1~N一定存在峰值) 我的理解是,首先已知 nums[mid+1]>nums[mid],那么mid+2只有两种可能,一个是大于mid+1,一个是小于mid+1,小于mid+1的情况,那么mid+1就是峰值,大于mid+1的情况,继续向右推,如果一直到数组的末尾都是大于的,那么可以肯定最后一个元素是峰值,因为nums[nums.length]=负无穷。
总结:上坡必有坡顶

int findPeakElement(vector<int>& nums) {
    int left = 0, right = nums.size() - 1;
    for (; left < right; ) {
        int mid = left + (right - left) / 2;
        if (nums[mid] > nums[mid + 1]) {
            right = mid;
        } else {
            left = mid + 1;
        }
    }
    return left;
}

力扣004. 寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
思路:
在这里插入图片描述

`class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int m = nums1.size();
        int n = nums2.size();
        if((m + n) % 2 == 0) return (
                                       getKthNum(nums1, nums2, (m+n)/2)
                                     + getKthNum(nums1, nums2, (m+n)/2 + 1)
                                    )/ 2;
        else return getKthNum(nums1, nums2, (m+n)/2 + 1); 
    }

    double getKthNum(vector<int>& nums1, vector<int>& nums2, int k) {
        int m = nums1.size();
        int n = nums2.size();
        int offset1 = 0;
        int offset2 = 0;

        while(true){
            if(offset1 == m) return nums2[offset2 + k - 1];
            if(offset2 == n) return nums1[offset1 + k - 1];
            if(k==1) return min(nums1[offset1], nums2[offset2]);

            int index1 = min(offset1 + k/2 -1, m-1);
            int index2 = min(offset2 + k/2 -1, n-1);
            if(nums1[index1] <= nums2[index2]){
                k -= index1 - offset1 + 1;
                offset1 = index1 + 1;
            }
            else{
                k -= index2 - offset2 + 1;
                offset2 = index2 + 1;
            }
        }
    }

};


作者:ding-bin-cheng
链接:https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xun-zhao-liang-ge-zheng-xu-shu-zu-de-zho-nbtz/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。`

力扣1095. 山脉数组中查找目标值
给你一个 山脉数组 mountainArr,请你返回能够使得 mountainArr.get(index) 等于 target 最小 的下标 index 值。
如果不存在这样的下标 index,就请返回 -1。
何为山脉数组?如果数组 A 是一个山脉数组的话,那它满足如下条件:
首先,A.length >= 3
其次,在 0 < i < A.length - 1 条件下,存在 i 使得:
A[0] < A[1] < … A[i-1] < A[i]
A[i] > A[i+1] > … > A[A.length - 1]
你将 不能直接访问该山脉数组,必须通过 MountainArray 接口来获取数据:
MountainArray.get(k) - 会返回数组中索引为k 的元素(下标从 0 开始)
MountainArray.length() - 会返回该数组的长度
思路:
1.先二分找到峰顶
2.对左右区间二分找target

class Solution {
    int n;
public:
    int findInMountainArray(int target, MountainArray& mountainArr) {
        n = mountainArr.length();
        int top = findTop(mountainArr);

        int pos1 = help1(0, top, target, mountainArr);
        if (pos1 != -1) return pos1;
        int pos2 = help2(top + 1, n - 1, target, mountainArr);
        return pos2;
    }

    int findTop(MountainArray& mountainArr) {
        int l = 0, r = n - 1;
        while (l < r) {
            int mid = (l + r) >> 1;
            if (mountainArr.get(mid) < mountainArr.get(mid + 1)) l = mid + 1;
            else r = mid;
        }
        return l;
    }

    int help1(int l, int r, const int& target, MountainArray& mountainArr) {
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (mountainArr.get(mid) == target) return mid;
            if (mountainArr.get(mid) < target) l = mid + 1;
            else r = mid - 1;
        }
        return -1;
    }

    int help2(int l, int r, const int& target, MountainArray& mountainArr) {
        while (l <= r) {
            int mid = (l + r) >> 1;
            if (mountainArr.get(mid) == target) return mid;
            if (mountainArr.get(mid) > target) l = mid + 1;
            else r = mid - 1;
        }
        return -1;
    }
};

作者:heygary
链接:https://leetcode-cn.com/problems/find-in-mountain-array/solution/c-zhong-gui-zhong-ju-de-8msjie-fa-er-fen-shi-jian-/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

力扣300. 最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
思路:
在这里插入图片描述

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n=(int)nums.size();
        if (n == 0) return 0;
        vector<int> dp(n, 0);
        for (int i = 0; i < n; ++i) {
            dp[i] = 1;
            for (int j = 0; j < i; ++j) {
                if (nums[j] < nums[i]) {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
        }
        return *max_element(dp.begin(), dp.end());
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/zui-chang-shang-sheng-zi-xu-lie-by-leetcode-soluti/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

贪心+二分算法
在这里插入图片描述

#include <iostream>
#include <vector>

using namespace std;

class Solution {
public:
    int lengthOfLIS(vector<int> &nums) {
        int len = nums.size();
        if (len < 2) {
            return len;
        }

        vector<int> tail;
        tail.push_back(nums[0]);
        // tail 结尾的那个索引
        int end = 0;

        for (int i = 1; i < len; ++i) {
            if (nums[i] > tail[end]) {
                tail.push_back(nums[i]);
                end++;
            } else {
                int left = 0;
                int right = end;
                while (left < right) {
                    int mid = (left + right) >> 1;
                    if (tail[mid] < nums[i]) {
                        left = mid + 1;
                    } else {
                        right = mid;
                    }
                }
                tail[left] = nums[i];
            }
        }
        return end + 1;
    }
};

作者:liweiwei1419
链接:https://leetcode-cn.com/problems/longest-increasing-subsequence/solution/dong-tai-gui-hua-er-fen-cha-zhao-tan-xin-suan-fa-p/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

求出字典序最小的最长递增子序列
第二步求子序列的说明:
怎么找到字典序最小的解
需要 maxLen[] 记录以 arr[i] 为结尾的最长子序列的长度。
本例 maxLen [ 1 1 2 2 3 3 4 5 4 ]
又贪: 当 maxLen[i] == maxLen[i - 1] ,最小字典序要选 arr[i] ,跳过 i - 1。 反证 :本例 maxLen[4] maxLen[5]均为3 ,对应元素 6、4 。 如果换位成 4、6,那么 maxLen[5] 的将变为 4 ,矛盾。 (即 maxLen[i] == maxLen[i - 1] 推出 arr[i] <= arr[i - 1])
为了回避掉 arr[8] 对于 end[4] 的无效更新(因为 arr[7] 是最长序列结尾)

//
// Created by jt on 2020/9/14.
//
#include <vector>
#include <algorithm>
using namespace std;

class Solution {
public:
    /**
     * return the longest increasing subsequence
     * @param arr int整型vector the array
     * @return int整型vector
     */
    vector<int> LIS(vector<int>& arr) {
        // write code here
        // 第一步:利用贪心+二分求最长递增子序列长度
        vector<int> res;
        vector<int> maxLen;
        if (arr.size() < 1) return res;
        res.emplace_back(arr[0]);  // 注:emplace_back(val)作用同push_back,效率更高
        maxLen.emplace_back(1);
        for (int i = 1; i < arr.size(); ++i) {
            if (arr[i] > res.back()) {
                res.emplace_back(arr[i]);
                maxLen.emplace_back(res.size());
            } else {
                // lower_bound(begin, end, val)包含在<algorithm>中
                // 它的作用是返回有序数组begin..end中第一个大于等于val的元素的迭代器
                int pos = lower_bound(res.begin(), res.end(), arr[i]) - res.begin();
                res[pos] = arr[i];
                maxLen.emplace_back(pos+1);
            }
        }
        // 第二步:填充最长递增子序列
        for (int i = arr.size()-1, j = res.size(); j > 0; --i) {
            if (maxLen[i] == j) {
                res[--j] = arr[i];
            }
        }
        return res;
    }
};

力扣354. 俄罗斯套娃信封问题
给你一个二维整数数组 envelopes ,其中 envelopes[i] = [wi, hi] ,表示第 i 个信封的宽度和高度。
当另一个信封的宽度和高度都比这个信封大的时候,这个信封就可以放进另一个信封里,如同俄罗斯套娃一样。
请计算 最多能有多少个 信封能组成一组“俄罗斯套娃”信封(即可以把一个信封放到另一个信封里面)。
注意:不允许旋转信封。
思路:转换成最长递增子序列
在二分查找的方法中,对宽度升序,并且在宽度相同时对高度降序的目的:
1.保证迭代排序后的envelopes时,宽度是非递减的(注意:相邻的两个envelope可能宽度相同)。
2.在相邻两个envelope宽度相同的情况时,两者的高度是非递增的,比如(3, 5), (3, 3), 不会出现“相同高度,后者套前者”的情况。

class Solution {
public:
    int maxEnvelopes(vector<vector<int>>& envelopes) {
        if (envelopes.empty()) {
            return 0;
        }
        
        int n = envelopes.size();
        sort(envelopes.begin(), envelopes.end(), [](const auto& e1, const auto& e2) {
            return e1[0] < e2[0] || (e1[0] == e2[0] && e1[1] > e2[1]);
        });

        vector<int> f = {envelopes[0][1]};
        for (int i = 1; i < n; ++i) {
            if (int num = envelopes[i][1]; num > f.back()) {
                f.push_back(num);
            }
            else {
                auto it = lower_bound(f.begin(), f.end(), num);
                *it = num;
            }
        }
        return f.size();
    }
};

作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/russian-doll-envelopes/solution/e-luo-si-tao-wa-xin-feng-wen-ti-by-leetc-wj68/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

力扣410. 分割数组的最大值
给定一个非负整数数组 nums 和一个整数 m ,你需要将这个数组分成 m 个非空的连续子数组。
设计一个算法使得这 m 个子数组各自和的最大值最小。
思路:
由题意可知:子数组的最大值是有范围的,即在区间 [max(nums),sum(nums)]之中。
令 l=max(nums),h=sum(nums),mid=(l+h)/2,计算数组和最大值不大于mid对应的子数组个数 cnt(这个是关键!)
如果 cnt>m,说明划分的子数组多了,即我们找到的 mid 偏小,故 l=mid+1;
否则,说明划分的子数组少了,即 mid 偏大(或者正好就是目标值),故 h=mid。
再理解:
我先猜一个mid值,然后遍历数组划分,使每个子数组和都最接近mid(贪心地逼近mid),这样我得到的子数组数一定最少;
如果即使这样子数组数量仍旧多于m个,那么明显说明我mid猜小了,因此 lo = mid + 1;
而如果得到的子数组数量小于等于m个,那么我可能会想,mid是不是有可能更小?值得一试。因此 hi = mid;

int splitArray(vector<int>& nums, int m) {
        long l = nums[0], h = 0;//int类型在这里不合适,因为h可能会超过int类型能表示的最大值
        for (auto i : nums)
        {
            h += i;
            l = l > i ? l : i;
        }
        while (l<h)
        {
            long mid = (l + h) / 2;
            long temp = 0;
            int cnt = 1;//初始值必须为1
            for(auto i:nums)
            {
                temp += i;
                if(temp>mid)
                {
                    temp = i;
                    ++cnt;
                }
            }
            if(cnt>m)
                l = mid + 1;
            else
                h = mid;
        }
        return l;
    }

作者:coder233
链接:https://leetcode-cn.com/problems/split-array-largest-sum/solution/er-fen-cha-zhao-by-coder233-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值