普通视图

发现新文章,点击刷新页面。
今天 — 2026年5月15日首页

每日一题-寻找旋转排序数组中的最小值🟡

2026年5月15日 00:00
已知一个长度为 n 的数组,预先按照升序排列,经由 1n旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:
  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

 

示例 1:

输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。

示例 2:

输入:nums = [4,5,6,7,0,1,2]
输出:0
解释:原数组为 [0,1,2,4,5,6,7] ,旋转 4 次得到输入数组。

示例 3:

输入:nums = [11,13,15,17]
输出:11
解释:原数组为 [11,13,15,17] ,旋转 4 次得到输入数组。

 

提示:

  • n == nums.length
  • 1 <= n <= 5000
  • -5000 <= nums[i] <= 5000
  • nums 中的所有整数 互不相同
  • nums 原来是一个升序排序的数组,并进行了 1n 次旋转

和最后一个数比大小,简洁二分(Python/Java/C++/C/Go/JS/Rust)

作者 endlesscheng
2022年11月23日 18:15

设 $x=\textit{nums}[\textit{mid}]$ 是现在二分取到的数。

我们需要判断 $x$ 和数组最小值的位置关系,谁在左边,谁在右边?

把 $x$ 与最后一个数 $\textit{nums}[n-1]$ 比大小:

  • 如果 $x > \textit{nums}[n-1]$,那么可以推出以下结论:
    • $\textit{nums}$ 一定被分成左右两个递增段;
    • 第一段的所有元素均大于第二段的所有元素;
    • $x$ 在第一段。
    • 最小值在第二段。
    • 所以 $x$ 一定在最小值的左边
  • 如果 $x \le \textit{nums}[n-1]$,那么 $x$ 一定在第二段。(或者 $\textit{nums}$ 就是递增数组,此时只有一段。)
    • $x$ 要么是最小值,要么在最小值右边

所以,只需要比较 $x$ 和 $\textit{nums}[n-1]$ 的大小关系,就间接地知道了 $x$ 和数组最小值的位置关系,从而不断地缩小数组最小值所在位置的范围,二分找到数组最小值。

二分基础知识【基础算法精讲 04】

本题视频讲解【基础算法精讲 05】

细节

下面代码用的开区间二分,用其他二分写法也是可以的。

二分的范围可以是 $(-1,n-1)$,也就是闭区间 $[0,n-2]$。

这是因为,如果 $\textit{nums}[n-1]$ 是数组最小值,那么 $\textit{nums}$ 分成两段,第一段 $[0,n-2]$,第二段 $[n-1,n-1]$,且第一段的所有数都大于 $\textit{nums}[n-1]$。每次 $x$ 和 $\textit{nums}[n-1]$ 比大小,一定是 $x>\textit{nums}[n-1]$。这意味着每次二分更新的都是 $\textit{left}$,那么循环结束后,答案自然就是 $n-1$ 了。

:这里有两个概念「二分范围」和「答案范围」。答案确实可以等于 $n-1$,但对于二分来说,代码中的 if (nums[mid] < nums[n - 1]) 在 $\textit{mid}=n-1$ 的时候一定不成立,我们可以直接知道 $n-1$ 是蓝色(根据视频中的红蓝染色法),所以 $n-1$ 无需在二分区间中。

答疑

:能否与 $\textit{nums}[0]$ 比大小?

:可以,但要多写一点代码。假设 $\textit{nums}$ 有两段。如果 $\textit{nums}[\textit{mid}] > \textit{nums}[0]$,那么 $\textit{mid}$ 在第一段,在最小值左边;否则,$\textit{mid}$ 要么是最小值,要么在最小值右边。这种写法需要在 $(0,n)$ 中二分,如果二分结果等于 $n$,说明 $\textit{nums}$ 其实只有一段,答案是 $\textit{nums}[0]$。

class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = -1, len(nums) - 1  # 开区间 (-1, n-1)
        while left + 1 < right:  # 开区间不为空
            mid = (left + right) // 2
            if nums[mid] < nums[-1]:
                right = mid
            else:
                left = mid
        return nums[right]
class Solution:
    def findMin(self, nums: List[int]) -> int:
        check = lambda i: nums[i] < nums[-1]
        i = bisect_left(range(len(nums) - 1), True, key=check)
        return nums[i]
