在有序旋转数组中找到一个数

在有序旋转数组中找到一个数

题目描述

有序数组arr可能经过一次旋转处理,也可能没有,且arr可能存在重复的数。例如,有序数组[1, 2, 3, 4, 5, 6, 7],可以旋转处理成[4, 5, 6, 7, 1, 2, 3]等。给定一个可能旋转过的有序数组arr,再给定一个数num,返回arr中是否含有num

关于旋转操作:可以简单的理解为把序列从某个位置切成两段然后交换位置

[要求]

期望复杂度为 O ( log ⁡ n ) O(\log n) O(logn)

输入描述:

第一行两个整数N, num。分别表示数组大小, 需要找的数。
接下来一行N个整数表示数组内的数。

输出描述:

若num存在于数组中,输出"Yes",否则输出"No"

示例1
输入
7 7
4 5 6  7 1 2 3
输出
Yes
示例2
输入
7 998244353
4 5 6  7 1 2 3
输出
No
备注:

1 ⩽ N ⩽ 1 0 5 1 \leqslant N \leqslant 10^5 1N105
1 ⩽ a r r i ⩽ 1 0 9 1 \leqslant arr_i \leqslant 10^9 1arri109


解法一(两次二分):

参考 在有序旋转数组中找到最小值 一题,如果我们可以找到分界点,就可以确定 num 在左右哪个区间,然后直接在该区间上进行正常的二分查找即可。

把复杂问题分解成简单的子问题 思想很重要。

解法一代码:
#include <cstdio>

using namespace std;

const int N = 100000;

int n, k;
int a[N];

int main(void) {
    scanf("%d%d", &n, &k);
    for ( int i = 0; i <n; ++i )
        scanf("%d", a + i);
    --n;
    while ( n > 0 && a[n] == a[0] ) --n;
    if ( a[0] == a[n] ) {
        puts( a[0] == k ? "Yes" : "No");
        return 0;
    }
    int l = 0, r = n, m;
    if ( a[n] < a[0] ) {
        while ( l < r ) {
            m = l + r >> 1;
            if ( a[m] >= a[0] ) l = m + 1;
            else r = m;
        }
        if ( k <= a[n] ) r = n;
        else l = 0, --r;
    }
    while ( l < r ) {
        m = l + r >> 1;
        if ( a[m] == k ) {
            puts("Yes");
            return 0;
        }
        if ( a[m] > k ) r = m;
        else l = m + 1;
    }
    puts( a[r] == k ? "Yes" : "No");
    return 0;
}
解法二(一次二分):

还是参考 在有序旋转数组中找到最小值 。我们可以在二分过程中,通过一些条件判断 num 在哪个区间:

  • a [ m i d ] > = a [ 0 ] a[mid] >= a[0] a[mid]>=a[0],说明 mid 在左边区间:
    1. n u m > = a [ l ] a n d n u m < = a [ m i d ] num >= a[l] \quad and \quad num <= a[mid] num>=a[l]andnum<=a[mid],说明此时要在区间 [l,mid] 查找, r = mid;
    2. n u m < a [ l ] num < a[l] num<a[l],说明 num 在右边区间,l = mid + 1;
    3. n u m > a [ m i d ] num > a[mid] num>a[mid],说明 num 在 mid 右边,l = mid + 1;
  • a [ m i d ] < a [ 0 ] a[mid] < a[0] a[mid]<a[0],说明 mid 在右边区间:
    1. n u m < = a [ m i d ] num <= a[mid] num<=a[mid],说明 num 在 mid 左边,r = mid;
    2. 若 $num > a[n] $,说明 num 在左边区间,r = mid;
    3. 否则的话,说明 num 在 mid 右边,l = mid + 1;
解法二代码:
#include <cstdio>

using namespace std;

const int N = 100000;

int n, k;
int a[N];

int main(void) {
    scanf("%d%d", &n, &k);
    for ( int i = 0; i <n; ++i )
        scanf("%d", a + i);
    --n;
    while ( n > 0 && a[n] == a[0] ) --n;
    if ( a[0] == a[n] ) {
        puts( a[0] == k ? "Yes" : "No");
        return 0;
    }
    int l = 0, r = n, m;
    while ( l < r ) {
        m = l + r >> 1;
        if ( a[m] == k ) {
            puts("Yes");
            return 0;
        }
        if ( a[m] >= a[0] ) {
            if ( a[l] <= k && k <= a[m] ) r = m;
            else l = m + 1;
        } else {
            if ( k <= a[m] || k > a[n]) r = m;
            else l = m + 1;
        }
    }
    puts( a[r] == k ? "Yes" : "No");
    return 0;
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值