剑指offer 3 高质量的代码
参考:
3.4 代码的鲁棒性
面试题15:链表中倒数第k个结点
题目:输入一个链表,输出该链表中倒数第k个结点。
思路梳理
- 遍历链表两次
- 需要注意特殊情况:
- 如果输入的链表为空;
- k大于链表的长度;
- k为0的情况。
- 对于正常情况,设置两个指针分别指向头结点,第一个指针向前走k-1步,走到正数第k个结点,同时保持第二个指针不动,然后第一个指针和第二个指针每次同时前移一步,这样第一个指针指向尾结点的时候,第二个指针指向倒数第k个结点。
- 判断尾结点的条件是 pNode.next == None。
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, x):
# self.val = x
# self.next = None
# -*- coding:utf-8 -*-
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
def FindKthToTail(self, head, k):
if head == None or k <= 0:
return None
pAHead = head
pBehind = None
for i in range(k-1):
if pAHead.next != None:
pAHead = pAHead.next
else:
return None
pBehind = head
while pAHead.next != None:
pAHead = pAHead.next
pBehind = pBehind.next
return pBehind
相关题目:
- 求链表的中间结点。定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另一个指针一次走两步。当走得快的指针走到链表的末尾时,走得慢的指针正好在链表的中间。
- 判断一个单向链表是否形成了环形结构。
- 定义两个指针,同时从链表的头结点出发,一个指针一次走一步,另一个指针一次走两步。如果走得快的指针追上了走得慢的指针,那么链表就是环形链表;
- 如果走得快的指针走到了链表的末尾(m_pNext指向NULL)都没有追上第一个指针,那么链表就不是环形链表。
面试题16:反转链表
需要注意三个问题:
- 输入的链表头指针为None或者整个链表只有一个结点时,反转后的链表出现断裂,返回的翻转之后的头节点不是原始链表的尾结点。
- 因此需要引入一个翻转后的头结点,以及一个指向当前结点的指针,一个指向当前结点前一个结点的指针,一个指向当前结点后一个结点的指针,防止出现断裂。
- 推广:递归实现反转链表
- 下面这句防止出现死循环现象
cur.next = None
代码:
# -*- coding:utf-8 -*-
class ListNode:
def __init__(self, x):
self.val = x
self.next = None
class Solution:
# 返回ListNode
def ReverseList(self, pHead):
pReversedHead = None
pNode = pHead
pPrev = None
while pNode != None:
pNext = pNode.next
if pNext == None:
pReversedHead = pNode
pNode.next = pPrev
pPrev = pNode
pNode = pNext
return pReversedHead
# 递归实现反转链表
def ReverseListRec(self, pHead):
#遇到空节点或者只剩一个节点时,直接返回这个节点
if not pHead or not pHead.next:
return pHead
else:
# 递归地找到最后一个节点,进行反转
pReversedHead = self.ReverseList(pHead.next)
pHead.next.next = pHead
pHead.next = None
return pReversedHead
node1 = ListNode(10)
node2 = ListNode(11)
node3 = ListNode(13)
node1.next = node2
node2.next = node3
S = Solution()
p = S.ReverseList(node1)
print(p.next.val)
面试题17:合并两个排序的链表
题目:输入两个递增排序的链表,合并这两个链表并使新链表中的结点仍然是按照递增排序的。
思路梳理
- 从合并两个链表的头结点开始。链表1的头结点的值小于链表2的头结点的值,因此链表1的头结点将是合并后链表的头结点;
- 继续合并两个链表中剩余的结点,在两个链表中剩下的结点依然是排序的,因此合并这两个链表的步骤和前面的步骤是一样的。
- 是典型的递归,但是要考虑鲁棒性,即需要适应空链表的情况。
class Solution(object):
def mergeTwoLists(self, l1, l2):
"""
:type l1: ListNode
:type l2: ListNode
:rtype: ListNode
"""
# 有其中一个是空链表的情况
if not l1:
return l2
if not l2:
return l1
# 定义头指针
if l1.val < l2.val:
head = ListNode(l1.val)
head.next = self.mergeTwoLists(l1.next, l2)
else:
head = ListNode(l2.val)
head.next = self.mergeTwoLists(l1, l2.next)
return head
面试题18:树的子结构
题目:输入两棵二叉树A和B,判断B是不是A的子结构。
思路梳理
分成两步:
- 第一步在树A中找到和B的根结点的值一样的结点R;
- 第二步再判断树A中以R为根结点的子树是不是包含和树B一样的结构。(需要新定义一个子方法,来实现该功能)
- 如果当前根A.val == 根B.val,调用子方法判断当前根出发的两个子树是否相同;返回值保存在res;
- 如果res is FALSE,则递归地、分别判断树A的左、右子树是否包含树B。
# -*- coding:utf-8 -*-
class TreeNode:
def __init__(self, x):
self.val = x
self.left = None
self.right = None
class Solution:
def HasSubtree(self, pRoot1, pRoot2):
res = False
if pRoot1!= None and pRoot2!= None:
if pRoot1.val == pRoot2.val:
res = self.DoseTree1HasTree2(pRoot1, pRoot2)
if not res:
res = self.HasSubtree(pRoot1.left, pRoot2)
if not res:
res = self.HasSubtree(pRoot1.right, pRoot2)
return res
# 用于递归判断树的每个节点是否相同
# 需要注意的地方是: 前两个if语句不可以颠倒顺序
# 如果颠倒顺序, 会先判断pRoot1是否为None, 其实这个时候pRoot2的结点已经遍历完成确定相等了, 但是返回了False, 判断错误
def DoseTree1HasTree2(self, pRoot1, pRoot2):
# 已经遍历到了树B的叶子节点,返回 True
if pRoot2!= None:
return True
# 先到达了树A的叶子节点,所以返回 False
if pRoot1!= None:
return False
# 如果值不相等,返回False
if pRoot1.val != pRoot2.val:
return False
# 如果以上条件都不满足,则证明当前树节点值相同;递归调用本方法,判断左右节点值是否相同
return self.DoseTree1HasTree2(pRoot1.left, pRoot2.left) and self.DoseTree1HasTree2(pRoot1.right, pRoot2.right)
pRoot1 = TreeNode(8)
pRoot2 = TreeNode(8)
pRoot3 = TreeNode(7)
pRoot4 = TreeNode(9)
pRoot5 = TreeNode(2)
pRoot6 = TreeNode(4)
pRoot7 = TreeNode(7)
pRoot1.left = pRoot2
pRoot1.right = pRoot3
pRoot2.left = pRoot4
pRoot2.right = pRoot5
pRoot5.left = pRoot6
pRoot5.right = pRoot7
pRoot8 = TreeNode(8)
pRoot9 = TreeNode(9)
pRoot10 = TreeNode(2)
pRoot8.left = pRoot9
pRoot8.right = pRoot10
S = Solution()
print(S.HasSubtree(pRoot1, pRoot8))
面试小提示:
二叉树相关的代码有大量的指针操作,每一次使用指针的时候,我们都要问自己这个指针有没有可能是NULL,如果是NULL该怎么处理。
3.5 本章小结
本章从规范性、完整性和鲁棒性3个方面介绍了如何在面试时写出高质量的代码。
- 缩进与对齐;
- 合理命名变量和函数;
- 考虑边界条件,做好错误处理;
- 在函数入口判断输入是否有效并对各种无效输入做好相应的处理。