class Solution {
    public int findMin(int[] nums) {
        int n = nums.length;
        int left = -1;
        int right = n - 1; // 开区间 (-1, n-1)
        while (left + 1 < right) { // 开区间不为空
            int mid = (left + right) >>> 1;
            if (nums[mid] < nums[n - 1]) {
                right = mid;
            } else {
                left = mid;
            }
        }
        return nums[right];
    }
}
class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = -1, right = nums.size() - 1; // 开区间 (-1, n-1)
        while (left + 1 < right) { // 开区间不为空
            int mid = left + (right - left) / 2;
            (nums[mid] < nums.back() ? right : left) = mid;
        }
        return nums[right];
    }
};
int findMin(int* nums, int numsSize) {
    int left = -1, right = numsSize - 1; // 开区间 (-1, n-1)
    while (left + 1 < right) { // 开区间不为空
        int mid = left + (right - left) / 2;
        if (nums[mid] < nums[numsSize - 1]) {
            right = mid;
        } else {
            left = mid;
        }
    }
    return nums[right];
}
func findMin(nums []int) int {
    left, right := -1, len(nums)-1 // 开区间 (-1, n-1)
    for left+1 < right { // 开区间不为空
        mid := left + (right-left)/2
        if nums[mid] < nums[len(nums)-1] {
            right = mid
        } else {
            left = mid
        }
    }
    return nums[right]
}
func findMin(nums []int) int {
    i := sort.Search(len(nums)-1, func(i int) bool {
        return nums[i] < nums[len(nums)-1]
    })
    return nums[i]
}
var findMin = function(nums) {
    let left = -1, right = nums.length - 1; // 开区间 (-1, n-1)
    while (left + 1 < right) { // 开区间不为空
        let mid = Math.floor((left + right) / 2);
        if (nums[mid] < nums[nums.length - 1]) {
            right = mid;
        } else {
            left = mid;
        }
    }
    return nums[right];
};
impl Solution {
    pub fn find_min(nums: Vec<i32>) -> i32 {
        let mut left = 0;
        let mut right = nums.len() - 1; // 左闭右开区间 [0, n-1)
        while left < right { // 区间不为空
            let mid = left + (right - left) / 2;
            if nums[mid] < nums[nums.len() - 1] {
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        nums[left]
    }
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(\log n)$,其中 $n$ 为 $\textit{nums}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

思考题

改成计算 $\textit{nums}$ 的最大值呢?

请读者实现该问题,以加深对本题的理解。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

欢迎关注 B站@灵茶山艾府

寻找旋转排序数组中的最小值

2021年4月4日 00:15

方法一:二分查找

思路与算法

一个不包含重复元素的升序数组在经过旋转之后,可以得到下面可视化的折线图:

fig1

其中横轴表示数组元素的下标,纵轴表示数组元素的值。图中标出了最小值的位置,是我们需要查找的目标。

我们考虑数组中的最后一个元素 $x$:在最小值右侧的元素(不包括最后一个元素本身),它们的值一定都严格小于 $x$;而在最小值左侧的元素,它们的值一定都严格大于 $x$。因此,我们可以根据这一条性质,通过二分查找的方法找出最小值。

在二分查找的每一步中,左边界为 $\it low$,右边界为 $\it high$,区间的中点为 $\it pivot$,最小值就在该区间内。我们将中轴元素 $\textit{nums}[\textit{pivot}]$ 与右边界元素 $\textit{nums}[\textit{high}]$ 进行比较,可能会有以下的三种情况:

第一种情况是 $\textit{nums}[\textit{pivot}] < \textit{nums}[\textit{high}]$。如下图所示,这说明 $\textit{nums}[\textit{pivot}]$ 是最小值右侧的元素,因此我们可以忽略二分查找区间的右半部分。

fig2

第二种情况是 $\textit{nums}[\textit{pivot}] > \textit{nums}[\textit{high}]$。如下图所示,这说明 $\textit{nums}[\textit{pivot}]$ 是最小值左侧的元素,因此我们可以忽略二分查找区间的左半部分。

fig3

由于数组不包含重复元素,并且只要当前的区间长度不为 $1$,$\it pivot$ 就不会与 $\it high$ 重合;而如果当前的区间长度为 $1$,这说明我们已经可以结束二分查找了。因此不会存在 $\textit{nums}[\textit{pivot}] = \textit{nums}[\textit{high}]$ 的情况。

当二分查找结束时,我们就得到了最小值所在的位置。

###C++

class Solution {
public:
    int findMin(vector<int>& nums) {
        int low = 0;
        int high = nums.size() - 1;
        while (low < high) {
            int pivot = low + (high - low) / 2;
            if (nums[pivot] < nums[high]) {
                high = pivot;
            }
            else {
                low = pivot + 1;
            }
        }
        return nums[low];
    }
};

###Java

class Solution {
    public int findMin(int[] nums) {
        int low = 0;
        int high = nums.length - 1;
        while (low < high) {
            int pivot = low + (high - low) / 2;
            if (nums[pivot] < nums[high]) {
                high = pivot;
            } else {
                low = pivot + 1;
            }
        }
        return nums[low];
    }
}

###Python

class Solution:
    def findMin(self, nums: List[int]) -> int:    
        low, high = 0, len(nums) - 1
        while low < high:
            pivot = low + (high - low) // 2
            if nums[pivot] < nums[high]:
                high = pivot 
            else:
                low = pivot + 1
        return nums[low]

###C

int findMin(int* nums, int numsSize) {
    int low = 0;
    int high = numsSize - 1;
    while (low < high) {
        int pivot = low + (high - low) / 2;
        if (nums[pivot] < nums[high]) {
            high = pivot;
        } else {
            low = pivot + 1;
        }
    }
    return nums[low];
}

###golang

func findMin(nums []int) int {
    low, high := 0, len(nums) - 1
    for low < high {
        pivot := low + (high - low) / 2
        if nums[pivot] < nums[high] {
            high = pivot
        } else {
            low = pivot + 1
        }
    }
    return nums[low]
}

###JavaScript

var findMin = function(nums) {
    let low = 0;
    let high = nums.length - 1;
    while (low < high) {
        const pivot = low + Math.floor((high - low) / 2);
        if (nums[pivot] < nums[high]) {
            high = pivot;
        } else {
            low = pivot + 1;
        }
    }
    return nums[low];
};

复杂度分析

  • 时间复杂度:时间复杂度为 $O(\log n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。在二分查找的过程中,每一步会忽略一半的区间,因此时间复杂度为 $O(\log n)$。

  • 空间复杂度:$O(1)$。

二分查找:为什么左右不对称?只比较mid与right的原因(C++, Java, Python3)

2020年3月3日 09:52

这道寻找最小值的题目可以用二分查找法来解决,时间复杂度为O(logN),空间复杂度为O(1)。

看一下代码:

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[right]) {          
                left = mid + 1;
            } else {                               
                right = mid;
            }
        }
        return nums[left];
    }
};
class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[right]) {          
                left = mid + 1;
            } else {                                
                right = mid;
            }
        }
        return nums[left];
    }
};
class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = 0, len(nums) - 1
        while left < right:
            mid = (left + right) >> 1
            if nums[mid] > nums[right]:         
                left = mid + 1
            else:                               
                right = mid
        return nums[left]
 var findMin = function(nums) {
    var left = 0;
    var right = nums.length - 1;
    while (left < right) {
        var mid = (left + right) >> 1;
        if (nums[mid] > nums[right]) {
            left = mid + 1;
        } else {
            right = mid;
        }
    }
    return nums[left];
};

