剑指offer在线编程(08-10)【6】

Date: 2019-08-10

1.  两个链表的第一个公共结点 (考察知识点:链表)

题目描述

输入两个链表,找出它们的第一个公共结点。

分析思路:  刚开始的想法是暴力解决,每一个节点和另一个链表的每一个节点进行比较,这样算法的时间复杂度是O(length1*length2)。看到牛客上别人的解释,发现:第一个公共节点(节点的val和next相同)之后的所有节点应该是一样的,即是有相同的尾部。则因此我们可以让长度比较长的链表先走(next)他们的长度差,然后基于相同长度的链表进行一对一的比较 !

代码如下:(写代码一定要记得分析题意,举例两个链表进行分析)

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def FindFirstCommonNode(self, pHead1, pHead2):
        # write code here
        def Getlength(head):  # 先定义计算链表的长度的子函数
            length = 0
            while head != None:
                length += 1
                head = head.next
            return length 
        def Gostep(node, k):   # 定义让长的链表先走k步的子函数
            while k != 0:
                node = node.next
                k -= 1
            return node
        
        length1 = Getlength(pHead1)  # 计算两个链表的长度、计算两者的长度差
        length2 = Getlength(pHead2)
        lengthdiff = abs(length1 - length2)
        if length1 >= length2:  # 如果链表1更长,则链表1先走
            Headlong = Gostep(pHead1, lengthdiff)
            Headshort = pHead2
        else:                  # 如果链表2更长,则链表2先走
            Headlong = Gostep(pHead2, lengthdiff)
            Headshort = pHead1
        while Headlong != None and Headshort != None and Headlong != Headshort:  # 一对一比较,不相等时则继续向next进行遍历比较,直到相等时停止。
            Headlong = Headlong.next
            Headshort = Headshort.next
        Commonnode = Headlong
        return Commonnode

2.  数字在排序数组中出现的次数 (考察知识: 数组和二分查找)

题目描述

统计一个数字在排序数组中出现的次数。

分析: 刚开始看到题目到的初始想法就是直接data.count(k),代码如下(通过了的)。但是转念一想,这不是本题的考察角度,应该考察的是利用二分查找来找到k的第一个index和最后一个index,然后相减+1便是我们要求的次数。

# -*- coding:utf-8 -*-
class Solution:
    def GetNumberOfK(self, data, k):
        # write code here
        return data.count(k)
# -*- coding:utf-8 -*-(通过的代码)
class Solution:
    def calfirstindex(self, data, k):
        low = 0
        high = len(data)-1
        while low <= high:
            mid = (low + high)//2  # 如果low < high ,则进行求中间的index
            if data[mid] > k:  # 中间index的元素大于k,则左移,减1
                high = mid -1
            elif data[mid] <k:  # 中间index的元素小于,则右移,加1
                low = mid + 1
            else: # 否则如果两者相等
                if mid == low or data[mid-1] != k:  # 且mid等于low或者mid-1的元素并不等于k,则就是第一个元素为k的index 
                    return mid
                else:  # 否则high进行mid-1的移动
                    high = mid -1
        return -1
    def callastindex(self, data, k):
        low = 0
        high = len(data)-1
        while low <= high:
            mid = (low + high)//2  # 如果low < high ,则进行求中间的index
            if data[mid] > k:  # 中间index的元素大于k,则左移,减1
                high = mid -1
            elif data[mid] <k:  # 中间index的元素小于,则右移,加1
                low = mid + 1
            else: # 否则如果两者相等(mid的元素和k相等)
                if mid == high or data[mid+1] != k:  # 且mid等于high或者mid+1的元素并不等于k,则就是最后一个元素为k的index 
                    return mid
                else:  # 否则low进行mid+1的移动
                    low = mid +1
        return -1
    
    def GetNumberOfK(self, data, k):
        # write code here
        if not data:
            return 0
        if self.callastindex(data,k) == -1 and self.calfirstindex(data,k) == -1:
            return 0
        return self.callastindex(data,k) - self.calfirstindex(data,k) +1

