胡凡算法笔记—二分算法
1. 在严格递增序列中找到一个数x
注意只能是严格递增的序列中找到一个数x,而不是在单调递增,或者单调递减中找数。
#include<iostream>
using namespace std;
int n;
const int Max = 1e+5;
int a[Max];
int binarySearch(int x){
int l = 0;
int r = n-1; // 使用的是闭区间[0,n-1]
int mid = 0;
while(l<=r){//注意使用的是闭区间
mid = (l+r)/2;
if (a[mid]==x){
return mid;
}
else if (a[mid]<x){ //a[mid]小于x则x在[mid+1,r]的部分
l = mid+1;
}
else { //大于x,则x在[l,mid-1]。
r = mid-1;
}
}
//不在队列中返回x
return -1;
}
int main(){
cin>>n;
int x;
cin>>x;
for(int i=0;i<n;i++)
cin>>a[i];
cout<<binarySearch(x);
}
2. 在递增序列中找到第一个大于等于x的序列下标
#include<iostream>
using namespace std;
int n;
const int Max = 1e+5;
int a[Max];
int binarySearch(int x){
int l = 0;
int r = n; //因为在此时要确保包括到所有的数据,所以应该是n。因此此时不确定是否是在序列中
int mid = 0;
while(l<r){//注意此时是小于号而不是等号,因为l==r是退出条件
mid = (l+r)/2;
if (a[mid]>=x){ //大于等于所以说明要查找的数在[l,mid]
r = mid;
}
else { //因为是a[mid]<x,所以大于等于的数一定在[mid+1,r]
l = mid+1;
}
}
return l; //返回条件
}
int main(){
cin>>n;
int x;
cin>>x;
for(int i=0;i<n;i++)
cin>>a[i];
cout<<binarySearch(x);
}
3. 在一个递增序列x(可能存在重复元素)中寻找第一个大于x的序列下标。
#include<iostream>
using namespace std;
int n;
const int Max = 1e+5;
int a[Max];
int binarySearch(int x){
int l = 0;
int r = n; //因为在此时要确保包括到所有的数据,所以应该是n。因此此时不确定是否是在序列中
int mid = 0;
while(l<r){//注意此时是小于号而不是等号,因为l==r是退出条件
mid = (l+r)/2;
if (a[mid]>x){ //大于所以说明要查找的数在[l,mid]
r = mid;
}
else { //因为是a[mid]=<x,所以大于数一定在[mid+1,r]
l = mid+1;
}
}
return l; //返回条件
}
int main(){
cin>>n;
int x;
cin>>x;
for(int i=0;i<n;i++)
cin>>a[i];
cout<<binarySearch(x);
}
4. 在一个递增序列x(可能存在重复元素)中寻找一个指定元素x,如果能找到,那么输出第一个x的下标;如果不能找到,那么输出−1。
可以使用2、3的两个函数的结合来实现这个问题
#include<iostream>
using namespace std;
int n;
const int Max = 1e+5;
int a[Max];
int big(int x){
int l = 0;
int r = n; //因为在此时要确保包括到所有的数据,所以应该是n。因此此时不确定是否是在序列中
int mid = 0;
while(l<r){//注意此时是小于号而不是等号,因为l==r是退出条件
mid = (l+r)/2;
if (a[mid]>x){ //大于所以说明要查找的数在[l,mid]
r = mid;
}
else { //因为是a[mid]=<x,所以大于数一定在[mid+1,r]
l = mid+1;
}
}
return l; //返回条件
}
int bigequal(int x){
int l = 0;
int r = n; //因为在此时要确保包括到所有的数据,所以应该是n。因此此时不确定是否是在序列中
int mid = 0;
while(l<r){//注意此时是小于号而不是等号,因为l==r是退出条件
mid = (l+r)/2;
if (a[mid]>=x){ //大于等于所以说明要查找的数在[l,mid]
r = mid;
}
else { //因为是a[mid]<x,所以大于等于的数一定在[mid+1,r]
l = mid+1;
}
}
return l; //返回条件
}
int main(){
cin>>n;
int x;
cin>>x;
for(int i=0;i<n;i++)
cin>>a[i];
int ans1 = big(x);
int ans2 = bigequal(x);
if(ans1==ans2){
cout<<-1;
}
else{
cout<<ans2;
}
}
5. 在一个递增序列x(可能存在重复元素)中寻找一个指定元素x,如果能找到,那么输出最后一个x的下标;如果不能找到,那么输出−1。
与4的情况类似,需要做的是将最后的输出改成cout<<ans1-1
#include<iostream>
using namespace std;
int n;
const int Max = 1e+5;
int a[Max];
int big(int x){
int l = 0;
int r = n; //因为在此时要确保包括到所有的数据,所以应该是n。因此此时不确定是否是在序列中
int mid = 0;
while(l<r){//注意此时是小于号而不是等号,因为l==r是退出条件
mid = (l+r)/2;
if (a[mid]>x){ //大于所以说明要查找的数在[l,mid]
r = mid;
}
else { //因为是a[mid]=<x,所以大于数一定在[mid+1,r]
l = mid+1;
}
}
return l; //返回条件
}
int bigequal(int x){
int l = 0;
int r = n; //因为在此时要确保包括到所有的数据,所以应该是n。因此此时不确定是否是在序列中
int mid = 0;
while(l<r){//注意此时是小于号而不是等号,因为l==r是退出条件
mid = (l+r)/2;
if (a[mid]>=x){ //大于等于所以说明要查找的数在[l,mid]
r = mid;
}
else { //因为是a[mid]<x,所以大于等于的数一定在[mid+1,r]
l = mid+1;
}
}
return l; //返回条件
}
int main(){
cin>>n;
int x;
cin>>x;
for(int i=0;i<n;i++)
cin>>a[i];
int ans1 = big(x);
int ans2 = bigequal(x);
if(ans1==ans2){
cout<<-1;
}
else{
cout<<ans1-1;
}
}
6. 寻找第一个1 [左边都是0,右边都是1],没有1则输出-1
这个题目使用二分的思路就是找到第一个等于1的数
#include<iostream>
using namespace std;
const int Max = 1e+5;
int a[Max];
int n;
//二分法找1
int findone(){
int l =0;
int r = n;
int mid = 0;
while(l<r){
mid = (l+r)/2;
if(a[mid]==1){ //有1则说明找的位置在[l,mid]
r = mid;
}
else { //不是1则说明照的位置在[mid+1,r]
l = mid+1;
}
}
//如果l==n 则说明没有1
if(l==n)
return -1;
return l;
}
int main(){
cin>>n;
for(int i = 0;i < n;i++){
cin>> a[i];
}
cout<<findone();
}
7.找到第一个偶数,左边是奇数,右边是偶数。找不到则输出-1
和6的思路是一模一样的,只是中间的条件改变一下
#include<iostream>
using namespace std;
const int Max = 1e+5;
int a[Max];
int n;
//二分法找偶数
int findeven(){
int l =0;
int r = n;
int mid = 0;
while(l<r){
mid = (l+r)/2;
if(a[mid]%2==0&&a[mid]!=1){ //偶数则说明找的位置在[l,mid]
r = mid;
}
else { //不是偶数则说明照的位置在[mid+1,r]
l = mid+1;
}
}
//如果l==n 则说明没有偶数
if(l==n)
return -1;
return l;
}
int main(){
cin>>n;
for(int i = 0;i < n;i++){
cin>> a[i];
}
cout<<findone();
}
8. 快速幂
#include<iostream>
using namespace std;
int a;
int b;
int binaryPow(int n){
if(n==0){
return 1;
}
if(n%2==0){
int mut = binaryPow(n/2);
return mut*mut;
}
else {
return a*binaryPow(n-1);
}
}
int main(){
cin>>a>>b;
cout<<binaryPow(b);
}
使用递归算法来求快速幂,用的方法是如果幂的次数是偶数则 a b = a b / 2 ∗ a b / 2 a^{b} = a^{b/2}*a^{b/2} ab=ab/2∗ab/2,如果是奇数则变成 a b = a ∗ a b − 1 a^{b} = a* a^{b-1} ab=a∗ab−1 此时的 b − 1 b-1 b−1为偶数
9.单峰序列
找到单峰序列中的最大值,这个思路可以转换成,这个单峰序列的最大值的左边是a[i]-a[i-1]<0,而右边是a[i]-a[i-1]>0
则依旧可以使用二分算法来进行寻找第一个递增编成递减的
#include<iostream>
using namespace std;
int n;
const int Max = 1e+5;
int a[Max];
int binarySearch(){
int l = 0;
int r = n;
int mid = 0;
while(l<r){
mid = (l+r)/2;
if(a[mid]<a[mid-1]){
r = mid;
}
else {
l = mid +1;
}
}
return l-1;
}
int main(){
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
cout<<binarySearch();
}
10.旋转严格递增数组找数
旋转数组可以直接拆分成两个有序数组。而且前面的有序数组的值一定比后边的有序数组的值大。
#include <stdio.h>
#include<iostream>
using namespace std;
int search(int* nums, int numsSize, int target) {
if (nums == NULL || numsSize <= 0) {
return -1; // 无效输入
}
int left = 0, right = numsSize - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
return mid; // 找到目标元素
}
// 判断左侧是否有序,因为两个数组,如果left<mid则说明左侧一定是有序的,反之左侧是没有顺序的
if (nums[left] <= nums[mid]) {
// 目标值在有序的左侧部分
//因为target在有序数组target和mid中间。
if (nums[left] <= target && target < nums[mid]) {
// 目标值在左半部分
right = mid - 1;
} else {
// 目标值在右半部分
left = mid + 1;
}
} else {
// 右侧是有序的
if (nums[mid] < target && target <= nums[right]) {
// 目标值在右半部分
left = mid + 1;
} else {
// 目标值在左半部分
right = mid - 1;
}
}
}
return -1; // 未找到目标元素
}
int main() {
int n;
cin>>n;
int x;
cin>>x;
int a[n];
for(int i=0;i<n;i++){
cin>>a[i];
}
cout<<search(a, n, x);
return 0;
}
11. 非严格递增的旋转数组找数]
依旧是旋转数组,但是现在的问题是不是严格的旋转数组,即可能会同时有多个相同的数,所以需要在找到目标值之后加一个循环,来确保找到的目标值是第一个元素
#include <stdio.h>
#include<iostream>
using namespace std;
int search(int* nums, int numsSize, int target) {
if (nums == NULL || numsSize <= 0) {
return -1; // 无效输入
}
int left = 0, right = numsSize - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
// 找到目标值,需要向左查找第一个目标值
while (mid > 0 && nums[mid - 1] == target) {
mid--;
}
return mid; // 返回第一个目标值的下标
}
// 判断左侧是否有序,因为两个数组,如果left<mid则说明左侧一定是有序的,反之左侧是没有顺序的
if (nums[left] <= nums[mid]) {
// 目标值在有序的左侧部分
//因为target在有序数组target和mid中间。
if (nums[left] <= target && target < nums[mid]) {
// 目标值在左半部分
right = mid - 1;
} else {
// 目标值在右半部分
left = mid + 1;
}
} else {
// 右侧是有序的
if (nums[mid] < target && target <= nums[right]) {
// 目标值在右半部分
left = mid + 1;
} else {
// 目标值在左半部分
right = mid - 1;
}
}
}
return -1; // 未找到目标元素
}
int main() {
int n;
cin>>n;
int x;
cin>>x;
int a[n];
for(int i=0;i<n;i++){
cin>>a[i];
}
cout<<search(a, n, x);
return 0;
}
12.严格递增的旋转数组的最小值
这个是比较简单的,因为左侧都是a[i]-a[i-1]<0。其余的通过a[left]和a[mid]、a[right] 的大小来判断最小值在哪个点
#include<iostream>
using namespace std;
const int Max = 1e+5;
int a[Max];
int n;
//找到最小值
int binarySearch(){
int l = 0;
int r = n-1;
int mid = 0;
if(n==1){
return a[0];
}
while(l<=r){
mid = (l+r)/2;
if(a[mid]-a[mid-1]<0){
return a[mid];
}
else{
//在右侧
if(a[mid]>a[r]){
l = mid+1;
}
//在左侧
else if(a[mid]<a[l]){
r = mid-1;
}
}
}
}
int main(){
cin>>n;
for(int i=0;i<n;i++)
cin>>a[i];
cout<<binarySearch();
}
13.非严格递增旋转数组的最小值
这个可以直接通过a[l]、a[mid]、a[r]的大小来进行判断,缩小范围
#include<iostream>
using namespace std;
const int Max = 1e+5;
int a[Max];
int n;
// 找到最小值
int findMin() {
int l = 0;
int r = n - 1;
int mid;
while (l < r) {
mid = l + (r - l) / 2;
if (a[mid] < a[r]) {//这是因为右侧是有序的,则说明右侧的数组都比a[mid]更大
r = mid; // 最小值在左侧子数组[l,mid]
} else {//反之则说明右侧不是有序的,则在右侧
l = mid + 1; // 最小值在右侧侧子数组,或者就是mid,范围是[mid+1,r]
}
}
// l指向最小值,或者a[l]和a[r]相等
return a[l];
}
int main() {
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
cout << findMin();
return 0;
}
14.严格递增旋转数组的中位数
中位数可以通过数组中最小值的位置和整个数组的大小来进行计算中位数
如果n为奇数 :中位数的位置为 p o s = m i n p o s + n / 2 pos = minpos+n/2 pos=minpos+n/2
如果n为偶数:中位数的俩个数的位置分别为 p o s 1 = m i n p o s + n / 2 、 p o s 2 = m i n p o s + n / 2 − 1 pos1 = minpos+n/2 、pos2 = minpos+n/2-1 pos1=minpos+n/2、pos2=minpos+n/2−1
#include<iostream>
using namespace std;
const int Max = 1e+5;
int a[Max];
int n;
// 找到最小值
int findMin() {
int l = 0;
int r = n - 1;
int mid;
while (l < r) {
mid = l + (r - l) / 2;
if (a[mid] < a[r]) {
r = mid; // 最小值在右侧子数组
} else {
l = mid + 1; // 最小值在左侧子数组,或者就是mid
}
}
return l;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
int minpos = findMin();
if(n%2==0){
float ans = (a[(minpos+n/2)%n]+a[(minpos+n/2-1)%n])/2.0;
printf("%.1f",ans);
}
else{
float ans = a[(minpos+n/2)%n];
printf("%.1f",ans);
}
return 0;
}