最近根据专题更新一些剑指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),一个数组存【】左侧的所有值,右侧存另一组值,然后让二组值相乘,得到一组新的值,新值即为构建的乘积数组。