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