3.  二叉树的深度   (考察知识点:树+递归)

题目描述

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

分析:该题算是中等偏下难度的题目,主要是考察利用递归的思想对树进行处理。第一种方法是:建立子函数计算深度:首先判断根节点的左右子树是否存在,

如果不存在,则只返回1;

如果左子树不存在,则只有右子树,因此往右遍历计算深度;

如果右子树不存在,则只有左子树,因此往左遍历计算深度,

否则递归计算左右子树的深度,然后求max +1.

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def TreeDepth(self, pRoot):
        if not pRoot:
            return 0
        return self.getpath(pRoot)
    def getpath(self, root):
        if not root.left and not root.right:
            return 1
        elif not root.left:
            return self.getpath(root.right)+1
        elif not root.right:
            return self.getpath(root.left)+1
        else:
            return max(self.getpath(root.left), self.getpath(root.right))+1

第二种方法看上去更为简洁:

直接递归计算左右子树的深度,然后比较即可

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def TreeDepth(self, pRoot):
        if not pRoot:
            return 0
        else:
            lefts = self.TreeDepth(pRoot.left)+1
            rights = self.TreeDepth(pRoot.right)+1
            return  max(lefts, rights)

4.  平衡二叉树  (考察知识点: 树+递归调用)

题目描述

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

分析:自己初始写了一个版本,虽然通过了,但是感觉有问题。

首先平衡二叉树的判断条件: 

平衡二叉树(Balanced Binary Tree),具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。并没有要求左子树小于根节点, 右子树大于根节点。

自己版本的思路:首先建立一个子函数计算深度,如果树为空,则为平衡树,否则计算左右子树的深度,判断是否满足平衡二叉树的条件!

问题在于:1)没有递归判断左右子树是否也是平衡二叉树,只是计算了根节点的左右子树深度是否满足条件(但是也通过);

                  2) 即是第二版本的答案考虑全面了,但是其是从上到下的一种解法,有很多重复计算的过程!

                  3)第三个版本采用自下而上的思想,避免重复,如果叶节点往上的左/右子树不平衡,则整棵树就不平衡了。注意的是: 子树不平衡时,返回-1.

# 版本一的解法,虽然通过了,但是自我感觉没有考虑全面。
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def caldepth(self, root):
        if not root:
            return 0
        else:
            lefts = self.caldepth(root.left)+1
            rights = self.caldepth(root.right)+1
            return max(lefts, rights)
        
    def IsBalanced_Solution(self, pRoot):
        # write code here
        if not pRoot:
            return  True
        else:
            leftdepth = self.caldepth(pRoot.left)
            rightdepth = self.caldepth(pRoot.right)
            diff = abs(leftdepth-rightdepth)
            if diff <=1:
                return True
            else:
                return False

方法一:自顶向下,对于每个节点,都计算一下左子树以及右子树的差的绝对值,即每个节点都判断一下。算法复杂度为O(N*2)

# -*- coding:utf-8 -*- 自上而下的全面版本,但是有较多重复计算
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def caldepth(self, root):
        if not root:
            return 0
        else:
            lefts = self.caldepth(root.left)+1
            rights = self.caldepth(root.right)+1
            return max(lefts, rights)
        
    def IsBalanced_Solution(self, pRoot):
        # write code here
        if not pRoot:
            return  True
        else:
            leftdepth = self.caldepth(pRoot.left)
            rightdepth = self.caldepth(pRoot.right)
            diff = abs(leftdepth-rightdepth)
            return diff <=1 and self.IsBalanced_Solution(pRoot.left) and self.IsBalanced_Solution(pRoot.right)

方法二:自下往上  算法复杂度O(N) 注意:子树不平衡时返回-1

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def IsBalanced_Solution(self, p):
        return self.dfs(p) != -1
    def dfs(self, p):
        if p is None:
            return 0
        left = self.dfs(p.left)
        if left == -1:
            return -1
        right = self.dfs(p.right)
        if right == -1:
            return -1
        if abs(left - right) > 1:
            return -1
        return max(left, right) + 1

5.  数组中只出现一次的数字  (考察知识点:数组+逻辑)

