0-n之间的缺失数字(由于是有序的0-n-1的数值,所以使用二分查找,判断当前数值是否和索引一样,不然就继续循环)

本文介绍了一种在已排序数组中查找缺失唯一数字的方法,利用二分查找策略,通过比较数组元素与期望值来确定缺失的位置。示例代码展示了如何在长度为n-1的递增数组中找到未包含的n个数字中的那个缺失值。
/*一个长度为n-1的递增排序数组中的所有数字都是唯一的,并且每个数字都在范围0~n-1之内。
  在范围0~n-1内的n个数字中有且只有一个数字不在该数组中,请找出这个数字。*/
// Math.floor(x):对一个数向下取整
// 二分查找法

var findmissingNumber = function (nums){
    let len = nums.length;
    // 判断是否是第一个或最后一个缺失
    if (nums[0] != 0){
        return 0
    }
    if (nums[len-1] != len){
        return len
    }
    // 二分查找
    let i=0, j=len-1,mid;
    while(i<j) {      //循环别忘记
        mid = Math.floor((i + j) / 2);
        if (nums[mid] == mid) {   //中间值一样说明前面的值没有出问题
            if (nums[mid + 1] != mid + 1) return mid + 1;
            i = mid + 1;
        } else {
            if (nums[mid - 1] == mid - 1) return mid;
            j = mid - 1;
        }
    }
}

console.log(findmissingNumber([0,1,2,3,4,6]))