首先说一下主要思路:

单调递增的序列:

        *
      *
    *
  *
*

做了旋转:

  *
*
        *
      *
    *

用二分法查找,需要始终将目标值(这里是最小值)套住,并不断收缩左边界或右边界。

左、中、右三个位置的值相比较,有以下几种情况:

  1. 左值 < 中值, 中值 < 右值 :没有旋转,最小值在最左边,可以收缩右边界

            右
         中
     左
    
  2. 左值 > 中值, 中值 < 右值 :有旋转,最小值在左半边,可以收缩右边界

     左       
             右
         中
    
  3. 左值 < 中值, 中值 > 右值 :有旋转,最小值在右半边,可以收缩左边界

         中  
     左 
             右
    
  4. 左值 > 中值, 中值 > 右值 :单调递减,不可能出现

     左
        中
            右
    

分析前面三种可能的情况,会发现情况1、2是一类,情况3是另一类。

如果中值 < 右值,则最小值在左半边,可以收缩右边界。
如果中值 > 右值,则最小值在右半边,可以收缩左边界。
通过比较中值与右值,可以确定最小值的位置范围,从而决定边界收缩的方向。

而情况1与情况3都是左值 < 中值,但是最小值位置范围却不同,这说明,如果只比较左值与中值,不能确定最小值的位置范围。

所以我们需要通过比较中值与右值来确定最小值的位置范围,进而确定边界收缩的方向。

接着分析解法里的一些问题:

首先是while循环里的细节问题。

这里的循环不变式是left < right, 并且要保证左闭右开区间里面始终套住最小值。

中间位置的计算:mid = left + (right - left) / 2
这里整数除法是向下取整的地板除,mid更靠近left
再结合while循环的条件left < right
可以知道left <= midmid < right
即在while循环内,mid始终小于right

因此在while循环内,nums[mid]要么大于要么小于nums[right],不会等于。

这样else {right = mid;}这句判断可以改为更精确的
else if (nums[mid] < nums[right]) {right = mid;}

再分析一下while循环退出的条件。

