剑指offer 3 高质量的代码
参考:
摘要
面试小提示:
- 应聘者在写代码的时候,最好用完整的英文单词组合命名变量和函数,以便面试官能一眼读懂代码的意图。
- 从功能测试、边界测试、负面测试3个方面设计测试用例,以保证代码的完整性
3.3 代码的完整性
面试题11:数值的整数次方
题目:实现函数double Power(double base, int exponent),求base的exponent次方。不得使用库函数,同时不需要考虑大数问题。
思路解析
-
全面考虑各种类型与边界值
- base输入:负数,零,正数分开考虑
- exponent输入:负数,零,正数分开考虑
-
需要注意的地方:
- 当指数为负数的时候
- 当底数为零且指数为负数的情况
- 在判断底数 base (double 类型) 是不是等于0的时候,不能直接写base==0, 因为计算机内表示小数时有误差,只能判断他们的差的绝对值是不是在一个很小的范围内
-
优化代码速度
- 递归地调用本方法进行计算,只改变传递的参数 exp
- 当n为偶数, a n = a n / 2 ∗ a n / 2 a^n = a^{n/2} * a^{n/2} an=an/2∗an/2
- 当n为奇数, a n = a ( n − 1 ) / 2 ∗ a ( n − 1 ) / 2 ∗ a a^n = a^{(n-1)/2} * a^{(n-1)/2} * a an=a(n−1)/2∗a(n−1)/2∗a
- 利用右移一位运算代替除以2
- 利用位与运算代替了求余运算法%来判断一个数是奇数还是偶数
class Solution: def Power(self, base, exp): # 处理边界值 if exp == 0: return 1 if exp == 1: return base if exp == -1: return 1/base res = self.Power(base, exp>>1) res *= res # 判断exp的奇偶性 if exp & 0x1 == 1: # 为奇数 res *= base return res
面试题12:打印1到最大的n位数
题目:输入数字n,按顺序打印出从1到最大的n位十进制数。比如输入3,则打印出1、2、3一直到最大的3位数即999。
思路解析
- 输入的n位数是否会导致溢出,因此利用字符串模拟整数的加法。定义长度为 n 的字符列表。
- 定义子方法,对数字进行加一运算,并且返回该数字是否溢出:
- 从后到前遍历每一个数字字符;
- 每次取最后一个数字字符,对其加一,并返回字符串形式的结果;
- 如果遇到 求和结果 超过10的情况,保存进位与数值结果。
注意:
4. 在打印函数中,需要判断打印的数字是否是以0开头的,同时判断条件是 num[i] != “0”,不能写作 num[i] != 0,因为是使用str类型的,后面一种写法导致判断无法成功。
def Print1ToMaxOfNDigits(n):
# 判断特殊情况
if n <= 0:
return
number = ['0'] * n
while not Increment(number):
PrintNumber(number)
# 判断是否达到了最大数字:如果在最高位idx=0产生进位,则返回True
def Increment(str_num):
# 是否在最高位溢出
isOverflow = False
# 是否产生进位
nTakeOver = 0
nLength = len(str_num)
# 对数组中的元素从后向前遍历
for i in range(nLength-1, -1, -1):
# 直接调用python自带的 int 方法转换,或者字符减字符来得到数值大小
nSum = int(str_num[i]) + nTakeOver
if i == nLength - 1:
nSum += 1
# 如果产生进位
if nSum >= 10:
if i == 0:
isOverflow = True
else:
# 产生进位
nTakeOver = 1
nSum -= 10
number[i] = str(nSum)
else:
number[i] = str(nSum)
break
return isOverflow
# 依次打印字符列表中从非零元素开始的每个数字字符
def PrintNumber(number):
isBeginning0 = True
nLength = len(number)
for i in range(nLength):
if isBeginning0 and number[i] != '0':
isBeginning0 = False
if not isBeginning0:
# 必须分开连续打印,结果才会是数字
print('%c' % number[i], end='')
print('')
Print1ToMaxOfNDigits(2)
面试题13:在O(1)时间删除链表结点
题目:给定单向链表的头指针和一个结点指针,定义一个函数在O(1)时间删除该结点。
- 在O(1)时把下一个结点的内存复制覆盖要删除的结点,并删除下一个结点;
- 对于尾结点而言,由于仍然需要顺序查找,时间复杂度是O(n)。
- 因此总的平均时间复杂度是[(n-1)*O(1)+O(n)]/n,结果还是O(1)
class ListNode:
def __init__(self, x=None):
self.val = x
self.next = None
def __del__(self):
self.val = None
self.next = None
class Solution:
def DeleteNode(self, pListHead, pToBeDeleted):
# 判断特殊情况,原始链表或者欲删除节点为空
if not pListHead or not pToBeDeleted:
return None
# 一般情况,直接将欲删除节点用 pToBeDeleted.next 替换
if pToBeDeleted.next != None:
pNext = pToBeDeleted.next
pToBeDeleted.val = pNext.val
pToBeDeleted.next = pNext.next
pNext.__del__()
# 欲删除节点为头结点
elif pListHead == pToBeDeleted:
pToBeDeleted.__del__()
pListHead.__del__()
# 欲删除节点为尾节点,需要从头遍历,直到找到尾节点,然后对其替换
else:
pNode = pListHead
while pNode.next != pToBeDeleted:
pNode = pNode.next
pNode.next = None
pToBeDeleted.__del__()
面试题14:调整数组顺序使奇数位于偶数前面
题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
思路梳理
维护两个指针,第一个指针初始化时指向数组的第一个数字,它只向后移动;第二个指针初始化时指向数组的最后一个数字,它只向前移动。
注意:在一个函数的输入中, 输入另一个函数的写法func = self.funcName,
funcName不需要加参数括号
class Solution:
# 一个类似于快排的方法, 只是简单的满足了奇数在前,偶数在后, 奇数的顺序发生了改变
def reOrderArray(self, array):
if len(array) < 1:
return
elif len(array) == 1:
return array
front = 0
rear = len(array)-1
while front <= rear:
while array[front] & 0x1 == 1:
front += 1
while array[rear] & 0x1 == 0:
rear -= 1
array[front], array[rear] = array[rear], array[front]
return array
# 直接利用Python的trick, 写一个简单的排列数组, 顺序不变
def reOrderArray2(self, array):
left = [x for x in array if x & 1]
right = [x for x in array if not x & 1]
return left + right
def reOrderArray3(self, array):
if len(array) < 1:
return []
if len(array) == 1:
return array
arrayOdd = []
arrayEven = []
for num in array:
if num & 0x1:
arrayOdd.append(num)
else:
arrayEven.append(num)
return arrayOdd+arrayEven
# 可扩展性的解法
# 注意在一个函数的输入中, 输入另一个函数的写法func = self.fucName,
# funcName不需要加括号
def Reorder(self, pData, length, func):
if length == 0:
return
pBegin = 0
pEnd = length - 1
while pBegin < pEnd:
while pBegin < pEnd and not func(pData[pBegin]):
pBegin += 1
while pBegin < pEnd and func(pData[pEnd]):
pEnd -= 1
if pBegin < pEnd:
pData[pBegin], pData[pEnd] = pData[pEnd], pData[pBegin]
return pData
def isEven(self, n):
return not n & 0x1
def ReorderOddEven(self, pData):
length = len(pData)
return self.Reorder(pData, length, func=self.isEven)