3 高质量的代码 3.4 代码的鲁棒性

参考:

  1. 所有offer题目的LeetCode链接及python实现
  2. github Target offer

3.4 代码的鲁棒性

面试题15:链表中倒数第k个结点

题目:输入一个链表,输出该链表中倒数第k个结点。

思路梳理

  1. 遍历链表两次
  2. 需要注意特殊情况:
    • 如果输入的链表为空;
    • 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的子结构。

思路梳理

分成两步:

  1. 第一步在树A中找到和B的根结点的值一样的结点R;
  2. 第二步再判断树A中以R为根结点的子树是不是包含和树B一样的结构。(需要新定义一个子方法,来实现该功能)
    1. 如果当前根A.val == 根B.val,调用子方法判断当前根出发的两个子树是否相同;返回值保存在res;
    2. 如果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个方面介绍了如何在面试时写出高质量的代码。

  1. 缩进与对齐;
  2. 合理命名变量和函数;
  3. 考虑边界条件,做好错误处理;
  4. 在函数入口判断输入是否有效并对各种无效输入做好相应的处理。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值