如果输入数组只有一个数,左右边界位置重合,left == right,不会进入while循环,直接输出。

如果输入数组多于一个数,循环到最后,会只剩两个数,nums[left] == nums[mid],以及nums[right],这里的位置left == mid == right - 1

如果nums[left] == nums[mid] > nums[right],则左边大、右边小,
需要执行left = mid + 1,使得left == right,左右边界位置重合,循环结束,nums[left]nums[right]都保存了最小值。

如果nums[left] == nums[mid] < nums[right],则左边小、右边大,
会执行right = mid,使得left == right,左右边界位置重合,循环结束,nums[left]nums[mid]nums[right]都保存了最小值。

细化了的代码:

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;                /* 左闭右闭区间,如果用右开区间则不方便判断右值 */ 
        while (left < right) {                      /* 循环不变式,如果left == right,则循环结束 */
            int mid = left + (right - left) / 2;    /* 地板除,mid更靠近left */
            if (nums[mid] > nums[right]) {          /* 中值 > 右值,最小值在右半边,收缩左边界 */ 
                left = mid + 1;                     /* 因为中值 > 右值,中值肯定不是最小值,左边界可以跨过mid */ 
            } else if (nums[mid] < nums[right]) {   /* 明确中值 < 右值,最小值在左半边,收缩右边界 */ 
                right = mid;                        /* 因为中值 < 右值,中值也可能是最小值,右边界只能取到mid处 */ 
            }
        }
        return nums[left];    /* 循环结束,left == right,最小值输出nums[left]或nums[right]均可 */     
    }
};
class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1;                /* 左闭右闭区间,如果用右开区间则不方便判断右值 */ 
        while (left < right) {                      /* 循环不变式,如果left == right,则循环结束 */
            int mid = left + (right - left) / 2;    /* 地板除,mid更靠近left */
            if (nums[mid] > nums[right]) {          /* 中值 > 右值,最小值在右半边,收缩左边界 */ 
                left = mid + 1;                     /* 因为中值 > 右值,中值肯定不是最小值,左边界可以跨过mid */ 
            } else if (nums[mid] < nums[right]) {   /* 明确中值 < 右值,最小值在左半边,收缩右边界 */ 
                right = mid;                        /* 因为中值 < 右值,中值也可能是最小值,右边界只能取到mid处 */ 
            }
        }
        return nums[left];    /* 循环结束,left == right,最小值输出nums[left]或nums[right]均可 */     
    }
};
class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = 0, len(nums) - 1          # 左闭右闭区间,如果用右开区间则不方便判断右值
        while left < right:                     # 循环不变式,如果left == right,则循环结束
            mid = (left + right) >> 1           # 地板除,mid更靠近left
            if nums[mid] > nums[right]:         # 中值 > 右值,最小值在右半边,收缩左边界
                left = mid + 1                  # 因为中值 > 右值,中值肯定不是最小值,左边界可以跨过mid
            elif nums[mid] < nums[right]:       # 明确中值 < 右值,最小值在左半边,收缩右边界
                right = mid                     # 因为中值 < 右值,中值也可能是最小值,右边界只能取到mid处
        return nums[left]                       # 循环结束,left == right,最小值输出nums[left]或nums[right]均可

再讨论一个问题:

为什么左右不对称?为什么比较midright而不比较midleft?能不能通过比较midleft来解决问题?

左右不对称的原因是:
这是循环前升序排列的数,左边的数小,右边的数大,而且我们要找的是最小值,肯定是偏向左找,所以左右不对称了。

为什么比较midright而不比较midleft
具体原因前面已经分析过了,简单讲就是因为我们找最小值,要偏向左找,目标值右边的情况会比较简单,容易区分,所以比较midright而不比较midleft

那么能不能通过比较midleft来解决问题?
能,转换思路,不直接找最小值,而是先找最大值,最大值偏右,可以通过比较midleft来找到最大值,最大值向右移动一位就是最小值了(需要考虑最大值在最右边的情况,右移一位后对数组长度取余)。