题目描述

一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

分析:该题应该有很多方法,首先看到该题想到的是利用count内置函数进行频次计算。遍历set(array),计算array中每个互异元素的频次,如果频次等于1,则放入res列表中,并且因为题目中谈到了只有两个仅仅出现了一次,所以如果len(res)==2时,可以直接返回。  但是:没有充分利用其他数字都出现了两次这一条件,所以下一版本不用count函数,且利用前面这个条件。

# -*- coding:utf-8 -*- 版本1:时间复杂度低一点,但是利用了内置函数count()
class Solution:
    # 返回[a,b] 其中ab是出现一次的两个数字
    def FindNumsAppearOnce(self, array):
        # write code here
        if not array:
            return False
        res = []
        for k in set(array):
            if array.count(k) == 1:
                res.append(k)
                if len(res) == 2:
                    return res

充分考虑条件:遍历array中的每一个元素,第一次遇到k元素时,将其放入res中,第二次遇到时,将其从res中移除,而只出现一次的元素会一直留在res中。(只出现一次的数字有两个,而其他数字都只出现2次)

# -*- coding:utf-8 -*-
class Solution:
    # 返回[a,b] 其中ab是出现一次的两个数字
    def FindNumsAppearOnce(self, array):
        # write code here
        if not array:
            return False
        res = []
        for k in array:
            if k in res:
                res.remove(k)
            else:
                res.append(k)
        return res

看到牛客上还有人说利用异或的思想,即一个相同的元素出现两次,则第一个k的异或和k会相互抵消,但对于只出现一次的元素而言,无法进行抵消!

6.  和为S的连续正数序列  (考察:数组+穷举+数学)

题目描述

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck!

输出描述:

输出所有和为S的连续正数序列。序列内按照从小至大的顺序,序列间按照开始数字从小到大的顺序

方法一(双指针):

用small和big分别表示序列的左值和右值,首先将small初始化为1,big初始化为2;


若[small, big]之和 > S,从序列中去掉第一个值(增大small);
若和 < S,增大big,和中加入新的big;
若等于S,将[small, big] 纳入到结果集中;

# -*- coding:utf-8 -*-  双指针的思想时间复杂度更低一些
class Solution:
    def FindContinuousSequence(self, tsum):
        if tsum <3:
            return []
        small = 1  # 初始指针small为1
        big = 2    #初始指针big为1
        Cursum = small + big  #累计子序列的和
        middle = (tsum + 1) // 2  # 计算S的二等分值,作为while依据
        output = []
        while small < middle:
            if Cursum == tsum:  # 如果子序列和等于s,则直接返回子序列,注意big+1,但是只取到了big
                output.append(range(small, big+1))
                big += 1
                Cursum += big
            elif Cursum > tsum:  # 当前子序列和大于S,则舍弃到最小值
                Cursum -= small
                small += 1
            elif Cursum < tsum:  # 当前子序列小于S,增加最大值big
                big += 1
                Cursum += big
        return output

方法二:采用等差数列的思想:

根据所学知识,有:

(a + b)(b - a + 1) = 2 * sum;此为等差数列公式。

令i = b - a + 1(项数), j = a + b(首末项之和);现讨论取值范围。i >= 2(序列中至少有2项), j >= 3(序列之和至少为3);隐藏的关系是: j > i同时还有 i * j = 2 * sum,进行放缩之后就有 i * i < 2 * sum,即 i < 根号(2 * sum)。对i进行遍历,找出i,j∈正整数且j - i + 1为偶的取值。
 

# -*- coding:utf-8 -*- 等差数列知识
class Solution:
    def FindContinuousSequence(self, tsum):
        # write code here
        res=[]
        for i in range(1,tsum/2+1):  # I是首项,j是尾项;首尾项之和*((j-i+1)/2) 为当前子序列的和
            for j in range(i,tsum/2+2):
                tmp=(j+i)*(j-i+1)/2
                if tmp>tsum:
                    break
                elif tmp==tsum:
                    res.append(range(i,j+1))
        return res

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值