【问题标题】:Find Min in Rotated Array在旋转数组中查找最小值
【发布时间】:2024-04-25 23:10:01
【问题描述】:

我正在尝试在已旋转的排序数组中找到最小元素。

例子:

     1 2 3 4 5 => sorted

     3 4 5 1 2 => Rotated => Find the min in this in O(log n)

我已经试着写代码了:

#include <iostream>
#include <vector>
#include <algorithm>

using namespace std;

int bs(vector<int> a)
{
    int l =0, r = a.size()-1;

    while(l<=r)
    {
        int mid = l + (r-l)/2; // Mid guaranteed to be in range

        if(a[mid-1]>a[mid]) return a[mid];

        if(a[mid]>a[l])
        {
            l=mid+1 ; continue;
        }
        else
        {
            r = mid-1 ; //Mid has been checked
        }
    }
}

int main()
{
  vector<int> a = {1,2,3,5,6,7,8,9,10};
  rotate(a.begin(),a.begin()+4,a.end()); //  Make 6 the head 
  for(auto x:a) cout<<x<<endl;
  cout <<"Min is " <<bs(a) <<endl;
  return 0;
}

我得到了输出:

6
7
8
9
10
1
2
3
5
Min is 3

,这显然是错误的。我想我正在操纵二进制搜索条件以适应我的问题,但我不知道出了什么问题。

我的方法类似于this,我认为我做的逻辑上是正确的,这当然是错误的想法。

【问题讨论】:

  • 你为什么不添加一些打印输出来告诉你你的代码在做什么?
  • running your code 产生“最小值为 3”
  • @AmiTavory 我把输出放了。
  • 我建议您在调试器中逐行逐行查看代码。
  • 我真的不明白你在问什么。它是一个排序的旋转数组。你知道轮回吗?如果是这样,您知道 min 元素的索引。如果不是,为什么你认为它可以在 log 时间内解决?

标签: c++ algorithm vector stl binary-search


【解决方案1】:

您有正确的策略,但没有清楚地考虑不变量。

我会假设元素是不同的。没有这个,它不能在 O(log n) 时间内完成。 (考虑除单个 1 外所有元素均为 0 的情况。)

如果a[0] &lt; a[size-1],则没有有效轮换,所以a[0]是最小值。

否则有两个递增的运行a[0]&lt;a[1]&lt;...&lt;a[k-1]a[k]&lt;a[k+1]&lt;...&lt;a[size-1],我们也知道a[k-1]&gt;a[k]

我们想找到k。

开始 - 就像你做的那样 - 用括号 [0, size-1] 猜测。

不变的是这个括号必须总是包含k。对于初始括号当然是这样!

为确保终止,我们必须在每次迭代期间将其缩小。当区间只有一个元素时,即 lo == hi,我们就有了答案。

计算一个新的猜测就像你展示的那样,

int mid = (lo + hi) / 2;

现在,有哪些可能性? Mid 位于 [0, k-1] 或 [k, size-1] 中。

如果是前者,那么我们知道mid

如果是后者,我们知道mid >= k,所以可以使用[lo, mid]。注意我们不能说 [lo, mid-1] 因为 mid 可能等于 k,打破了不变量。

这引发了另一个问题。如果 mid 的计算结果是 mid == hi,那么新括号与旧括号相同。我们将没有进展和无限循环。幸运的是,这永远不会发生,因为 (lo + hi) / 2 &lt; hi 每当 lo &lt; hi 时。

难题的最后一块是如何判断mid 位于哪个运行中。这很容易。如果a[mid] &gt;= a[0],我们知道它在第一次运行。否则它就在第二个。

将所有这些都封装在代码中:

if (a[0] < a[size - 1]) return 0;
int lo = 0, hi = size - 1;
while (lo < hi) {
  int mid = (lo + hi) / 2;
  if (a[mid] >= a[0])
    lo = mid + 1;
  else
    hi = mid;
}
return lo;

【讨论】:

  • 非常感谢,这个答案很好,我明白了。
  • 你能看看*.com/questions/31356239/…。另一个与二分搜索有关的问题。非常感谢:))
  • @MartinLampard 您可以使用我刚刚使用的完全相同的技术来回答这个问题。我不会剥夺你这种快乐。建立不变量。找到一个终止条件,当与不变量“与”时暗示答案。确保每次迭代都有进展。这将不会失败。注意:有正确版本的二分搜索依赖于稍微不同的不变量。因此,他们可以使用不同的终止条件来暗示已经得到答案。稍微不同的终止条件可以暗示相同不变量的答案。
