剑指offer——数组归类(3, 3_1, 4,11,21,39,40,42,45,51,53,56,66)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Lollipop66/article/details/80816166
最近根据专题更新一些剑指offer的一些思路(如果有需要剑指offer代码(java)可以评论,然后可以分享给大家)

3.找出数组中重复数字

思路1,排序之后,然后依次查找当前与下一个是否一致(时间复杂度O(nlogn),空间复杂度O(1))

思路2,使用HashTree,依次存入,然后判断是否存在与其一致的值(时间复杂度O(n),空间复杂度O(n))

思路3,分三种情况:

         1)i与numbers[i]相等,直接看i+1的情况     

       2)i与numbers[i]不等, 并且number[i]位置的值(number[number[i]])与numbers[i]相等,就说明存在重复的值,直接返回这个值

       3)i与numbers[i]不等, 并且number[i]位置的值(number[number[i]])与numbers[i]不等,那就交换这二个值,直到变成 1)或者 2)的情况时间复杂度(O(n)),空间复杂度O(1)


3_1.不修改数组找出重复的数字

思路:可以根据直接复制一个数组,然后用3的方法,但是空间复杂度为O(n),  
                     
           我们可以根据二分查找改进思路,统计start到end之间出现该数值的次数,如果大于end-start+1的话,说明重复的数字就在这个范围内,然后继续对这个范围取中间值,在进行这个过程,知道end与start相等,并且出现次数大于1,直接返回该值

4.二维数组中的查找(从左到右递增,从上到下递增,给一个target值,查找是否存在)

思路1:正常情况可以遍历整个数组,然后进行查找(时间复杂度O(m*n),m行, n列)

思路2:我们可以对右上角的数字进行比较,当等于右上角的数字时,返回true(找到target),

           当target大于右上角的数字时,向下移动一行,因为肯定大于右上角数字所在行的所有值,

           同理当小于target值时,直接向左移动一列,

          这样不断循环就可以知道这是否有该值,终止条件是行和列都在数组的范围内,保证不越界



11. 旋转数组的最小数字(3 ,4 ,5 ,1, 2)

思路1:遍历整个数组,找到下一个数字比自己小的,这个小的就是最小的数字,时间复杂度O(n)

思路2: 正常有序的数组的查找可以使用二分查找,这个旋转数组也可以使用二分超找,或者说设置前后,以及中间三个指针    (start,end,mid),循环的条件保证array[start]>=array[end]     (这样可以保证是旋转数组,而不是有序数组,如果是有序数组,直接最后返回array[0]),  这里面要仔细斟酌一下边界,start +1 =end,时,返回end,当array[start]>=array[mid], start = mid;       当array[mid]<=array[end]时,end = mid;


21.调整数组顺序使得奇数位于偶数前面

牛客网上要求,调整之后的数组的奇数的相对位置不变,偶数的相对位置也不变,

思路,使用二个指针或者说标记,一个放在数组的最前面left,一个放在数组的后面right,left发现一个偶数就将其放在数组的末尾,然后前面位置的所有数字均向前移动,将这个偶数放在数组的最末尾,这样知道left和right相遇


39. 数组中出现次数超过一半的数字

思路1:可以利用HashMap记录每一个值出现的次数,时间和空间复杂度均为O(n)

思路2:采用一种相消的思路,因为我们要找的数字是出现次数多余一半的,那么我们可以记录一个数字为num,和这个num出现的次数(或者说抵消之后的次数,初始化为1),当count为0时,我们改变num的值,将其设置为当前遍历的值。所以最后得到的num可能是出现次数大于一半的,

为什么说可能?

因为可能根本不存在超过一半的数字,这样就需要在重新遍历一遍,这样就记录num出现的次数,这样就可以找到超过一半的数字,并不存在返回0;


40. 最小的k个数

思路: 用一个容器存储k个数,当容器size小于k时,直接将数组中的数存入容器,当等于k时,找到容器中最大的数,并与当前遍历的数组的当前位置的值进行比较,如果这个数字比max小,则将这个数字替换掉max,遍历完全整个数组,这样可以得到最小的k个数,时间复杂度为O(nlogk);

为什么是logk,可以通过红黑树来存储容器中的数字,然后找到最大值。


42. 连续子数组的最大和

之前自己在做题的时候把他想的复杂了,虽然写出来了,但是分了很多情况等等

