《剑指Offer》第二章(一)题3-8
为春招实习做准备,记录一下《剑指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的更多相关文章
- 剑指offer第二章
剑指offer第二章 1.二维数组中的查找 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序.请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含 ...
- 剑指offer—第二章算法之二分查找(旋转数组的最小值)
旋转数组的最小数字 题目:把一个数组最开始的若干元素搬到数组的末尾,我们称之为数组的旋转.输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素.例如:数组{3,4,5,1,2}为{1,2,3,4, ...
- 剑指offer—第二章算法之快速排序
算法:排序和查找(二分查找,归并排序,快速排序),位运算等. 查找:顺序查找,哈希查找,二叉排序树查找,哈希表. 二分查找可以解决:"旋转数组中的最小数字","数字在排序 ...
- 《剑指offer(第二版)》面试题55——判断是否为平衡二叉树
一.题目大意 输入一颗二叉树,判断该二叉树是否为平衡二叉树(AVL树). 二.题解 <剑指offer>上给出了两种解决方式: 1.第一种是从根节点开始,从上往下遍历每个子节点并计算以子节点 ...
- 《剑指offer 第二版》题解
剑指Offer 按题号排序 面试题 3:数组中重复的数字 面试题 4:二维数组中的查找 面试题 5:替换空格 面试题 6:从头到尾打印链表 面试题 7:重建二叉树 面试题 8:二叉树的下一个节点 面试 ...
- 经典面试题目——找到第n个丑数(参考《剑指offer(第二版)》面试题49)
一.题目大意 给你一个数n,要求返回第n个丑数.其中,丑数的定义如下: 丑数是指只包含因子2.3和5的数.(数字1也是丑数,不过是个特例)引用<剑指offer>上的话来说,对于一个数M,如 ...
- 《剑指offer(第二版)》——面试题36:二叉搜索树与双向链表
具体的题目大意和参考思路在此处不详述(见<剑指offer>),实质就是在中序遍历的过程中调整指针的指向,关于中序遍历有递归和非递归两种操作,所以此处也用了两种方法. 方法1(递归法): 代 ...
- 《剑指offer(第二版)》面试题60——n个骰子的点数
一.题目描述 把n个骰子仍在地上,所有的骰子朝上的一面的点数之和为s,输入n,打印出s所有可能的值出现的概率. 二.题解 <剑指offer>上给出的两种方法,尤其是代码,晦涩难懂且没有注释 ...
- 《剑指offer(第二版)》面试题64——求1+2+...+n
一.题目描述 求1+2+3+...+n,要求不能使用乘除法.for.while.if.else.switch.case等关键字以及条件判断语句 (即三元运算符,A? B : C) 二.题解 虽然求和问 ...
- 结合《剑指offer(第二版)》面试题51来谈谈归并排序
一.题目大意 给定一个数组A,对于数组A中的两个数字,如果排在前面的一个数字大于(必须大于,等于不算)后面的数字,则这两个数字组成一个逆序对.要求输出数组A中的逆序对的总数.例如,对于数组{7,5,6 ...
随机推荐
- 高德API对接
<?php class GaoDeAPI { private $key = '123456789'; # 你应用key /** * 地址转经纬度 */ public function getTr ...
- Don’t Repeat Yourself,Repeat Yourself
Don't Repeat Yourself,Repeat Yourself Don't repeat yourself (DRY, or sometimes do not repeat yoursel ...
- ASCII、UNICODE、UTF
在计算机中,一个字节对应8位,每位可以用0或1表示,因此一个字节可以表示256种情况. ascii 美国人用了一个字节中的后7位来表达他们常用的字符,最高位一直是0,这便是ascii码. 因此asci ...
- codevs 3981 动态最大子段和(线段树)
题目传送门:codevs 3981 动态最大子段和 题目描述 Description 题目还是简单一点好... 有n个数,a[1]到a[n]. 接下来q次查询,每次动态指定两个数l,r,求a[l]到a ...
- nor flash之写保护
背景 没有电池的嵌入式设备,很容易发生随机掉电.因此要让产品可靠稳定,就必须保证各种场景下的掉电安全. 例如系统更新过程随机掉电,不能导致系统无法启动.例如正常读写flash过程中掉电,最多正在传输的 ...
- (1)解锁 MongoDB replica set核心姿势
副本集Replica Set是一个术语,定义具有多节点的数据库集群,这些节点具有主从复制(master-slave replication) 且节点之间实现了自动故障转移. 这样的结构通常需要具有奇数 ...
- cogs 3. 服务点设置 dijkstra
3. 服务点设置 ★ 输入文件:djsa.in 输出文件:djsa.out 简单对比时间限制:1 s 内存限制:128 MB [问题描述] 为了进一步普及九年义务教育,政府要在某乡镇建 ...
- Java入门 - 语言基础 - 10.条件语句
原文地址:http://www.work100.net/training/java-if-else.html 更多教程:光束云 - 免费课程 条件语句 序号 文内章节 视频 1 概述 2 if...e ...
- 关于checkbox取值的问题
今天,在做一个小界面的时候,要用到checkbox来判断用户是否选择记住密码. 本来我想这个不是挺简单吗,直接用jquery获取checkbox的checked属性值好了,如下: var rememb ...
- PHP——数组
数组的定义 数组能够在单个变量中存储多个值. 创建空数组: $arr = array();//表示创建一个空数组,并把创建的空数组赋值给变量$arr 数值数组 自动分配 ID 键(ID 键总是从 0 ...