以下是先找最大值的代码,可以与前面找最小值的比较:

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;
        while (left < right) {
            int mid = left + (right - left + 1) / 2;   /* 先加一再除,mid更靠近右边的right */
            if (nums[left] < nums[mid]) {
                left = mid;                            /* 向右移动左边界 */
            } else if (nums[left] > nums[mid]) {
                right = mid - 1;                       /* 向左移动右边界 */
            }
        }
        return nums[(right + 1) % nums.size()];    /* 最大值向右移动一位就是最小值了(需要考虑最大值在最右边的情况,右移一位后对数组长度取余) */
    }
};
class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1; 
        while (left < right) {
            int mid = left + (right - left + 1) / 2;   /* 先加一再除,mid更靠近右边的right */
            if (nums[left] < nums[mid]) {
                left = mid;                            /* 向右移动左边界 */
            } else if (nums[left] > nums[mid]) {
                right = mid - 1;                       /* 向左移动右边界 */
            }
        }
        return nums[(right + 1) % nums.length];    /* 最大值向右移动一位就是最小值了(需要考虑最大值在最右边的情况,右移一位后对数组长度取余) */
    }
};
class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = 0, len(nums) - 1   
        while left < right:          
            mid = (left + right + 1) >> 1           # 先加一再除,mid更靠近右边的right     
            if nums[left] < nums[mid]:         
                left = mid                          # 向右移动左边界
            elif nums[left] > nums[mid]:       
                right = mid - 1                     # 向左移动右边界
        return nums[(right + 1) % len(nums)]        # 最大值向右移动一位就是最小值了(需要考虑最大值在最右边的情况,右移一位后对数组长度取余)

使用left < right作while循环条件可以很方便推广到数组中有重复元素的情况,即154题:
https://leetcode.cn/problems/find-minimum-in-rotated-sorted-array-ii/

只需要在nums[mid] == nums[right]时挪动右边界就行:

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[right]) {
                left = mid + 1;
            } else if (nums[mid] < nums[right]) {
                right = mid;
            } else {
                right--;
            }
        }
        return nums[left];
    }
};
class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1; 
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > nums[right]) {
                left = mid + 1;
            } else if (nums[mid] < nums[right]) {
                right = mid;
            } else {
                right--;
            }
        }
        return nums[left];
    }
};
class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = 0, len(nums) - 1                 
        while left < right:                     
            mid = (left + right) >> 1          
            if nums[mid] > nums[right]:       
                left = mid + 1                 
            elif nums[mid] < nums[right]:     
                right = mid                     
            else:
                right -= 1
        return nums[left]

初始条件是左闭右闭区间,right = nums.size() - 1
那么能否将while循环的条件也选为左闭右闭区间left <= right

可以的,代码如下:

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;
        while (left <= right) {                         // 循环的条件选为左闭右闭区间left <= right
            int mid = left + (right - left) / 2;
            if (nums[mid] >= nums[right]) {             // 注意是当中值大于等于右值时,
                left = mid + 1;                         // 将左边界移动到中值的右边
            } else {                                    // 当中值小于右值时
                right = mid;                            // 将右边界移动到中值处
            }
        }
        return nums[right];                             // 最小值返回nums[right]
    }
};
class Solution {
    public int findMin(int[] nums) {
        int left = 0;
        int right = nums.length - 1; 
        while (left <= right) {                         // 循环的条件选为左闭右闭区间left <= right
            int mid = left + (right - left) / 2;
            if (nums[mid] >= nums[right]) {             // 注意是当中值大于等于右值时,
                left = mid + 1;                         // 将左边界移动到中值的右边
            } else {                                    // 当中值小于右值时
                right = mid;                            // 将右边界移动到中值处
            }
        }
        return nums[right];                             // 最小值返回nums[right]
    }
};
class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = 0, len(nums) - 1    
        while left <= right:                    # 循环的条件选为左闭右闭区间left <= right
            mid = (left + right) >> 1
            if nums[mid] >= nums[right]:        # 注意是当中值大于等于右值时,      
                left = mid + 1                  # 将左边界移动到中值的右边
            else:                               # 当中值小于右值时
                right = mid                     # 将右边界移动到中值处
        return nums[right]                      # 最小值返回nums[right]

这道题还有其它解法:

始终将nums[mid]与最右边界的数进行比较,相当于在每次裁剪区间之后始终将最右边的数附在新数组的最右边。

class Solution {
public:
    int findMin(vector<int>& nums) {
        int right_boundary = nums[nums.size() - 1];
        int left = 0;
        int right = nums.size() - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > right_boundary) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return nums[left];
    }
};
class Solution {
    public int findMin(int[] nums) {
        int right_boundary = nums[nums.length - 1];
        int left = 0;
        int right = nums.length - 1;
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] > right_boundary) {          
                left = mid + 1;
            } else {                                
                right = mid;
            }
        }
        return nums[left];
    }
};
class Solution:
    def findMin(self, nums: List[int]) -> int:
        right_boundary = nums[- 1]
        left, right = 0, len(nums) - 1
        while left < right:
            mid = (left + right) >> 1
            if nums[mid] > right_boundary:         
                left = mid + 1
            else:                               
                right = mid
        return nums[left]

或者在处理了第一种情况之后,始终将nums[mid]与最左边界的数nums[0]进行比较,即相当于在每次裁剪区间之后始终将最左边的数附在新数组的最左边,再不断处理情况2及情况3。