<think>嗯,我现在要解决的问题是在一个有序数组0到n-1找到缺失的那个数字,并且时间复杂度不能超过O(log n)。首先,我需要理解题目要求。数组是有序的,且范围是从0到n-1,但缺其中一个数,所以数组的实际长度应该是n-1个元素。比如,如果n是5,那么数组应该是0,1,2,3,4中的一个数缺失,比如可能是[0,1,3,4],这样缺的是2。 那怎么高效地找到这个缺失的数呢?因为题目要求时间复杂度不超过O(log n),显然不能使用线性扫描的方法,那样是O(n)的时间复杂度。而log级别的复杂度提示我应该使用二分查找的方法,因为二分的时间复杂度是O(log n)。 那我需要考虑如何在这个问题中应用二分查找。通常二分查找用于在有序数组中查找特定值,但这里的场景是查找缺失的数,可能需要比较中间元素的值索引的关系。 比如,假设数组没有缺失的话,每个元素的值应该等于其索引。例如,数组的第0个元素是0,第1个元素是1,依此类推。如果有缺失的话,那么从某个位置开始,元素的值会比其索引1。比如,在数组[0,1,3,4]中,索引2的位置的值是3,这时候3比2大1,说明缺失数字是2。 所以,问题可以转化为找到第一个元素的值大于其索引的位置,然后这个位置的索引就是缺失数字。例如,在例子中,索引2的位置是3,此时缺失数字是2,也就是这个索引的值。或者,可能需要更仔细地分析。 比如,当数组中没有缺失时,每个元素的值等于索引。当缺失发生时,缺失位置之前的元素还是满足值等于索引,而缺失位置之后的元素的值会比索引1。因此,我们需要找到第一个满足nums[mid] > mid的位置,然后缺失的数就是mid。比如,对于数组[0,1,2,4],缺失的是3。此时,索引3的位置是4,此时mid是3,这时候应该返回mid作为缺失的数吗? 或者,另一个例子:数组是[1,2,3,4],缺失的是0。此时,所有元素的值都比索引1。因此,第一个出现nums[mid] > mid的位置是索引0,这时候缺失的数应该是0。那这时候二分查找需要处理这种情况。 那如何正确设置二分的条件呢?可能需要寻找左边界或者右边界。或者,可以通过比较中间元素的值索引的大小来决定搜索的方向。 具体来说,假设当前中间位置是mid,如果nums[mid] == mid,说明mid之前的元素都没有缺失缺失的位置应该在mid的右边。反之,如果nums[mid] > mid,说明缺失的位置在mid的左边或者就是mid自己。这时候需要调整搜索范围。 举个例子,数组是[0,1,3,4,5],n应该是6,数组长度是5。缺失的是2。此时,中间位置是mid=2(数组索引0到4),nums[2]=3>2,说明缺失的在左边或者mid的位置。那么此时应该向左搜索,找第一个出现nums[mid] > mid的位置。当左指针移动到索引0,右指针到索引2,新的mid是1。此时nums[1]=1 ==1,说明右边可能有缺失。此时左指针移动到mid+1=2,此时左等于右,此时检查nums[2]=3>2,说明缺失的是2。 所以,在二分查找过程中,当nums[mid] > mid时,缺失的位置可能在左边或者mid处,所以将右指针移动到mid。如果nums[mid] == mid,则左指针移动到mid+1。这样循环直到左指针小于右指针时终止,最后左指针的位置就是缺失的数? 那可能需要初始的左指针是0,右指针是数组的长度-1?或者数组长度是n-1?因为题目中的数组是0~n-1一个元素,所以数组的长度是n-1。例如n=5的话,数组长度是4。例如,数组元素是0,1,2,3,或者0,1,3,4等。这时候,正确的右指针初始应该是数组的最后一个元素的索引,即n-2。或者,原题中的数组是0到n-1,所以数组的长度是n,因为如果所有元素都存在的话应该是0到n-1,共n个元素。但题目中说缺了一个,所以数组长度是n-1。所以,当给定数组长度为n-1时,寻找缺失的数。比如,当数组是长度为4,那么n是5,原来的元素是0,1,2,3,4,现在缺一个,所以数组长度是4。 这样的话,右指针的初始位置应该是数组的最后一个元素的索引,即n-2,比如当数组长度为4时,索引0到3。而正确的n是5。 这里可能需要明确数组的长度是n-1,而原来的完整数组是0到n-1,共n个元素。所以,现在数组长度是n-1,所以要找的是这n个元素中缺的那个。 所以,在二分查找时,初始的左指针left是0,右指针right是数组长度-1,也就是n-2? 或者,不管数组长度如何,我们比较的是中间元素的数值索引。比如,数组中的每个元素nums[i],在正常情况下应该等于i。如果某个位置i,nums[i] > i,说明缺失发生在i或者之前的某个位置。 所以,二分查找的条件是:当nums[mid] == mid,说明左边没问题,向右查找。否则,向左查找。最后,当循环结束时,左指针的位置就是缺失的数? 或者,可能在循环结束后,如果左指针等于数组的最后一个索引,而该位置的数值等于该索引,说明缺失的是最后一个数n-1? 比如,假设数组是[0,1,2,3],n=5,数组长度是4,此时正确的缺失数是4。此时,每个元素的值都等于索引,所以循环结束后,左指针会是4吗?这时候数组的长度是4,索引最大是3。所以这时候需要处理这种情况。 比如,当数组所有元素都满足nums[i] ==i,那么说明缺失的是最后一个元素,也就是n-1。例如数组是0到n-2,所以缺失的是n-1。 那在二分过程中,如果整个数组都满足nums[mid] == mid,那么最后返回n-1? 这可能需要调整二分的条件。比如,当二分查找结束后,如果left等于数组的长度(此时数组的长度是n-1),那么说明缺失的是n-1。比如,数组是0,1,2,此时n是4,数组长度是3。此时,循环结束后,left可能等于3,这超过了数组的最大索引2,这时候应该返回3。 所以,可能的思路是:进行二分查找找到第一个nums[mid] > mid的位置,此时缺失的数是mid。如果在整个数组中没有找到这样的位置,说明缺失的是n-1。 那如何实现这个逻辑? 具体的算法步骤可能如下: 1. 初始化left=0,right=数组长度-1。 2. 在循环中,当left <= right: a. 计算mid = left + (right-left)/2 b. 如果nums[mid] == mid,说明缺失的在右侧,left = mid +1 c. 否则,缺失的在左侧或当前mid的位置,right = mid -1 3. 循环结束后,left的值即为缺失的数。如果所有元素都满足nums[i] ==i,那么left最终会等于数组的长度,即n-1(因为数组长度是n-1),所以返回left。 例如,测试几个例子: 例子1:数组是[0,1,3,4],n=5,数组长度是4。那么循环过程: 初始left=0,right=3。 mid = 1,nums[1]=1等于mid,left=2. mid=2+ (3-2)/2=2+0=2。nums[2]=3>2,说明可能在左边或mid处,所以right=1. 此时left=2>right=1循环结束。left的值是2,即缺失的数。 正确。 例子2:数组是[1,2,3,4], n=5,数组长度4,正确缺失的是0. 初始left=0,right=3. mid=1,nums[1]=2>1 → 所以right=0. 现在mid=0,nums[0]=1>0 → right=-1. 循环结束,left=0,返回0。正确。 另一个例子:数组是[0,1,2,3], n=5,数组长度4。缺失的是4。此时,循环过程中每次mid的值: 初始left=0,right=3. mid=1 → nums[1]=1=1 → left=2. mid=2 → nums[2]=2=2 → left=3. mid=3 → nums[3]=3=3 → left=4. 循环结束,left=4,此时数组长度为4,返回4,正确。 看来这个逻辑是正确的。那算法的步骤就是这样的。时间复杂度是O(log n),因为每次二分都将问题规模减半。 那现在需要把这个思路转化为具体的代码。比如,用Java的话: public int missingNumber(int[] nums) { int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == mid) { left = mid + 1; } else { right = mid - 1; } } return left; } 这个代码是否正确呢? 测试例子1: nums = [0,1,3,4],数组长度4。代码返回left=2,正确。 例子2: nums=[1,2,3,4],返回left=0,正确。 例子3: nums=[0,1,2,3],返回left=4(因为数组长度是4,此时left=4,即n-1=5-1=4?不,这里可能有个问题。原题中的n是什么?比如,数组中的元素本来是0到n-1,现在缺一个,所以数组长度是n-1。例如,数组长度是4的话,n应该是5,因为原来的完整数组是0到4(共5个元素),现在缺一个,所以数组长度是4。所以代码中返回的left是4,即n-1=4,此时n=5,是正确的。 所以这个代码是正确的。 那这个思路就是正确的。通过二分查找找到第一个nums[mid]不等于mid的位置,最终left即为缺失的数。 综上,解决问题的步骤是: 1. 使用二分查找,比较中间元素的值与索引的关系。 2. 如果中间元素等于其索引,说明缺失在右侧,否则在左侧。 3. 最终left的位置即为缺失的数,或者在数组全符合条件时返回n-1。 这样就满足了O(log n)的时间复杂度要求。</think>要在一个有序数组 $0$ 到 $n-1$ 中找到缺失的数,且时间复杂度不超过 $O(\log n)$,可以采用二分查找的思路。以下是具体步骤: ### 关键思路 1. **数组特性**:若数组完整,每个元素的值应等于其索引缺失会导致某一位置后的元素值比索引大 $1$。 2. **二分条件**: - 若 `nums[mid] == mid`,说明缺失在右侧,调整左边界 `left = mid + 1`。 - 若 `nums[mid] > mid`,说明缺失在左侧或当前缺失位置,调整右边界 `right = mid - 1`。 3. **终止条件**:当 `left > right` 时,`left` 的值即为缺失的数。若数组全匹配,则缺失的是最后一个数 $n-1$。 ### 算法步骤 1. 初始化 `left = 0`,`right = nums.length - 1`。 2. 循环二分直到 `left > right`: - 计算中点 `mid = left + (right - left) / 2`。 - 比较 `nums[mid]` 与 `mid`,调整边界。 3. 返回 `left`。 ### 代码实现 ```java public int missingNumber(int[] nums) { int left = 0, right = nums.length - 1; while (left <= right) { int mid = left + (right - left) / 2; if (nums[mid] == mid) { left = mid + 1; } else { right = mid - 1; } } return left; } ``` ### 复杂度分析 - **时间复杂度**:$O(\log n)$,每次二分将搜索范围减半。 - **空间复杂度**:$O(1)$,仅需常数空间。 ### 示例验证 1. **示例 1**: - 输入:`nums = [0,1,3,4]`,$n=5$。 - 过程: - 初始 `left=0`,`right=3`,`mid=1`(值 $1$ 匹配,左移)。 - 下一轮 `left=2`,`right=3`,`mid=2`(值 $3 > 2$,右移)。 - 循环终止,返回 `left=2`。 - 输出:$2$。 2. **示例 2**: - 输入:`nums = [0,1,2,3]`,$n=5$。 - 过程: - 所有元素匹配索引,最终 `left=4`。 - 输出:$4$(即 $n-1$)。 通过该方法,可高效定位缺失的数,满足时间复杂度要求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值