js-剑指刷题记录(查找)

1.旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0

我的解法

遇到这种有很多特殊情况的,我真的嗝屁。。。

其他分析

这道题真的看了很久很久。当然一个个遍历比较大小也能找出最大值但是这样做复杂度高也没利用旋转数组的特点。

非递减序列经过旋转,数组就分为前后两个非递减序列而且前面的序列任何一项都大于等于后面序列的任何一项,最小元素就是两者的分界点。有序序列的查找用二分法:

  • 我们用两个指针low,high分别指向数组的第一个元素和最后一个元素;

  • 找到数组的中间元素mid。
    mid大于low,mid元素位于前面的递增子数组,最小元素肯定位于mid元素的后面。指针low指向mid元素。移动之后,low指针仍然位于前面的递增数组中。
    mid小于high,mid元素位于后面的递增子数组,最小元素位于mid元素的前面。指针high指向mid元素。移动之后,high仍然位于后面的递增数组中。
    这样可以缩小寻找的范围。

  • 按照以上思路,low总是指向前面递增数组的元素,high总是指向后面递增的数组元素。

  • 最终low指向前面数组的最后一个元素,high指向后面数组中的第一个元素。也就是说他们将指向两个相邻的元素,high-low == 1,而high指向的刚好是最小元素,循环结束。

如果是一个严格递增序列,没有重复元素,代码非常好写。我只贴出来了部分重要代码用来说明思路。

let high = rotateArray.length-1  //旋转数组末尾
let low = 0   //旋转数组头
 while(high - low > 1){
     let mid = parseInt((high+low)/2)
     if(rotateArray[mid]>rotateArray[low]){ //mid在左递增数组
         low = mid
     }else if(rotateArray[mid]<rotateArray[high])     
     {  //mid在右递增数组
         high = mid
     }

但是本题中可能会有重复元素(非严格递增),要考虑的情况就会比较复杂了。
有重复元素就要考虑三种情况了,前提low >= high
情况一:rotateArray[mid] == rotateArray[low] > rotateArray[high]
low和mid之间只能是5,mid和high之间不一定,anyway指针low = mid
js-剑指刷题记录(查找)_第1张图片
情况二:rotateArray[mid] == rotateArray[high]< rotateArray[low]
high和mid之间只能是3,mid和low之间不一定,anyway指针high = mid
js-剑指刷题记录(查找)_第2张图片
情况三:rotateArray[mid] == rotateArray[high] == rotateArray[low]
这里就没法判断了,看下面两张图,只能一个个遍历low和high直接的元素找到最小值
js-剑指刷题记录(查找)_第3张图片js-剑指刷题记录(查找)_第4张图片
代码如下,值得注意的是,low和mid和high三者相等的情况一定写在最前面,因为三个判断条件之间是有包含关系的

function minNumberInRotateArray(rotateArray)
{
    let high = rotateArray.length-1
    let low = 0
    
    if(rotateArray.length == 0 ){
        return 0
    }
    //旋转数组是数组本身,那么旋转数组是一个递增数组,第一个元素是最小的
    if(rotateArray[low]<rotateArray[high]){
        return rotateArray[low]
    }
    
    while(high - low > 1){
        let mid = parseInt((high+low)/2)
        if(rotateArray[mid]==rotateArray[low]
        &&rotateArray[mid]==rotateArray[high]){
            var result = rotateArray[low]
            //遍历low和high之间的所有元素
            for(var i=low;i<high;i++){
                if(rotateArray[i] > rotateArray[i+1])
                    result = rotateArray[i+1]
            }
            return result
        }else if(rotateArray[mid]>=rotateArray[low]){ //mid在左排序数组
            low = mid
        }else if(rotateArray[mid]<=rotateArray[high]){//mid在又排序数组
            high = mid
        }
    }
        return rotateArray[high]
}

2.整数中1出现的次数

求出1-13的整数中1出现的次数,并算出100-1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。

我的解法

我哪有解法

其他分析

数学找规律题。牛客评论第二个是比较好的题解:一位一位的找。牛客-整数中1出现的次数

个位
个位数上的1是每隔10出现1次,比如1,11,21。也就是说0~9出现一个个位1,10~19出现一个个位1,20~29出现一个个位1,以此类推。
首先就可以确定,n/10的整数商是多少就有多少个个位1,比如27/10=2余7。那么余数呢?7>1说明还有一个个位1,如果余数是0,那么就没有个位1了。
可以得到
k = n%100
count = (n/10)*1 +1(k>=1) or 0(k<1)
十位
十位数上的1是每隔100出现10次,比如100到199有110~119,200到299有210~219,以此类推。
类似的可以先确定n/100的整数商是多少就有多少个十位1,比如217/100=2余17。接着处理余数,余数小于10的话,就没有十位1了,大于19就还有10个十位1,在这二者之间呢?比如17,有17-10+1=8个十位1。
可以得到
k = n%100
count = (n/10)*1 +10(k>19) or 0(k<10) or k-10+1
百位
有了以上两个例子。我们可以知道n/1000的整数商个百位1,另外余数小于100就没有百位1,余数大于199就还有100个百位1,在这二者之间就是余数-100+1
其他位就是类似啦~

function NumberOf1Between1AndN_Solution(n)
{
    if(n<=0) return 0
    let count = 0
    for(let i=1;i<=n;i*=10){ //i就表示数位,从个位开始
        let k = n%(i*10)  //余数,个位是除以10,百位是除以1000,所以是i*10
        let j
        //余数的三种情况
        if(k>2*i-1){  
            j = i
        }else if(k<i){
            j = 0
        }else{
            j = k-i+1
        }
        count +=(Math.floor(n/(i*10)))*i+j
    }
    return count
}

不过以上对余数的判断冗余,题解提出了更好的计算j的方法,把三个判断统一为:

Math.min(Math.max(k - i + 1, 0), i)

我也没看懂咋回事

你可能感兴趣的:(js-剑指刷题记录(查找))