【解决方案2】:

使用简单地找到旋转点

auto sorted_end = is_sorted_until(a.begin(), a.end());

来自cppreference的描述:

检查范围[first, last) 并找到从最开始的元素按升序排序的最大范围。

要在两种旋转方式中获取最小值,请使用

min(a.front(), *is_sorted_until(a.begin(), a.end()))

这小于O(n),但不小于O(log n)

编辑由于您链接了 SO 线程,我正在将答案翻译为 C++

int findMin(vector<int> & arr) {
    int low = 0;
    int high = arr.size() - 1;
    while (arr[low] > arr[high]) {
        int mid = (low + high) >> 1;
        if (arr[mid] > arr[high])
            low = mid + 1;
        else
            high = mid;
    }
    return arr[low];
}

http://ideone.com/BlzrWj查看它。

【讨论】:

    【解决方案3】:

    这有赋值的味道,所以没有标准库。

    我的方法是通过寻找不连续性来找到支点:

    int findpivot(vector<int> a)
    {
        int left = 0;
        int right = a.size() - 1;
    
        while (a[left] > a[right])
        {
            int mid = (right + left) / 2;
            if (a[mid] > a[right])
            { 
                left = mid + 1;
            }
            else
            {
                right = mid;
            }
        }
        return left;
    }
    

    然后,我会查看枢轴两侧的第一个值是否为最低值。运行该函数发现(事后看来,“Duh!”)它总是返回最小值的索引,因为我在枢轴处寻找不连续性的方式。

    最终结果与 Java 解决方案相同,只是我返回了枢轴点,而 Java 解决方案返回了枢轴点处的值。

    至于第二个问题,为什么 OP 被否决? OP 没有被否决。 OP的问题是。

    好的,那么为什么 OP 的问题被否决了?

    我能想到的最佳答案是这个问题是重复的。 OP 找到了重复项,但没有正确实施。这限制了这个问题对未来提问者的有用性。

    【讨论】:

    • 谢谢,但我应该在 while 检查条件中使用 l 和 r,而不是 a[l] 和 a[r]。
    • 不,它不是重复的,我使用的是与 l 和 r 略有不同的算法,我写了类似的算法,而不是重复。
    • @MartinLampard 第一条评论。两者都具有相同的结果和相同的时间复杂度,尽管直接使用 left 和 right 可能具有更快的运行时间。评论 2:您使用了不同的算法,但不同的算法不起作用。使其工作产生了 Java 解决方案的优化版本,并将问题推回为重复。从好的方面来说,Gene 提供了一个优化版本,供未来的提问者使用。 +1,吉恩。
    【解决方案4】:

    试试这个:

    #include <iostream>
    #include <vector>
    #include <stdio.h>
    #include <algorithm>
    #include <math.h>
    
    using namespace std;
    int findMinInRotatedSortedVec(vector<int>v, int start, int end)
    {
        if (start == end) return v[start];
        int mid = (end-start)/2+start;
        if (v[start] == v[mid]) {
            if (v[mid] == v[end]) {
                int min1 = findMinInRotatedSortedVec(v, start, mid);
                int min2 = findMinInRotatedSortedVec(v, mid+1, end);
                return (min1 > min2) ? min2 : min1;
            }
            if (v[mid] < v[end]) return v[start];
        }
        if ((v[start] < v[mid]) && (v[mid] <= v[end])) return v[start];
        if (v[start] > v[mid]) return findMinInRotatedSortedVec(v, start, mid);
        return findMinInRotatedSortedVec(v, mid+1, end);
    }
    
    int main() {
        vector<int> v (70);
        std::iota(v.begin(), v.end(), 31);
        rotate(v.begin(),v.begin()+43,v.end());
        cout <<"Min is " << findMinInRotatedSortedVec(v,0,v.size()-1) <<endl;
      return 0;
    }
    

    【讨论】:

      【解决方案5】:

      &lt;algorithm&gt; 提供了一种用于查找最小元素的内置方法,称为min_element(除了 max 和 minmax)。

      你可以这样使用它:

      std::vector<int>::iterator result = std::min_element(std::begin(a), std::end(a));
      std::cout << "Min is " << std::distance(std::begin(a), result);
      

      而且根本不必使用你的 bs() 方法。

      【讨论】:

      • 不,我无法回答。
      • 这个解决方案是 O(n),这是一个矫枉过正。