class Solution {
public:
    int findMin(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;
        if (nums[0] < nums[right])
            return nums[left];
        while (left < right) { 
            int mid = left + (right - left) / 2; 
            if (nums[0] > nums[mid]) 
                right = mid; 
            else 
                left = mid + 1; 
        } 
        return nums[left];
    }
};
class Solution:
    def findMin(self, nums: List[int]) -> int:
        left, right = 0, len(nums) - 1
        if nums[left] < nums[right]:
            return nums[left]
        while left < right:
            mid = (left + right) >> 1
            if nums[0] > nums[mid]:
                right = mid
            else:
                left = mid + 1
        return nums[left]
昨天 — 2026年5月14日首页

5连板蒙娜丽莎:无半导体材料业务,参股公司珠海晶瓷非英伟达供应商

2026年5月14日 20:47
36氪获悉,蒙娜丽莎公告,公司注意到投资者近期对公司对外投资事项较为关注,公司澄清如下:公司对外投资了珠海晶瓷电子科技有限公司,投资金额5000万元,公司未将珠海晶瓷纳入合并报表范围。珠海晶瓷目前产品处于研发送样阶段,尚未产生批量订单,尚未盈利,后续项目验证是否达到要求,能否实现批量生产存在不确定性,未来亦存在技术研发进度不及预期的风险。珠海晶瓷并非网上传言“是英伟达的供应商”。

仁度生物:实控人拟变更为海鲸药业,股票复牌

2026年5月14日 20:25
36氪获悉,仁度生物公告,公司控股股东、实际控制人居金良等与南京海鲸药业签署《股份转让协议》,合计转让851.57万股,转让总价约5.16亿元。交易完成后,公司控股股东将由居金良变更为海鲸药业,实际控制人变更为张现涛。通过本次权益变动,有利于提升公司整体经营治理与运营管理水平,整合公司现有体外诊断领域产品资源,并结合海鲸药业的产品优势,构建从诊断到治疗的全链条业务布局,打造一体化综合解决方案,持续增强公司核心竞争力与国内外市场影响力。公司股票将于2026年5月15日开市起复牌。

印度禁食糖出口至9月底,保障国内供应

2026年5月14日 20:13
世界食糖主要生产和出口国印度5月13日宣布,该国从即日起至9月30日禁止食糖出口,以保障国内供应和稳定价格。据了解,印度政府发布的这项出口禁令适用于原糖和白糖。在禁令正式发布前货物已开始装运或进入清关流程、承运船已抵达印度港口等情况可不受禁令限制,印度与他国政府所签的关乎粮食安全的出口订单也不受影响。(央视新闻)

爱普股份:拟1.08亿元收购控股子公司爱普配料49%股权

2026年5月14日 19:57
36氪获悉,爱普股份公告,公司拟以现金方式收购控股子公司上海爱普食品配料有限公司(简称“爱普配料”)少数股东持有的爱普配料49%股权,交易对价合计为1.08亿元。交易完成后,公司持有爱普配料的股权比例由51%增加至100%,爱普配料将成为公司的全资子公司。

DeepSeek 融资后,大模型领域会有什么新格局?

2026年5月14日 19:52

最近业界都在传,DeepSeek 进行了目标可能高达 500 亿人民币的首轮融资,将刷新中国大模型公司单轮融资最高纪录。据我了解到的消息,是不是 500 亿的规模虽然无法确定,但其首轮融资金额已打破纪录并严重超募,成功完成首轮融资已成定局。

那么,今天这样的形势下,中国大模型玩家的未来格局是什么样的?甚至未来市场上能有多少家大模型玩家?这些玩家将如何构成?其实挺值得讨论一下。

 

竞赛规则和目标越来越明确

先说个业界的共识:Scaling law 并没有到头,

「更多维度的数据,更高质量的数据工程,更大的算力带来的更庞大的基模,还在给模型能力带来快速增长,甚至这种能力的成长还没有看到边界。」这基本上是现阶段硅谷一线 Researcher 的共识。

这种共识代表着某种「确定性」,这意味着提升模型智能水平,在一定程度上从一个高风险的科学探索问题,开始更靠近一个高投入的工程和资本问题。只要有足够的数据、算力和工程执行力,持续投入就能换来相对可预期的智能提升。

与此同时智能本身也逼近了一个临界点——智能程度足以实现自进化。Anthropic Claude Cowork 的代码全部由 Claude Code 产出,并在十天中完成。用当前最先进的智能生产力,投入下一代智能的迭代,这种模型能力到达某种临界点后,可用于提升下一代模型的训练效率或数据质量的现实,开始给人一种近乎「左脚踩右脚」的梯云纵的想象。

