为春招实习做准备,记录一下《剑指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)

树结构很简单,不说了。

《剑指Offer》第二章(一)题3-8的更多相关文章

  1. 剑指offer第二章

    剑指offer第二章 1.二维数组中的查找 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含 ...

  2. 剑指offer—第二章算法之二分查找(旋转数组的最小值)

    旋转数组的最小数字 题目:把一个数组最开始的若干元素搬到数组的末尾,我们称之为数组的旋转.输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素.例如:数组{3,4,5,1,2}为{1,2,3,4, ...

  3. 剑指offer—第二章算法之快速排序

    算法:排序和查找(二分查找,归并排序,快速排序),位运算等. 查找:顺序查找,哈希查找,二叉排序树查找,哈希表. 二分查找可以解决:"旋转数组中的最小数字","数字在排序 ...

  4. 《剑指offer(第二版)》面试题55——判断是否为平衡二叉树

    一.题目大意 输入一颗二叉树,判断该二叉树是否为平衡二叉树(AVL树). 二.题解 <剑指offer>上给出了两种解决方式: 1.第一种是从根节点开始,从上往下遍历每个子节点并计算以子节点 ...

  5. 《剑指offer 第二版》题解

    剑指Offer 按题号排序 面试题 3:数组中重复的数字 面试题 4:二维数组中的查找 面试题 5:替换空格 面试题 6:从头到尾打印链表 面试题 7:重建二叉树 面试题 8:二叉树的下一个节点 面试 ...

  6. 经典面试题目——找到第n个丑数(参考《剑指offer(第二版)》面试题49)

    一.题目大意 给你一个数n,要求返回第n个丑数.其中,丑数的定义如下: 丑数是指只包含因子2.3和5的数.(数字1也是丑数,不过是个特例)引用<剑指offer>上的话来说,对于一个数M,如 ...

  7. 《剑指offer(第二版)》——面试题36:二叉搜索树与双向链表

    具体的题目大意和参考思路在此处不详述(见<剑指offer>),实质就是在中序遍历的过程中调整指针的指向,关于中序遍历有递归和非递归两种操作,所以此处也用了两种方法. 方法1(递归法): 代 ...

  8. 《剑指offer(第二版)》面试题60——n个骰子的点数

    一.题目描述 把n个骰子仍在地上,所有的骰子朝上的一面的点数之和为s,输入n,打印出s所有可能的值出现的概率. 二.题解 <剑指offer>上给出的两种方法,尤其是代码,晦涩难懂且没有注释 ...

  9. 《剑指offer(第二版)》面试题64——求1+2+...+n

    一.题目描述 求1+2+3+...+n,要求不能使用乘除法.for.while.if.else.switch.case等关键字以及条件判断语句 (即三元运算符,A? B : C) 二.题解 虽然求和问 ...

  10. 结合《剑指offer(第二版)》面试题51来谈谈归并排序

    一.题目大意 给定一个数组A,对于数组A中的两个数字,如果排在前面的一个数字大于(必须大于,等于不算)后面的数字,则这两个数字组成一个逆序对.要求输出数组A中的逆序对的总数.例如,对于数组{7,5,6 ...

随机推荐

  1. 高德API对接

    <?php class GaoDeAPI { private $key = '123456789'; # 你应用key /** * 地址转经纬度 */ public function getTr ...

  2. Don’t Repeat Yourself,Repeat Yourself

    Don't Repeat Yourself,Repeat Yourself Don't repeat yourself (DRY, or sometimes do not repeat yoursel ...

  3. ASCII、UNICODE、UTF

    在计算机中,一个字节对应8位,每位可以用0或1表示,因此一个字节可以表示256种情况. ascii 美国人用了一个字节中的后7位来表达他们常用的字符,最高位一直是0,这便是ascii码. 因此asci ...

  4. codevs 3981 动态最大子段和(线段树)

    题目传送门:codevs 3981 动态最大子段和 题目描述 Description 题目还是简单一点好... 有n个数,a[1]到a[n]. 接下来q次查询,每次动态指定两个数l,r,求a[l]到a ...

  5. nor flash之写保护

    背景 没有电池的嵌入式设备,很容易发生随机掉电.因此要让产品可靠稳定,就必须保证各种场景下的掉电安全. 例如系统更新过程随机掉电,不能导致系统无法启动.例如正常读写flash过程中掉电,最多正在传输的 ...

  6. (1)解锁 MongoDB replica set核心姿势

    副本集Replica Set是一个术语,定义具有多节点的数据库集群,这些节点具有主从复制(master-slave replication) 且节点之间实现了自动故障转移. 这样的结构通常需要具有奇数 ...

  7. cogs 3. 服务点设置 dijkstra

    3. 服务点设置 ★   输入文件:djsa.in   输出文件:djsa.out   简单对比时间限制:1 s   内存限制:128 MB [问题描述] 为了进一步普及九年义务教育,政府要在某乡镇建 ...

  8. Java入门 - 语言基础 - 10.条件语句

    原文地址:http://www.work100.net/training/java-if-else.html 更多教程:光束云 - 免费课程 条件语句 序号 文内章节 视频 1 概述 2 if...e ...

  9. 关于checkbox取值的问题

    今天,在做一个小界面的时候,要用到checkbox来判断用户是否选择记住密码. 本来我想这个不是挺简单吗,直接用jquery获取checkbox的checked属性值好了,如下: var rememb ...

  10. PHP——数组

    数组的定义 数组能够在单个变量中存储多个值. 创建空数组: $arr = array();//表示创建一个空数组,并把创建的空数组赋值给变量$arr 数值数组 自动分配 ID 键(ID 键总是从 0 ...