leetcode-剑指offer-5
本系列博文为题库刷题笔记,(仅在督促自己刷题)如有不详之处,请参考leetcode官网:https://leetcode-cn.com/problemset/lcof/
45.面试题55- 二叉树的深度
输入一棵二叉树的根节点,求该树的深度。从根节点到叶节点依次经过的节点(含根、叶节点)形成树的一条路径,最长路径的长度为树的深度。
自顶向下递归:父亲节点给叶子节点提供信息,不断往下递归;到达每一个叶子节点之后,更新目前的最大深度。递归出口还是:if node==None: return ,框架是不变的。从上往下灵魂:底层出口。
class Solution(object):
def __init__(self):
self.deep=0
def maxDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""
def top_down(node,l):
if node==None:
return
if node.left==None and node.right==None: # 灵魂
self.deep=max(self.deep,l)
top_down(node.left,l+1)
top_down(node.right,l+1)
top_down(root,1)
return self.deep
自底向上:对于某一个节点,只要左右子树的深度知道了(l,r),该节点的深度为max(l,r)+1
不断从下往上,最终输出根节点的深度。从下往上的灵魂,最底层信息如何传递给上层,本质是后序遍历。
class Solution(object):
def maxDepth(self, root):
"""
:type root: TreeNode
:rtype: int
"""
def bottom_up(node):
if node==None:
return 0
l_deep=bottom_up(node.left)
r_deep=bottom_up(node.right)
return max(l_deep,r_deep)+1
return bottom_up(root)
46.面试题55-2-平衡二叉树
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
思路:要确认每个节点的左右子树的深度,然后比较两个的大小相差不超过1。自底向上的可解释性高。
1.当节点root 左 / 右子树的深度差 ≤1 :则返回当前子树的深度,即节点 2.root 的左 / 右子树的深度最大值 +1( max(left, right) + 1 );
当节点root 左 / 右子树的深度差 >=2 :则返回 −1 ,代表 此子树不是平衡树 。
class Solution(object):
def isBalanced(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
def bottom_up(node):
if node==None:
return 0
l_deep=bottom_up(node.left)
if l_deep==-1:
return -1
r_deep=bottom_up(node.right)
if r_deep==-1:
return -1
if abs(l_deep-r_deep)>1: # 返回值的类型应该要一致 如何将这两个统一在一起
return -1
else:
return max(l_deep,r_deep)+1
return bottom_up(root)!=-1
自顶向下:检查每个节点的是否满足平衡二叉树的要求,在递归遍历左右子树的情况。设置单独的函数求解二叉树节点的深度。–效率低,运行时间是自底向上的70倍
class Solution(object):
def isBalanced(self, root):
"""
:type root: TreeNode
:rtype: bool
"""
if root==None:
return True # 空节点默认为是符合平衡二叉树的要求的
flag1=abs(self.tree_deep_bu(root.left)-self.tree_deep_bu(root.right))
flag2=self.isBalanced(root.left)
flag3=self.isBalanced(root.right)
return flag1<2 and flag2 and flag3
def tree_deep_bu(self,node):
if node==None:
return 0
return max(self.tree_deep_bu(node.left),self.tree_deep_bu(node.right))+1
47.面试题57-1-和为s的两个数字-双指针
输入一个递增排序的数组和一个数字s,在数组中查找两个数,使得它们的和正好是s。如果有多对数字的和等于s,则输出任意一对即可。
双指针技巧:两个指针分别指向数组的头和尾,如果两数之和大于目标值,则移动右指针;两数之和小于目标值,则移动左指针。直至找到目标值。
正确性:所有和为上三角,移动i->i+1,会消除不符合要求的一行数字,移动j->j-1会消除不满足要求的一列数字。直至搜索到正确答案。
class Solution(object):
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
l,r=0,len(nums)-1
while(l<r):
if nums[l]+nums[r]==target:
return (nums[l],nums[r])
elif nums[l]+nums[r]<target:
l+=1
else:
r-=1
return (-1,-1)
48.面试题57-2-和为s 的连续正数序列-双指针
输入一个正整数 target ,输出所有和为 target 的连续正整数序列(至少含有两个数)。
序列内的数字由小到大排列,不同序列按照首个数字从小到大排列。
例如:
输入:target = 9
输出:[[2,3,4],[4,5]]
连续序列:双指针技巧,快慢指针分别指向序列的两个端点,利用求和公式求此时的和:如果和小于目标值,移动右指针;如果和大于目标值,更新起始左指针;和等于目标值,更新左指针。退出条件为l>r。
循环结束条件l<r,当l == r 说明r 后面没有两个数字的和可以比target小。
class Solution(object):
def findContinuousSequence(self, target):
"""
:type target: int
:rtype: List[List[int]]
"""
res=[]
l,r=1,2
while(l<r):
sum_val=(l+r)*(r-l+1)/2
#print(l,r,sum_val,target)
if sum_val==target:
res.append([])
for j in range(l,r+1):
res[-1].append(j)
l+=1
elif sum_val<target:
r+=1
else:
l+=1
return res
49.面试题56-数组中出现数字的次数-位运算
位运算系列题目:leetcode 260,136,137,645 异或操作解题思路
异或操作: 数字a与数字b的异或操作为两个数字的二进制上每一位进行对位异或操作,如果同一位置上两个数字相同,异或结果为0,否则为1.
异或规律:
1.任何数字和本身异或为0,
2.任何数字和0异或为本身,
3.异或满足交换律
异或草用于检测出现1,3,5次的数字。
leetcode-136 只出现一次的数字I
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素
解题思路:异或操作的三条性质保证使用对所有数字进行异或操作可以找到不同的那个数字,因为异或满足交换律,所以可以看成将所有的相同数字进行异或得到0再与不同数字进行异或操作,得到不同的那个数字。
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
single_num=0
for val in nums:
single_num^=val
return single_num
leetcode-137 只出现一次的数字II
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
解题思路1: 位运算
为了区分出现一次的数字和出现三次的数字,使用两个位掩码:seen_once 和 seen_twice。两个掩码的变换规则为:(不是很懂)
仅seen_twice 为0时,改变seen_once
仅seen_once 为0时,改变seen_twice
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
seen_once,seen_twice=0,0
for num in nums:
seen_once=~seen_twice&(seen_once^num)
seen_twice=~seen_once&(seen_twice^num)
return seen_once
解题思路2:数学计算
[a,b,b,b]->set 操作:[a,b]-> 求和:a+b-> 乘三:3(a+b)-> 减原数组的和3(a+b)-(a+3b)-> 除以2即可得到a
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
temp=set(nums)
return (sum(temp)*3-sum(nums))//2
leetcode-260 只出现一次的数字III
一个整型数组 nums 里除两个数字(a,b表示)之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
思路:时间复杂度是o(n), 要求一次遍历。空间复杂度是o(1) 只能有常数个空间限制。
分组异或:将只出现了一次的两个数字分到两组,每组中其他元素为成对出现的相同数字。
核心是如何分组:全部数字异或的结果将是ab两个数字异或的结果。异或结果中某一位为1,主要是由于a,b在该位置二进制表示不同导致的。所以可以依据该位置将数字分为两组,其分组满足上面的要求,因为相同的数字在该位肯定要么同时为0,要么同时为1.
异或结果如何取到为1的那一位呢:不为零的最低位
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
bitmask=0
for num in nums:
bitmask=bitmask^num # 保存两个出现一次数字x,y的异或结果
diff=bitmask&(-bitmask) # 异或结果中最每个位置上的1要么来自x 要么来自y,取最右边的1.
x=0
for num in nums:
if num & diff: # 将最右边有1的所有数字拿出来,再做一次异或,就能找到x
x=x^num
return (x,x^bitmask)
50.面试题51-数组中的逆序对-归并排序
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
思路:归并排序解逆序数。在归并的过程中,计算逆序对的数量。
归并是分治思想的典型应用,不断二分到单个元素,然后两两合并。在求逆序数的情况下就是两个两个求逆序数,然后四个四个求逆序数,把所有的逆序数相加即可。
核心在两个需要合并(merge)的序列中逆序数如何求?如下:
在左指针lPtr 发生右移动的时候,当前 lPtr 指向的数字比 rPtr 小,但是比 R 中 [0 … rPtr - 1] 的其他数字大,[0 … rPtr - 1] 的其他数字本应当排在 lPtr 对应数字的左边,但是它排在了右边,所以这里就贡献了 rPtr 个逆序对。
class Solution(object):
def reversePairs(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
def merge_sort(nums,l,r):
if l<r:
mid=(l+r)//2
count=merge_sort(nums,l,mid)+merge_sort(nums,mid+1,r)
#print("a",count)
i,j,tmp=l,mid+1,[]
while(i<=mid and j<=r):
if nums[j]<nums[i]:
tmp.append(nums[j])
count+=mid-i+1
j+=1
else:
tmp.append(nums[i])
i+=1
while(i<=mid):
tmp.append(nums[i])
i+=1
while(j<=r):
tmp.append(nums[j])
j+=1
nums[l:r+1]=tmp
#print(nums,count)
return count
else:
return 0
res=merge_sort(nums,0,len(nums)-1)
return res
class Solution(object):
def reversePairs(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
def cout_rev(l,r):
if l < r:
mid = (l + r) // 2
count = cout_rev(l,mid) + cout_rev(mid+1, r)
i, j, tmp = l, mid+1, []
while(i<=mid or j <= r):
if i > mid or (j <= r and nums[i] > nums[j]):
tmp.append(nums[j])
j += 1
else:
count += j - (mid + 1)
tmp.append(nums[i])
i += 1
nums[l:r+1] = tmp[:]
return count
else:
return 0
res = cout_rev(0, len(nums)-1)
return res
51.面试题54-二叉搜索树的第k大节点-中序遍历
给定一棵二叉搜索树,请找出其中第k大的节点。
思路:二搜索树的中序遍历序列为升序,中序遍历res返回res[-k] 即可。
class Solution(object):
def kthLargest(self, root, k):
"""
:type root: TreeNode
:type k: int
:rtype: int
"""
def in_order_dfs(node):
if node==None:
return
in_order_dfs(node.left)
res.append(node.val)
in_order_dfs(node.right)
res=[]
in_order_dfs(root)
return res[-k]
52.面试题58-翻转单词的顺序-库函数
调用内置函数:
class Solution(object):
def reverseWords(self, s):
"""
:type s: str
:rtype: str
"""
s_lis=s.split() # 将字符串按空格划分成列表,多个空格看作一个
s_lis.reverse() # 将列表逆序
res=" ".join(s_lis) # 将链表中字符串按空格链接
return res
53.面试题58-左旋转字符串
字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。
左移没有顺序变换问题。
思路1:每次左移动一位, 移动k次
class Solution(object):
def reverseLeftWords(self, s, n):
"""
:type s: str
:type n: int
:rtype: str
"""
l=len(s)
n=n%l
for i in range(n):
s=s[1:]+s[0]
return s
思路2:拼接s[n:]与s[:n]
class Solution(object):
def reverseLeftWords(self, s, n):
"""
:type s: str
:type n: int
:rtype: str
"""
l=len(s)
n=n%l
s_r=s[n:]
s_l=s[:n]
return s_r+s_l
54.面试题59-滑动窗口的最大值-左右dp
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
leetcode 239动态规划法-那题标为困难,这里标为简单,神奇。
左右两个dp 数组,比较得到答案。
n=len(nums)
if n*k==0:
return []
if k==1:
return nums
left=[nums[0]]*n
right=[nums[-1]]*n
for i in range(1,n): # i:[0,n-1]
if i%k==0:
left[i]=nums[i]
else:
left[i]=max(left[i-1],nums[i])
ind=n-i-1
if ind%k==0:
right[ind]=nums[ind]
else:
right[ind]=max(right[ind+1],nums[ind])
res=[]
for i in range(n-k+1):
res.append(max(left[i+k-1],right[i]))
return res
55.面试题60-n个骰子的点数-dp
把n个骰子扔在地上,所有骰子朝上一面的点数之和为s。输入n,打印出s的所有可能的值出现的概率。
你需要用一个浮点数数组返回答案,其中第 i 个元素代表这 n 个骰子所能掷出的点数集合中第 i 小的那个的概率。
class Solution(object):
def twoSum(self, n):
"""
:type n: int
:rtype: List[float]
"""
dp=[[0]*(6*11+1) for _ in range(n)]
for j in range(1,7):
dp[0][j]=1 # 一个骰子,价值为j 的个数
for i in range(1,n): # i+1个骰子
for j in range(i+1,6*(i+1)+1): # i+1个骰子点数和的范围 [i+1,6(i+1)]
for k in range(max(j-6,1),j): # dp[i][j]=sum_{i=1}^{j-1}dp[i-1][j-i]
#print(i,j,k)
dp[i][j]+=dp[i-1][k]
res=[]
#print(dp)
for j in range(6*11+1):
if dp[n-1][j]!=0:
res.append(dp[n-1][j]/ float(6**n))
return res
56.面试题61-扑克牌中的顺子
从扑克牌中随机抽5张牌,判断是不是一个顺子,即这5张牌是不是连续的。2~10为数字本身,A为1,J为11,Q为12,K为13,而大、小王为 0 ,可以看成任意数字。A 不能视为 14。
输入:数组长度为 5 ,数组的数取值为 [0, 13] 。
思路:判断所给的5个数字是否能构成一个顺子
身为大小王的0可以为任何数字,将数组排序后,检查缺失数字的个数,如果缺失数字的个数少于0的个数,则可以构成顺子。
class Solution(object):
def isStraight(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
nums.sort()
num_0=0
i,index_z=0,0
while(i<5):
if nums[index_z]==0:
num_0+=1
index_z+=1
i+=1
gap=0
for i in range(index_z,4):
if nums[i+1]-nums[i]==1:
continue
elif nums[i+1]-nums[i]==0:
return False
else:
gap+=nums[i+1]-nums[i]-1
print(num_0,gap,nums)
if gap<=num_0: # gap 可以比0的个数少,0可以无中生有
return True # [0,0,0,9,11]->[9,10,11,..]
else:
return False
class Solution(object):
def isStraight(self, nums):
"""
:type nums: List[int]
:rtype: bool
"""
nums.sort()
res = [nums[-1]] * 5
l, r, res_Index = 0, 3, 3
# l 指向nums左边待处理元素
# l 指向nums右边待处理元素
# res_index 当前这个元素应该为多少
while(l <= r):
if nums[r] + 1 == res[res_Index+1]:
res[res_Index] = nums[r]
r -=1
res_Index-=1
else:
if nums[l] == 0:
res[res_Index] = res[res_Index+1] -1
l += 1
res_Index -= 1
else:
return False
return True
57.面试题62-圆圈中最后剩下的数字–约瑟夫环问题
0,1,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
例如,0、1、2、3、4这5个数字组成一个圆圈,从数字0开始每次删除第3个数字,则删除的前4个数字依次是2、0、4、1,因此最后剩下的数字是3。
参考博文:递归https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/solution/huan-ge-jiao-du-ju-li-jie-jue-yue-se-fu-huan-by-as/
核心是如何从n-1恢复到n的编号,f = (f+m)%n
class Solution(object):
def lastRemaining(self, n, m):
"""
:type n: int
:type m: int
:rtype: int
"""
pre_pos=0
for i in range(1,n+1):
pos=(pre_pos+m)%i # 加m 是在右移动
pre_pos=pos
return pos
58.面试题63-股票的最大利润-最大谷峰值
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
注意:你不能在买入股票前卖出股票。
解题思路:一次买卖,找最大的峰谷值
class Solution(object):
def maxProfit(self, prices):
"""
:type prices: List[int]
:rtype: int
"""
if len(prices)==0:
return 0
min_price=prices[0]
max_pro=0
for val in prices[1:]:
max_pro=max(max_pro,val-min_price)
min_price=min(min_price,val)
return max_pro