【前言】二分复习巩固 。OJ:acwing + leetcode
1 第一个错误的版本
题目描述
你是产品经理,目前正在带领一个团队开发新的产品。不幸的是,你的产品的最新版本没有通过质量检测。由于每个版本都是基于之前的版本开发的,所以错误的版本之后的所有版本都是错的。
假设你有 n 个版本 [1, 2, …, n],你想找出导致之后所有版本出错的第一个错误的版本。
你可以通过调用 bool isBadVersion(version) 接口来判断版本号 version 是否在单元测试中出错。实现一个函数来查找第一个错误的版本。你应该尽量减少对调用 API 的次数。
样例
示例:
给定 n = 5,并且 version = 4 是第一个错误的版本。
调用 isBadVersion(3) -> false
调用 isBadVersion(5) -> true
调用 isBadVersion(4) -> true
所以,4 是第一个错误的版本。
笔记
算法思想:参照模板2(l+r>>1 不用 +1),此题应当选择右半边第一个为 true 的版本
代码
// Forward declaration of isBadVersion API.
bool isBadVersion(int version);
class Solution {
public:
int firstBadVersion(int n) {
long long l=1,r=n; //注意是否可能越界
while(l<r){
int mid=l+r>>1;
if(isBadVersion(mid)) r=mid;
else l=mid+1;
}
return r;
}
};
2 寻找峰值
题目描述
峰值元素是指其值大于左右相邻值的元素。
给定一个输入数组 nums,其中 nums[i] ≠ nums[i+1],找到峰值元素并返回其索引。
数组可能包含多个峰值,在这种情况下,返回任何一个峰值所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞。
说明:
你的解法应该是 O(logN) 时间复杂度的。
样例
示例 1:
输入: nums = [1,2,3,1]
输出: 2
解释: 3 是峰值元素,你的函数应该返回其索引 2。
示例 2:
输入: nums = [1,2,1,3,5,6,4]
输出: 1 或 5
解释: 你的函数可以返回索引 1,其峰值元素为 2;
或者返回索引 5, 其峰值元素为 6。
笔记
- 普通解法:判断第一个和最后一个数——>判断中间数字 num[i]大于num[i-1]和num[i+1],返回i。线性,时间复杂度为 o(n), 大佬说是最辣眼睛的做法。。。
- 95%的二分题目存在两段性,直接可以看出使用二分法,而此题属于虽不存在此性质,但刚好可以通过二分把答案缩小一半!
- 二分 :时间复杂度o(logn) , 不论何种情况一定存在一个峰值 。某个结点小于下一个结点,那么从下一个结点往后,一定有一个峰值,极端情况,当持续递增时,最后一个结点为峰值;某个结点大于下一个结点,则从 0 到此结点种一定存在一个峰值,极端情况为,持续递减时,第一个结点为峰值
- while(l<r) 当 l=r 直接跳出,不进入循环,所以不存在mid = l = r =最后结点的情况
代码
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int l=0,r=nums.size()-1;
while(l<r){
int mid=l+r>>1;
if(nums[mid+1]>nums[mid]) l=mid+1;
else r=mid;
}
return l; //边界不需单独判断,若递增到最后直接返回最后一个元素
}
};
3 寻找重复数
题目描述
给定一个包含 n + 1 个整数的数组 nums,其数字都在 1 到 n 之间(包括 1 和 n),可知至少存在一个重复的整数。假设只有一个重复的整数,找出这个重复的数。
说明:
不能更改原数组(假设数组是只读的)。
只能使用额外的 O(1) 的空间。
时间复杂度小于 O(n2) 。
数组中只有一个重复的数字,但它可能不止重复出现一次。
样例
示例 1:
输入: [1,3,4,2,2]
输出: 2
示例 2:
输入: [3,1,3,4,2]
输出: 3
笔记
- 抽屉原理。一分为二,统计左边的个数和右边的个数,至少会有一边个数大于抽屉个数,在这里面找答案,直到l==r
- 两边可能同时有答案,也可能一边有答案,至少一边存在答案
代码
普通方法:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n= nums.size();
int ans;
int used[n]={0};
for(int i=0;i<n;i++){
if(used[nums[i]]==0) used[nums[i]] ++;
else {
ans=nums[i];
break;
}
}
return ans;
}
};
二分法:
class Solution {
public:
int findDuplicate(vector<int>& nums) {
int n=nums.size()-1;
int l=1,r=n; //下标0——size-1 装的 1——n, 下标+1
while(l<r){
int mid=l+r>>1; //l==1 不用l+r+1>>1
int cnt=0; //统计左边的个数
for(auto x:nums)
if(x>=1 && x<=mid) cnt++;
if(cnt > mid-l+1) r=mid;
else l=mid+1;
}
return r; //为避免越界,选左边return r;选右边return l
}
};
4 H指数 II
题目描述
给定一位研究者论文被引用次数的数组(被引用次数是非负整数),数组已经按照升序排列。编写一个方法,计算出研究者的 h 指数。
h 指数的定义: “h 代表“高引用次数”(high citations),一名科研人员的 h 指数是指他(她)的 (N 篇论文中)至多有 h 篇论文分别被引用了至少 h 次。(其余的 N - h 篇论文每篇被引用次数不多于 h 次。)"
说明:
如果 h 有多有种可能的值 ,h 指数是其中最大的那个。
样例
示例:
输入: citations = [0,1,3,5,6]
输出: 3
解释: 给定数组表示研究者总共有 5 篇论文,每篇论文相应的被引用了 0, 1, 3, 5, 6 次。
由于研究者有 3 篇论文每篇至少被引用了 3 次,其余两篇论文每篇被引用不多于 3 次,所以她的 h 指数是 3。
笔记
- 二分法:【至少存在h个数,大于h,求最大的h】
- 用模板1,选择左边小那部分继续循环
代码
class Solution {
public:
int hIndex(vector<int>& nums) {
int l=0,r=nums.size();
while(l<r){
int mid=l+r+1>>1;
if(nums[nums.size()-mid]>=mid) l=mid;
else r=mid-1;
}
return r;
}
};
5 P1090 合并果子
题目描述
在一个果园里,多多已经将所有的果子打了下来,而且按果子的不同种类分成了不同的堆。多多决定把所有的果子合成一堆。
每一次合并,多多可以把两堆果子合并到一起,消耗的体力等于两堆果子的重量之和。可以看出,所有的果子经过 n-1n−1 次合并之后, 就只剩下一堆了。多多在合并果子时总共消耗的体力等于每次合并所耗体力之和。
因为还要花大力气把这些果子搬回家,所以多多在合并果子时要尽可能地节省体力。假定每个果子重量都为 11 ,并且已知果子的种类 数和每种果子的数目,你的任务是设计出合并的次序方案,使多多耗费的体力最少,并输出这个最小的体力耗费值。
例如有 33 种果子,数目依次为 11 , 22 , 99 。可以先将 11 、 22 堆合并,新堆数目为 33 ,耗费体力为 33 。接着,将新堆与原先的第三堆合并,又得到新的堆,数目为 1212 ,耗费体力为 1212 。所以多多总共耗费体力 =3+12=15=3+12=15 。可以证明 1515 为最小的体力耗费值。
样例
输入 #1
3
1 2 9
输出 #1
15
笔记
哈夫曼树/最小生成树
代码
#include<iostream>
#include<algorithm>
using namespace std;
int main(){
int n,val[10000],ans=0;
cin>>n;
for(int i=0;i<n;i++){
cin>>val[i];
}
sort(val,val+n);
for(int i=0;i<n-1;i++){
val[i+1]=val[i]+val[i+1];
ans+=val[i+1];
int j=i+1,temp=val[i+1];
while(temp>val[j+1]&&j<n-1){
val[j]=val[j+1];
j++;
}
val[j]=temp;
}
cout<<ans<<endl;
return 0;
}