相信中国模型领域应该也已经迅速形成了两个共识:

一方面,无论大厂还是创业公司,都必须拼尽全力,尽快跨过这道临界点。跨不过去,差距将被彻底拉开,牌桌就没有你的位置。

另一方面,这并不是靠一代、两代模型 SOTA 就能赢得的比赛。它是一场星际飞行,在一个足够长的时间里,玩家们比拼的是持续的加速度,而非某一次的冲刺加速。这意味着,你需要足够大的「能量池」,才能留在竞赛里。

模型不只是现有巨头的游戏

训练模型需要巨大的资源消耗,上顶级模型牌桌的入场券大概就得 10 亿美元级别。这笔钱,依靠一级市场的融资已经非常不易。但这很可能仅仅是「起步价」。

真正的考验在于,你是否具备「百亿甚至千亿美元」级别的持续投入能力。这笔钱大概率无法单靠融资,它可能来自两个核心支柱之一:一个能持续产生巨额利润的强大业务,或是能被资本市场长期、海量注资的信心。

虽然模型一度被认为是独属于「巨头」的游戏,但随着这场游戏的确定性和「规则」逐渐明确,反而现在有一些新玩家在入场,他们未必是传统意义上的巨头,但是他们也有强烈的入场动力。

这背后的思考是,基础大模型并非一个产品,而是这个时代的「工业母机」。拥有自主、可控、持续迭代的模型能力,相当于在新时代拥有了自己的核心工业体系。反之,如果完全依赖外部模型 API,就像你只能做来料加工甚至是服务业,而不拥有核心的工业体系,那么无论你的应用层业务多么繁荣,都会面临「空心化」的问题,沦为他人工业链条中的一环。当然,更大的问题是你也会严重缺少在新时代开疆辟土的「工业能力」。

基于此,中国的模型牌桌上可能将会有三类玩家:

第一类玩家是科技巨头,例如阿里、字节、腾讯。

他们的核心优势并非单纯的资本,而是他们都有着非常强大的主营业务。

这种优势对于训模型的重要性,可以从「云」的发展中窥见:阿里云之所以能成为亚洲第一云,根源在于它诞生于双十一的极限交易洪峰中,是被自有业务的极端场景「逼」出来的基础设施。亚马逊 AWS 也是如此。

所以科技巨头作为第一梯队的核心优势在于他们强大的「A 面」——电商、社交、内容等庞大的主营业务,是「B 面」模型引擎的天然训练场与价值出口。

对科技巨头而言,投入模型研发并非选项,而是必须。放弃自研模型,就意味着平台「空心化」,将未来的「工业主权」拱手让人,这是他们绝对无法接受的。

比如之前比较被大家忽视的腾讯,其最近的模型虽然还没有 SOTA 到引人注目,但已经看得出在快速追赶和提速。按照这种发展趋势,未来也一定是不可忽视的力量。

第二类玩家是 MiniMax、智谱、月之暗面、阶跃星辰这类模型创业公司。

他们是本轮浪潮中最敏锐的先行者,他们因模型而来,也 all in 模型,凭借技术信仰和敏锐的执行力,在模型能力上紧咬前沿。

作为新兴公司,他们的优势理应体现在更高的组织效率、更敏捷的执行力,以及在技术上的原生「天赋点」,能以相对更优的效率取得成果。

但他们面临的挑战也显而易见:如何解决持续长跑的燃料问题?因此长出一个强大的 A 面业务一定是值得追求的。

例如相对于 OpenAI,Anthropic 最近一年多来在硅谷更受推崇,就是因为它凭借 Claude Code 等产品迅速建立起了强大的 A 面,展现了从第二类向第一类玩家迁移的趋势。

在全球化中寻找出路或许是一个要探索的方向。毕竟在中美两大市场之外,广阔的全球市场提供了巨大空间。将模型能力或基于模型的产品,像电力一样输出到全球不同角落,用一定的价值循环做支点,驱动模型引擎的持续迭代,形成价值闭环。这条路不容易,但概率不为零。

相对于我们比较熟悉的前两类,第三类玩家也很值得讨论。

这类玩家是战局中最值得关注的变量。他们虽然没有巨头的体量,但具备几个典型的特征:较高的利润率、可观的利润规模、有砸几十亿美元的投入决心,以及创始人自己对大模型领域足够深的投入,在技术决心和业务决定上的极度笃定

DeepSeek 就是这类里最独树一帜的代表。它正是基于幻方量化在长期量化交易业务中锤炼的 AI 与超算技术能力而创立,也得益于幻方量化这个坚实业务 A 面的支撑,加之创始人梁文峰兼具决心、意愿与模型领域的能力,使 DeepSeek 成为了业界不可忽视的力量。