思路1:这里面思想很简单,记录一个max值,记录遍历到当前位置的最大和,其实在遍历整个数组的过程中,存储下一个sum,就是当前的和,

如果这个sum大于等于0,将当前数组值加进去就可以,然后比较sum和max的大小,得到最大的给max,

如果sum<0,直接舍弃,然后sum等于当前遍历的数组的值。

思路2:可以采用动态规划来做



45.把数组排成最小的数

思路:之前自己做题的时候考虑的比较复杂

思路:::::其实很简单,就是一个排序的改编,我们只需要考虑如何这个数组mn和nm谁大就可以了,比较这个二个数字如何组合最小即可,按照这种比较方式对整个数组进行排序,我们这里需要考虑组合之后的数字会不会超出int的范围,所以将数字转换成字符串,然后利用BigInteger来进行比较,这样可以得到一个数组的排序,之后转换成字符串即可。



51.数组中的逆序对

思路1:正常寻找数组中的逆序对,肯定需要遍历一个数组,当遍历到某个值时候,需要继续遍历里面的数组,所以需要O(n^2);但是时间复杂度比较高。

思路2:我们可以考虑改进或者说改变归并排序来对整个逆序对的改进,因为我们需要统计逆序对,可以考虑有一定的规律,

这里简单说下二路归并排序,假如有4个数,下标为0,1,2,3,

        0,1, 2, 3

  (0,1)        (2,3)

(0)(1)      (2)(3)

其实就是一个先分开在合并的过程,

而对于逆序对,来说,我们可以先统计组内的逆序对数,然后将其排序,之后对二组的序列统计逆序对数,合并,

eg:7,5,6,4(Offer的例子)

         (7,5,6,4)

   (7,5)         (6,4)

 (7)(5)       (6)(4)

   (5,7)【1】     (4,6)【1】

这里从后向前比较,比较6和7,因为7比6大,所以有【2】,因为7比第二组中组大数大,所以有二个逆序对,

然后比较5和6,【0】

然后比较5和4【1】

所以总共【5】

(用【5】标记表示有5个逆序对)

很明显整个过程就是一个归并排序。


53.数字在排序数组中出现的次数

思路1:正常应该考虑遍历真个数组,然后记录某个数字k出现的次数(时间复杂度为O(n))

思路2:我们知道查找某个数字可以使用二分查找,我们这里面也可以使用二分查找来做,做二次二分查找,第一次找到数组中第一次出现k的位置,第二次找到最后出现k的位置,这样就可以知道次数,

我们这里需要很好的控制边界,first需要是小标为0,或者前一个小于k,当前为k的值

last为数组最后一个数字或者下一个数字大于k,当前为k的值

在查找的时候,需要控制好搜索的边界,容易漏掉一些数字!


56. 数组中只出现一次的二个数字

知识点:二个相同的数字的异或等于0,  0与其它数字的异或还等于这个数字

思路: 数组中其它数字都是出现二次,所以异或之后都等于0,整个数组异或的值就等于这个二个不同的数字的异或的值,也就是说二个不同的数字的二进制至少有一位是不同的

那我们可以将数组分成二部分,一部分这个不同位上是1,一部分是0,这样每部分异或之后得到的值就是这二个只出现一次的数字。

而我们如何找到这个这二个数字的不同位,而且他有可能不只一位不同,所以我们可以将这个异或的值与1进行与操作,然后将1左移一位,这样直到找到异或的值的一个1。


56_2.数组中只有一个数字出现1次,其余出现三次,找到这个只出现一次的数字

思路:将所有数字转换为二进制,把所有数字对应的二进制位加和,如果这个和可以被3整除,则这个数字该二进制位上是0,否则是1


66.构建乘积数组

假设A[3] = {1,2,3}

要得到的B[]

            A[0]    A[1]    A[2]

B[0]      【】    【*】  【*】

B[1]      【*】  【】   【*】

B[2]       【*】  【*】   【】


正常需要O(n^2)的时间复杂度,但是我们可以使用空间换时间的方法,使时间复杂度为O(n),一个数组存【】左侧的所有值,右侧存另一组值,然后让二组值相乘,得到一组新的值,新值即为构建的乘积数组。










猜你喜欢

转载自blog.csdn.net/Lollipop66/article/details/80816166
今日推荐