前言
牛客网剑指offer题目汇总,记录自己刷题历程,原文链接:点击查看
题目一:二维数组中的查找 (数组)
题目描述:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:矩阵从左到右,从上到下都是有序的,因此,先查找目标数target在二维数组中的哪一行,通过判断是否在该行的第一个数和最后一个数之间,如果是,则定位到了行,因为该行是有序的,所以接下来通过二分查找,即可查找成功。
注:时间复杂度的话,查找行花了O(n), 二分查找O(logn),总共应该O(n)+O(logn),奇怪的是我使用普通的遍历查找,运行时间更更少!这不科学,二分查找效率应该更高,这应该是数据量小导致的。
代码:
|
|
题目二:替换空格 (字符串)
题目描述:请实现一个函数,将一个字符串中的空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
思路:
第一种: 直接用StringBuffer提供的replace函数。(不可取,得自己搞)
|
|
Replaces the characters in a substring of this sequence with characters in the specified String.
第二种: 先统计出空格的个数,然后计算得到替换后的字符串的长度,然后重新更新字符串的长度,此时,从后向前遍历字符串,如果是空格,替换,如果不是空格,赋原值,知道遍历结束。
注:从后往前,每个空格后面的字符只需要移动一次。从前往后,当遇到第一个空格时,要移动第一个空格后所有的字符一次;当遇到第二个空格时,要移动第二个空格后所有的字符一次;以此类推。所以总的移动次数会更多。
代码:
|
|
题目三:从尾到头打印链表 (链表)
题目描述:输入一个链表,从尾到头打印链表每个节点的值。
思路:
第一种: 遍历链表,将链表存入Arraylist数组vals中,然后对vals进行反转。(确实很low)
第二种: 使用递归思想,递归到最后一个结点,然后层层返回,此时依次add进vals数组中。
代码:
|
|
|
|
题目四:重建二叉树 (树)
题目五:用两个栈实现队列 (栈、队列)
题目描述:用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。
思路:
入队push:将元素进栈A。
出队pop:判断栈B是否为空,如果为空,则将栈A中所有元素pop,并push进栈B,然后栈B出站。
注: 1 push,将数据直接压入stack1即可;2 pop,将stack1中的数据弹出压入到stack2中,则数据顺序相反,为保证最新进入的数据一致处于栈顶,只有将stack2中的数据全部pop后,才能继续将stack1中的数据压入到stack2中,继续做pop。
代码:
|
|
题目十一:二进制中1的个数 (位运算)
题目描述:输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
思路:
方法一: 通过n&n-1可以消除整数最右边的1。多次执行n=n&n-1,最终n为0时,表示所有的1都被消除了,消除1所执行的次数即为1的个数。
分析:为啥n&n-1可以消除整数最右边的1? 如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变成0,原来在最右边1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。我们发现减1的结果是把从最右边的1开始的所有位都取反了,这个时候将n于n-1做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变为0,那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
n=12 1100
n-1=11 1011
n=12&11 1000
n=8 1000
n-1=7 0111
n=8&7 0000
代码:
更多参考点击:算法-求二进制数中1的个数
题目十二:数值的整数次方 (代码的完整性)
题目:给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。
思路:
方法一:估计是个人都能想出来,假设要求a^b,只需将a连乘b次,此时的时间复杂度是O(b)。
方法二: 快速幂,快速幂能将复杂度降至O(logb),确实是快了不少。
原理:假设我们要求a^b,b拆成二进制时,该二进制数第i位的权值为2^(i-1),如下:
a^11 = a^(1011) = a^(1000+0010+0001) = a^(2^0 + 2^1 + 2^3) = a^(2^0)*a^(2^1)*a^(2^3)
通过使用&和>>位运算操作,依次遍历指数二进制表示中的每一位,我们发现,结果为每一位值为1时,也即a^(2^i)的累乘,此时,相邻位的值都是前一个值的翻倍。
更多信息:快速幂
代码:
题目十三:调整数组顺序使奇数位于偶数前面 (数组)
题目描述:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
思路:
方法一: 插入排序的思想。从第二个数开始,之前的数(也就只有一个数)是已经排好序的,此时如果第二个数是奇数的话,我们只需要插入到之前序列中所有偶数之前,如果是偶数的话,则不需要插入;继续第三个数,依次遍历完数组即可。
方法二:重新定义一个vector,从前往后遍历vector,遇到奇数push_back;再遍历一遍vector,遇到偶数push_back,以空间换时间,这就不实现了,easy。
代码:
题目十四:链表中倒数第k个结点
题目描述:输入一个链表,输出该链表中倒数第k个结点。
思路: 定义两个指针p1,p2,分别指向表头,再定义count=0,表示p1在第0个结点,此时p1开始遍历链表,每经过一个结点count++,当count>=k时,p2开始遍历链表,直到p1遍历结束,此时p2指向的结点即为倒数第k个结点。
代码:
题目十五:反转链表
题目描述:输入一个链表,反转链表后,输出链表的所有元素。
思路: 依次遍历每个结点,同时通过头插法再重新创建新的链表
注:可以利用之前的结点,而不需要重新创建新的结点,以后改进。
代码:
题目十六:合并两个排序的链表
题目描述:输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
思路: 当两个链表list1,list2(指向第一个结点)不空时,比较list1.val和list2.val的值,较小的作为合并后的第一个结点,假设list1.val较小,此时list1 = list1.next,继续比较list1.val和list2.val,直到某一个链表遍历结束,将没遍历结束的链表添加到合并后链表末尾。
注:可以利用之前的结点,而不需要重新创建新的结点,这样,最后直接指向没遍历结束的链表即可,同时也节省了创建新的结点的空间,后续改进。
代码:
|
|