但当所有人都意识到模型是一场长跑时,仅靠一个中型 A 面业务的利润支撑是不足的。因此,这也解释了 DeepSeek 走向融资就是一种必然选择。

此外,DeepSeek 之所以受到大力加持,还在于其独特的战略站位。首先 DeepSeek 在开源路线上已经在建立初步的生态,实际上带动了硅基流动、无问芯穹等公司的存在。同时,在所有国内大模型公司里,DeepSeek 也是与国产算力生态整合最为积极和坚定的。毕竟对于其他巨头和创业公司而言,这是一条充满不确定性与挑战的、让他们很难 all in 的道路,但 DeepSeek 做出了这个选择,使其在事实上承载了部分与国家科技战略相关的使命。这种与国家战略的深度共振,超越了单一的商业业务,构成了其更宏大、也更稳固的一个「A 面」。

这不仅仅意味着 DeepSeek 可能获得不亚于大厂的持续资源能力,也意味着开源模型领域的进一步能力加强,成为了中美闭源大模型之外的一股对冲力量。

其实我还有一个大胆的推测,大模型玩家中,可能还有一个值得重点关注的是米哈游创始人之一的蔡浩宇创立的 AI 公司 Anuttacon,其近期发布的 LPM 1.0 表演模型可能只是一道「前菜」。据传蔡浩宇在 LLM 上的专注投入已经开始一段时间了。

另外的一个证据是在米哈游 4 月 17 日的官方校招信息中,已明确开始为「基础大模型」举办技术分享暨顶尖校招生招募,并清晰地指出这是基于「自研 Foundation Model」。更值得注意的是,信息中提到米哈游的另一位创始人刘伟也将前往现场共同探讨交流。从这也看得出来,投身基础大模型大概率不只是蔡浩宇的个人兴趣和动作,而可能是创始团队在这方面达成了共同的决心,是一次重要的集体战略选择。

从基本能力上看,米哈游的游戏业务年收入在百亿美元级别,利润可能在 30-40 亿美元区间。同时蔡浩宇已经建立独立的公司,应该已经开始明确集中精力聚焦于大模型领域。他大概率不是像之前外界猜测的那样只是围着未来游戏引擎来搞模型——虽然最开始的出发点可能确实有这个因素。因为从当下模型的发展趋势看,就算最终其模型的能力可以帮助米哈游创造「AI 世代的新游戏」,那也一定不会只是垂直于游戏领域的「AI 游戏引擎」,而必须是基于强大语言模型向上生长的数字世界「工业母机」。

实际上,米哈游在游戏领域的成就已经非常可观,我猜测其创始人们很可能不只把大模型作为确保游戏业务能穿越时代的「防守动作」。这为什么不能是他们依托游戏业务这么好的阵地的「进攻性」能力?是通向更大赔率和更大规模的未来业务新可能性呢?

毕竟米哈游凭借其游戏业务的支撑,并且同样可以借助资本市场的杠杆,它有潜力成为第三类玩家中另一个重要的力量。前提是,要看他们什么时候能在大语言模型上放出他们的阶段性成果,验证他们在技术上获得被认可的「参赛资格」。

这种半场杀入,但目标坚决,并一定会寻找用技术创新超车的新玩家,至少对技术人才们而言是一种新机遇。

总之,到今天看,大模型还不能说只是巨头的游戏,故事还在分叉,精彩还在继续。

农业农村部修订印发生猪产能综合调控实施方案

2026年5月14日 19:52
36氪获悉,近日,农业农村部印发《生猪产能综合调控实施方案(2026年修订)》。此次《方案》综合考虑猪肉市场供需、生猪生产效率提升等因素,设定全国能繁母猪正常保有量稳定在3750万头左右,是自2024年2月以来再次下调正常保有量目标。《方案》明确,适当收紧能繁母猪存栏量绿色区域和黄色区域上限以及黄色区域下限,建立产能分级联动调控机制,强化生产和市场预期引导,促进生猪市场供需更加适配。

沃格光电:控股股东及股东相关方因涉嫌信披违规被证监会立案

2026年5月14日 19:43
36氪获悉,沃格光电公告,公司控股股东、实际控制人易伟华及持股5%以上股东辉睿1号私募投资基金的管理人深圳中锦程资产管理有限公司于2026年5月14日收到证监会《立案告知书》,因涉嫌信息披露违法违规,证监会决定对易伟华、深圳中锦程资产管理有限公司立案。本次调查与公司经营无关,公司将密切关注进展并履行信披义务。
❌
❌