为春招实习做准备,记录一下《剑指Offer》里面的面试题

第二章

面试题3:数组之中的重复数字。

这个题吧,虽然不难,但是不知道为什么就是看了很久,可能很久没有做算法题了。最后面一句话说的挺好的,给你出题之后,要问清楚题目,以及要求,时间效率优先还是空间效率优先,虽然我一般都会选择时间效率优先,因为内存现在都比较大了。

题目很简单,一个长度为n的数组,数字都在0~n-1,找出其中任意一个重复的数字,注意是任意一个。

书中讲到了三个算法:

1.时间复杂度是O(n),空间复杂度也是O(n)

遍历数组,碰到一个数字,先在哈希表之中判断是否存在该数字,如果不存在,则将其放入到哈希表之中,如果存在,则找出一个重复的数字。

很明显,需要一个O(n)的哈希表,遍历数组O(n)复杂度

我感觉这种挺好的,好理解,也好实现,最多有O(n)的空间复杂度,但是面试肯定不可能问你这么简单的。

2.时间复杂度是O(n),空间复杂度O(1)

首先明白最重要的一点,如果这个数组是没有重复数字的,那么当数组有序时,数字m就一定在m位置出现。同样的遍历数组,假设现在遍历到下标为i的数字m,判断i==m 是否成立,成立,则该数字就在他本身的位置,不成立,则判断数组之中下标为i,m两个数字是否相等,相等,则找到一个重复数字,不相等,则将下标为i,m的数字进行交换,将数字m放到他本来的位置,之后继续判断新的i下标处的数字,直到相等为止,i++,进行下一轮判断。

说实话一开始不理解他为什么要交换,最后想了一下,交换是为了让每一个数字都在它应该在的位置,这样当我们审查第i个数字m时,如果第m个数字就是m,已经被交换到了他应该在的位置,这时候比较就可以得出一个重复的数字。

下面是代码,可以看到,一定不能忘了对输入的有效性进行判断,这是一种良好的编程习惯吧,书上是c++,python 方便一点

class Solution:
def findDuplicate(self, nums) -> int:
# 找出一个重复数字
if nums == None: # 输入检测
return False
length = len(nums)
for num in nums: # 是否有超过范围的数字
if num > length-1 or num < 0:
return False
for i in range(length): # 遍历数组
while nums[i] != i: # 这个数字是否在它应该在的位置
if nums[i] == nums[nums[i]]: # 判断是否有重复
return nums[i]
# 没有重复则进行交换,nums[i] 和 nums[nums[i]]
temp = nums[i]
nums[i] = nums[temp]
nums[temp] = temp
return False

可以看到虽然有两个循环,但是每一个数字最多交换两次就可以到它自己的位置,所以总体的复杂度为O(n)。

3.不能修改原来数组

这种其实题目都和上面那一个不一样,要求不能修改原来的数组,并且长度是n+1,所有数字都在1~n之间。

先说一下一点。

由于数组长度是n+1,但是数字的范围都是在1~n之间,所以肯定是有重复数字的,和之前不同。

第一种方法:

创建一个n+1大小的数组,遍历原来的数组,将数字m复制到新数组的下标为m的位置,如果m位置已经有了数字,那么就说明这个数字是重复的,时间空间复杂度都是O(n),还是比较简单的一种方法。

第二种方法:

时间复杂度O(nlogn),空间复杂度O(1)

基本思想是利用二分法逐渐缩小重复数字的区间(注意这个区间是1~n,而不是原来的数组),最后找到重复数字。将数字1~n从中间分为两部分,1~m和m+1~n,然后判断数字1~m在数组之中出现的次数,如果大于m,那么说明1~m之间有重复数字,反之就是m+1~n,之后用相同的方法,逐渐缩小区间即可。但是这种方法不能找出全部的重复数字,因为当出现次数小于等于m时,无法判断是一个数字出现了两次,还是每个数字都出现了一次,如果是前一种情况,那么就丢失了重复数字。

代码如下:

class Solution:
def findDuplicate(self, nums) -> int:
# 找出一个重复数字
if nums == None: # 输入检测
return False
length = len(nums)
for num in nums: # 是否有超过范围的数字
if num > length-1:
return False
print("2222")
start = 1
end = length-1 # 对数字1-n,所以是length-1
while end >= start:
middle = int(((end-start)/2) + start) # 计算中点
count = self.countRange(nums, start, middle) # 统计数组之中start~middle数字在数组之中出现的次数
if end == start: # 判断是否查找结束
if count > 1: # 说明有重复数字
return start
else:
break
if count > (middle-start+1): # 比较出现次数和前值区间数字个数,大于则说明前置区间有重复数字
end = middle
else:
start = middle + 1
return False def countRange(self, nums, start, end): # 统计某一个区间之内数字在数组之中出现的次数
if nums == None:
return False
count = 0
for i in nums:
if i <= end and i >= start:
count += 1
return count  

