学习使用工具
剑指Offer http://itmyhome.com/sword-means-offer/sword-means-offer.pdf
LeetCode的剑指Offer题库 https://leetcode.cn/problemset/all/
剑指 Offer 49. 丑数
我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
示例:
输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。
**说明: **
-
1
是丑数。 -
n
不超过1690。
解法:
1是丑数,剩下的丑数都可以由已知丑数乘2、3、5得到。
建立三个一模一样的列表,记录丑数序列。同时设置3个指针,分别遍历三个列表中的元素,被遍历过的数已经被用于做乘子来得到更大的丑数。而三个列表分别记录该数是否已经被乘过2、3或5。
设置计数变量i,每次循环寻找下一个最小的丑数,并令对应列表的指针后移一位。同时判定该数是否已经被加入列表,如果已经加入,则继续寻找;若没有,则将该数加入列表,并使i+1。当i已经计数到n时,结束循环。返回列表中的第n个数即可。
def nthUglyNumber(self, n: int) -> int:
ans2 = [1]
ans3 = [1]
ans5 = [1]
poi2 = 0
poi3 = 0
poi5 = 0
i = 0
while i < n:
temp = min(ans2[poi2]*2, ans3[poi3]*3, ans5[poi5]*5)
if temp == ans2[poi2]*2:
poi2 += 1
elif temp == ans3[poi3]*3:
poi3 += 1
else:
poi5 += 1
if not temp in [ans2[-1], ans3[-1], ans5[-1]]:
ans2.append(temp)
ans3.append(temp)
ans5.append(temp)
i += 1
return ans2[n - 1]
剑指 Offer 50. 第一个只出现一次的字符
在字符串 s 中找出第一个只出现一次的字符。如果没有,返回一个单空格。 s 只包含小写字母。
示例 1:
输入:s = "abaccdeff"
输出:'b'
示例 2:
输入:s = ""
输出:' '
限制:
0 <= s 的长度 <= 50000
解法:
开辟flag数组进行记录。flag[i]
记录26个小写字母是否出现过。遍历原始字符串,检查每个字符,如果该字符未出现过,则将对应flag置为1;如果已出现过,从字符串中删去所有该字符。遍历结束后,返回结果字符串中的第一个字符。
def firstUniqChar(self, s: str) -> str:
flag = [0] * 26
ans = s
for c in s:
if flag[ord(c) - ord('a')] == 0:
flag[ord(c) - ord('a')] = 1
else:
ans = ans.replace(c, '')
if ans:
return ans[0]
else:
return ' '
剑指 Offer 51. 数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5
限制:
0 <= 数组长度 <= 50000
解法:
进行一次归并排序。在合并阶段,记录逆序对总数即可。
def reversePairs(self, nums: List[int]) -> int:
temp = [0] * len(nums) # 辅助数组,用于归并阶段
def merge(l: int, r: int): # 归并排序,返回值为此数组所含的逆序对总数
if l >= r: # 如果l>=r,说明此时子数组的长度已经<=1,返回0
return 0
m = (l + r) // 2 # 划分
res = merge(l, m) + merge(m + 1, r) # res等于左右两个数组各自划分后得到的逆序对总数
i = l # 左指针遍历左数组
j = m + 1 # 右指针遍历右数组
temp[l: r + 1] = nums[l: r + 1] # temp保存原始数组的值便于归并
for k in range(l, r + 1): # 归并阶段,k表示当前的排序位置,每次将对应的元素置于nums[k]
if i == m + 1: # 如果左指针已经遍历完毕,则将右指针数字加入排序位置,右指针后移
nums[k] = temp[j]
j += 1
elif j == r + 1: # 如果右指针已经遍历完毕,则将左指针数字加入排序位置,左指针后移
nums[k] = temp[i]
i += 1
elif temp[i] <= temp[j]:# 如果左指针小于右指针,则将左指针数字加入排序位置,左指针后移
nums[k] = temp[i]
i += 1
else:
# 如果左指针大于右指针,则将右指针数字加入排序位置,右指针后移。
# 此时产生数量为(m-i+1)的逆序对(即左数组剩下的元素数量),加入res
nums[k] = temp[j]
j += 1
res += m - i + 1
return res
return merge(0, len(nums) - 1)
剑指 Offer 52. 两个链表的第一个公共节点
输入两个链表,找出它们的第一个公共节点。
如下面的两个链表:
在节点 c1 开始相交。
示例 1:
输入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
输出:Reference of the node with value = 8
输入解释:相交节点的值为 8 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [4,1,8,4,5],链表 B 为 [5,0,1,8,4,5]。在 A 中,相交节点前有 2 个节点;在 B 中,相交节点前有 3 个节点。
示例 2:
输入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
输出:Reference of the node with value = 2
输入解释:相交节点的值为 2 (注意,如果两个列表相交则不能为 0)。从各自的表头开始算起,链表 A 为 [0,9,1,2,4],链表 B 为 [3,2,4]。在 A 中,相交节点前有 3 个节点;在 B 中,相交节点前有 1 个节点。
示例 3:
输入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
输出:null
输入解释:从各自的表头开始算起,链表 A 为 [2,6,4],链表 B 为 [1,5]。由于这两个链表不相交,所以 intersectVal 必须为 0,而 skipA 和 skipB 可以是任意值。
解释:这两个链表不相交,因此返回 null。
注意:
- 如果两个链表没有交点,返回
null
. - 在返回结果后,两个链表仍须保持原有的结构。
- 可假定整个链表结构中没有循环。
- 程序尽量满足 O(n) 时间复杂度,且仅用 O(1) 内存。
解法:
自己写的稍微有点笨蛋的解法,先获取两个链表的长度,然后截去长链表的开头部分让两个链表一样长,之后再遍历就行了。
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
if not headA or not headB:
return None
def getlen(head: ListNode):
count = 0
while head:
count += 1
head = head.next
return count
lenA = getlen(headA)
lenB = getlen(headB)
if lenA >= lenB:
A = headA
for i in range(lenA - lenB):
A = A.next
B = headB
else:
A = headB
for i in range(lenB - lenA):
A = A.next
B = headA
while A:
if A == B:
return A
A = A.next
B = B.next
return A
翻答案发现有更好的双指针相遇解法,截取一下K神的解说:
考虑构建两个节点指针 A , B 分别指向两链表头节点 headA , headB ,做如下操作:
指针 A 先遍历完链表 headA ,再开始遍历链表 headB ,当走到 node 时,共走步数为:a+(b−c)
指针 B 先遍历完链表 headB ,再开始遍历链表 headA ,当走到 node 时,共走步数为:b+(a−c)
如下式所示,此时指针 A , B 重合,并有两种情况:
a+(b−c)=b+(a−c)
若两链表 有 公共尾部 (即c>0) :指针 A , B 同时指向「第一个公共节点」node 。
若两链表 无 公共尾部 (即c=0) :指针 A , B 同时指向 null。
因此返回 A 即可。
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
A, B = headA, headB
while A != B:
A = A.next if A else headB
B = B.next if B else headA
return A