面试题4:

这个题目很简单,判断一个数字是否在一个二维数组之中存在,这个二维数组从左向右递增,从上向下递增,书中的解法很巧妙,从一个具体的问题入手,将复杂的问题普遍化。

从右上角数字开始,比较该数字,如果该数字大于目标数字,则剔除最后一列,因为该数字之下的数字都大于该数字,肯定也大于目标数字了,同理该数字小于目标数字,则剔除第一行,当然如果相等了就结束。右上角和左下角都可以,但是左上角和右下角不行,因为无法通过判断来剔除一行或者一列。代码如下:

class Solution:
def find(self, nums, target, rows, columns) -> int:
if nums == None or rows < 0 or columns < 0 or target == None:
return False
row = 0
column = columns-1
while row < rows and columns >= 0:
if nums[row][column] == target:
return True
elif nums[row][column] > target: # 剔除最后一列
column -= 1
elif nums[row][column] < target: # 剔除最上面一行
row += 1 return False

 没什么说得,右上角数字就是nums[row][column]

面试题5:

这个题目也很简单,给你一个字符串,将里面的空格进行替换,替换成 %20 ,一般特殊字符替换规则都是替换为%+ASCII的十六进制表示。

注意一个问题,替换之后的字符串会变长,所以进行替换时必须进行移动,不然会出现内存覆盖的现象,比如 空格 替换为 %20,一个字符变成了三个字符,如果直接插入,那就会将该空格之后的两个字符覆盖掉。

时间复杂度是O(n2):n的平方

遍历字符串,碰到空格,就将空格之后的字符整体向后移动2个位置,然后将%20以当前位置为基准,插入即可。由于遇到空格就需要移动字符串,遍历又需要O(n)的复杂度,所以时间复杂度是O(N2)

时间复杂度为O(N)的方法:

很巧妙,反正我现在是想不到的。基本思路是先分配所需要的内存,从后向前遍历,这样子就不需要每次遇到空格都移动字符串了。具体来说,先遍历字符串,计算空格数量,然后计算出替换之后的字符串长度。接着声明两个指针,一个指向当前字符串的尾部p1,另一个指向替换之后字符串的尾部p2,使用p1从后向前进行遍历,对于碰到的字符,分两种情况进行讨论:

1.碰到了非空格字符,将p1所指的字符复制到p2位置处,并将p1,p2都向前移动一个位置

2.碰到了空格,此时,以p2为基准,在p2之前(包括p2)插入%20,并将p2向前移动三个位置,p1向前移动一个位置。

这样循环直到p1,p2互相重合,说明已经遍历完了字符串。

属实巧妙,相当于先计算出来需要多长的字符串,然后按照逻辑直接对字符串进行填充,而且空间复杂度也是O(1),虽然直接申请一个字符串,然后无脑复制过去也可以,但是需要O(N)的空间复杂度,而且也显得笨笨的。

代码如下:

class Solution{
public void ReplaceBlank(StringBuilder stringBuilder, int length){
if(stringBuilder==null || length < 0){
return;
}
int originalLength = 0; //原字符串长度
int numberOfBlank = stringBuilder.count; //字符串之中空格的数量
int i = 0;
while(stringBuilder.charAt(i) != '\0'){
originalLength++; //增加源字符串长度
if(stringBuilder.charAt(i) == ' '){
numberOfBlank++;
}
i++;
}
int newLength = originalLength + numberOfBlank * 2; //新字符串长度
if(newLength > length){
return;
}
int indexOfOriginal = originalLength;
int indexOfNew = newLength;
while(indexOfOriginal >= 0 && indexOfNew > indexOfOriginal) {
if(stringBuilder.charAt(indexOfOriginal) == ' '){
//空格,进行%20填充 --时候就能够向前移动了
stringBuilder.replace(indexOfNew--, indexOfNew+1, "0");
stringBuilder.replace(indexOfNew--, indexOfNew+1, "2");
stringBuilder.replace(indexOfNew--, indexOfNew+1, "%");
}else{
//不是空格,正常填充字符
stringBuilder.replace(indexOfNew--, indexOfNew+1, stringBuilder.substring(indexOfOriginal, indexOfOriginal+1));
}
--indexOfOriginal; //原字符串指针向前移动
}
}
}

java真操蛋,感觉的真的难用。

面试题6:从尾到头打印链表

这个题比较简单,纯粹就是复习回顾的,直接上代码;不过注意分析问题时候,这个题相当于是第一个遍历的节点,最后一个输出,即先进后出,典型的栈的特点,所以直接用栈来进行存储或者递归就可以了。

class ListNode:
def __init__(self, val=None):
self.val = val
self.next = None def reverseOut(head: ListNode):
if head.next == None:
return
stack = []
while head.next:
stack.append(head.next.val)
head = head.next
while stack:
print(stack.pop()) # 测试
head = ListNode()
node = head
for i in range(10):
node.next = ListNode(i)
node = node.next
reverseOut(head)

 递归方法很简单,不过层数会很深,2n次方级别的

def recursion(head: ListNode):
if head.next:
recursion(head.next)
print(head.next.val)

面试题7:重建二叉树

思路很简单,但是代码挺复杂的这个,暂时略过

面试题8,二叉树的下一个节点

题目:给定二叉树之中的一个节点,然后找出在中序遍历的结果之中,该节点的下一个遍历的节点。二叉树中每一个节点有一个额外的指针指向其父节点

问题分析:中序遍历很简单,“”左中右,所以可以想到,在当前节点遍历之后,如果它有右子树,那么就会遍历他的右子树最左边的那个节点,如果没有右子树的话,就说明当前节点和它的左子树(有的话)都已经遍历完了,该向上遍历了,根据中序遍历的特点,我们需要找到一个左子节点,以该左子节点为根节点的树都已经遍历完了,该遍历该左子节点的父节点了,所以它的父节点就是下一个需要遍历的节点。

所以,分为三种情况:

1.当前节点有右子树,那么下一个遍历的就是右子树的最左节点

当前节点没有右子树,可以分为两种情况

1.当前节点是左子节点,下一个遍历的就是该节点的父节点

2.当前节点是右节点并且没有右子树,有没有左子树不影响。向上回溯,找到一个左子节点,该节点的父节点即为所求。

但是其实1情况是二情况的一种特殊情况,即该节点本身就是左子节点,不需要再向上进行回溯,所以写代码时候当做一种情况写就可以了。

代码如下:

class BTree:
def __init__(self, val=None, left=None, right=None, parent=None):
self.val = val
self.left = left
self.right = right
self.parent = parent def getNext(pNode: BTree):
if pNode == None:
return
pNext = None
# 有右子树的情况
if pNode.right != None:
temp = pNode.right
while temp.left: # 右子树的最左边的节点
temp = temp.left
pNext = temp
else:
current = pNode
parent = pNode.parent
# 向上回溯,找到一个节点为左子节点,循环条件:当前节点是右子节点且有父节点
while parent != None and current == parent.right:
current = parent
parent = parent.parent
pNext = parent
return pNext

测试起来比较麻烦,因为需要构建节点的父指针,得一个一个设置,简单写一个

a = BTree(val=0)
d = BTree(val=3)
g = BTree(val=6)
b = BTree(val=1, left=d, parent=a)
c = BTree(val=2, right=g, parent=a)
d.parent = b
g.parent = c
a.left = b
a.right = c print(getNext(d).val)

树结构很简单,不说了。

最新文章

  1. 释放Android的函数式能量(I):Kotlin语言的Lambda表达式
  2. sublime text 3 配置php开发环境
  3. 【leetcode❤python】107. Binary Tree Level Order Traversal II
  4. vim 打开多个文件
  5. js获取浏览器地址栏传递的参数
  6. JS列
  7. LOJ #10070 最小生成树计数
  8. C#字符串和数组互转
  9. Linux下系统时间函数、DST等相关问题总结(转)
  10. Linux基础命令---uniq
  11. mysql获取某个表的所有属性名及其数据
  12. 全网最详细的Oracle10g/11g的官方下载地址集合【可直接迅雷下载安装】(图文详解)
  13. docker-compose教程(安装,使用, 快速入门)
  14. Windows下python3安装pip管理包(转贴)
  15. android 带CheckBox对话框
  16. 二十四种设计模式:访问者模式(Visitor Pattern)
  17. lua_pcall,lua_call 调用前后栈情况
  18. Xtrareport 多栏报表
  19. parameter server
  20. [原创]java WEB学习笔记12:一个简单的serlet连接数据库实验

热门文章

  1. Go指针,如此轻松掌握,希望有收获
  2. 一起来立Flag吧!超炫的数据图表分析 2020 年 Java 技术趋势
  3. 百度地图开发API
  4. MySQL快速回顾:更新和删除操作
  5. extract函数的使用
  6. Beetlex实现完整的HTTP协议
  7. cogs 1298. 通讯问题 Tarjan
  8. 权限认证基础:区分Authentication,Authorization以及Cookie、Session、Token
  9. 存储过程带参数和sqlcommand
  10. 6.反编译 java---class (字节码文件)---反编译(IDEA):