第01天 整数
剑指 Offer II 001. 整数除法
class Solution:
# 时间复杂度:O(logn), 空间复杂度:O(1)
def divideCore(self, dividend, divisor):
result = 0
while dividend <= divisor: # 因为此时被除数和除数都成了负数
value = divisor
quotient = 1
while (value >= -2**30 and dividend <= value + value):
quotient += quotient
value += value
result += quotient
dividend -= value
return result
def divide(self, dividend: int, divisor: int) -> int:
if dividend == -2**31 and divisor == -1:
return 2**31 - 1 # 因为负数转化为正数会有溢出
negative = 2
if dividend > 0:
negative -= 1
dividend = -dividend
if divisor > 0:
negative -= 1
divisor = -divisor
result = self.divideCore(dividend, divisor)
if negative == 1:
return -result
else:
return result
剑指 Offer II 002. 二进制加法
class Solution:
def addBinary(self, a: str, b: str) -> str:
result = ""
i = len(a) - 1
j = len(b) - 1
carry = 0
while i >= 0 or j >= 0:
digitA = int(a[i]) if i >= 0 else 0 # 使用id(i)可以看出i自增前后的在内存中的位置是变了的
i -= 1
digitB = int(b[j]) if j >= 0 else 0
j -= 1
sum = digitA + digitB + carry
carry = 1 if sum >= 2 else 0
sum = sum - 2 if sum >= 2 else sum
result += str(sum)
result += '1' if carry == 1 else ''
return result[::-1]
剑指 Offer II 003. 前 n 个数字二进制中 1 的个数
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def countBits(self, n: int) -> List[int]:
result = [0] * (n + 1) # 利用好i/2计算1中的个数,
for i in range(1, n + 1):
result[i] = result[i >> 1] + (1 & i) # i&(i-1)中1的个数仅比i中1的个数少1个
return result
第02天 整数
剑指 Offer II 004. 只出现一次的数字
class Solution:
# 时间复杂度:O(n) 空间复杂度:O(1)
def singleNumber(self, nums: List[int]) -> int:
counts = [0] * 32
for num in nums:
for i in range(32):
counts[i] += (num >> (31 - i)) & 1 # 表示从左边起num中的第i个位数中是否是1 # python中特殊的是对于负数,右移动补0
res = 0
for i in range(32):
res = (res << 1) + counts[i] % 3
# 负数,原码y 反码-y-1 补码-y 移码是补码的
# python中的按位操作和计算机底层的操作是一样的,比如正数和负数都用补码保存,只不过正数的补码是自身,负数的补码是原码取反再加1
# 而python中的函数却是经过处理的,便于我们自身理解的,这也是为什么称python是高级语言
# res ^ 0xffffffff 表示对res的按位取反(截取出32位,把符号位的1全部反过来,但是数据位的也反了) 也就是此时是补码的反码(叫移码)
# ~ res (此时再取反一次就行了叫,就表示真正的补码)
return res if counts[0] % 3 == 0 else ~(res ^ 0xffffffff) # 最高位如果是1,证明是负数,此时保存的是补码。最高位如果是0,证明是正数,此时保存的是原码。因为Java中的int是32位
剑指 Offer II 005. 单词长度的最大乘积
class Solution:
# 时间复杂度:O(nk + n^2) 空间复杂度:O(n),
# 当判断两个字符串是否包含相同字符时,该方法只需要一次位运算,而哈希表方法可能需要26次布尔运算
def maxProduct(self, words: List[str]) -> int:
n = len(words)
flags = [0] * n
for i in range(n):
for c in words[i]:
flags[i] |= 1 << (ord(c) - ord('a')) # 将字符对应的位数标记为1,或运算
res = 0
for i in range(n):
for j in range(i+1, n):
if flags[i] & flags[j] == 0: # 位运算判断是否有相同字符
prod = len(words[i]) * len(words[j])
res = max(res, prod)
return res
剑指 Offer II 006. 排序数组中两个数字之和
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def twoSum(self, numbers: List[int], target: int) -> List[int]:
i, j = 0, len(numbers) - 1
while i < j and numbers[i] + numbers[j] != target:
if numbers[i] + numbers[j] < target:
i += 1
else:
j -= 1
return [i, j]
第03天 数组
剑指 Offer II 007. 数组中和为 0 的三个数
class Solution:
# 时间复杂度:O(nlogn + n^2), 排序+找 空间复杂度:O(1)
def twosum(self, nums, i, res):
j = i + 1
k = len(nums) - 1
while j < k:
if nums[i] + nums[j] + nums[k] == 0:
res.append([nums[i], nums[j], nums[k]])
temp = nums[j]
while (nums[j] == temp and j < k): # twosum中滤掉重复的数,只需要考虑较小的数就行了
j += 1
elif nums[i] + nums[j] + nums[k] < 0:
j += 1
else:
k -= 1
def threeSum(self, nums: List[int]) -> List[List[int]]:
res = []
if len(nums) >= 3:
nums.sort() # 先排序,变成有序的
i = 0
while i < len(nums) - 2:
self.twosum(nums, i, res)
temp = nums[i]
i += 1
while nums[i] == temp and i < len(nums) - 1: # 滤掉重复的数
i += 1
return res
剑指 Offer II 008. 和大于等于 target 的最短子数组
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)。 感觉是一种贪心算法的思想。
#如果大于target,就左移P1(因为都是正数,所以相当于减少数字,减少和),如果大于target,就右移P2(相当于增加数字,增加和)
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
left, sum = 0, 0
res = float('inf')
for right in range(len(nums)):
sum += nums[right]
while left <= right and sum >= target:
res = min(res, right - left + 1)
sum -= nums[left]
left += 1
return res if res != float('inf') else 0
剑指 Offer II 009. 乘积小于 K 的子数组
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
product = 1
left, res = 0, 0
for right in range(len(nums)):
product *= nums[right]
while left <= right and product >= k:
product /= nums[left]
left += 1
res += right - left + 1 if right >= left else 0
return res
第04天 数组
剑指 Offer II 010. 和为 k 的子数组
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def subarraySum(self, nums: List[int], k: int) -> int:
sumToCount = dict()
sumToCount.setdefault(0, 1)
sum, count = 0, 0
for num in nums:
sum += num
count += sumToCount.get(sum - k, 0)
sumToCount[sum] = sumToCount.get(sum, 0) + 1
return count
剑指 Offer II 011. 0 和 1 个数相同的子数组
哈希表,累加数组数字求子数组之和
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(n)
def findMaxLength(self, nums: List[int]) -> int:
sumToIndex = {
0:-1}
sum, maxLength = 0, 0
for i in range(len(nums)):
sum += -1 if nums[i] == 0 else 1
if sum in sumToIndex.keys():
maxLength = max(maxLength, i - sumToIndex.get(sum))
else: # 只有不存在时才添加,这样可以保证保留的是第一个累积到当前扫描的数字之和
sumToIndex[sum] = i
return maxLength
剑指 Offer II 012. 左右两边子数组的和相等
累加数组数字求数字之和
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def pivotIndex(self, nums: List[int]) -> int:
total = 0
for i in range(len(nums)):
total += nums[i]
sum = 0
for i in range(len(nums)):
sum += nums[i]
if sum - nums[i] == total - sum: # 当前数字中左边数组之和 是否等于 右边数组之和
return i
return -1
剑指 Offer II 013. 二维子矩阵的和
累加数组数字求子数组之和
class NumMatrix:
# 时间复杂度:O(mn), 空间复杂度:O(mn)
def __init__(self, matrix: List[List[int]]):
if len(matrix) == 0 or len(matrix[0]) == 0:
return 0
self.sums = [[0] * (len(matrix[0]) + 1) for _ in range(len(matrix)+1)] # 都一个是为了防止求得本身是左上角的矩阵,以防出具ai出具ai
for i in range(len(matrix)):
rowSum = 0
for j in range(len(matrix[0])):
rowSum += matrix[i][j]
self.sums[i + 1][j + 1] = self.sums[i][j + 1] + rowSum
# 时间复杂度:O(1)
def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
return self.sums[row2 + 1][col2 + 1] - self.sums[row1][col2 + 1] - self.sums[row2 + 1][col1] + self.sums[row1][col1]
第05天 字符串
剑指 Offer II 014. 字符串中的变位词
哈希表、双指针
class Solution:
# 时间复杂度:O(m + n), 空间复杂度:O(1)
def areAllZero(self,counts):
for count in counts:
if count != 0:
return False
return True
def checkInclusion(self, s1: str, s2: str) -> bool:
if len(s2) >= len(s1):
counts = [0] * 26
for i in range(len(s1)):
counts[ord(s1[i]) - ord('a')] += 1
counts[ord(s2[i]) - ord('a')] -= 1
if self.areAllZero(counts):
return True
for j in range(len(s1), len(s2)):
counts[ord(s2[j]) - ord('a')] -= 1
counts[ord(s2[j - len(s1)]) - ord('a')] += 1
if self.areAllZero(counts):
return True
return False
剑指 Offer II 015. 字符串中的所有变位词
哈希表、双指针
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def areAllZero(self, counts):
for count in counts:
if count != 0:
return False
return True
def findAnagrams(self, s1: str, s2: str) -> List[int]:
indices = []
if len(s1) >= len(s2):
counts = [0] * 26
for i in range(len(s2)):
counts[ord(s2[i]) - ord('a')] += 1
counts[ord(s1[i]) - ord('a')] -= 1
if self.areAllZero(counts):
indices.append(0)
for j in range(len(s2), len(s1)):
counts[ord(s1[j]) - ord('a')] -= 1
counts[ord(s1[j - len(s2)]) - ord('a')] += 1
if self.areAllZero(counts):
indices.append(j - len(s2) + 1)
return indices
剑指 Offer II 016. 不含重复字符的最长子字符串
哈希表+双指针,避免多次遍历整个哈希表
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)。避免需要多次遍历整个哈希表
def lengthOfLongestSubstring(self, s: str) -> int:
counts = dict()
longest = 1 if len(s) > 0 else 0
countDup = 0
j = -1
for i in range(len(s)):
counts[s[i]] = counts.get(s[i], 0) + 1
if counts[s[i]] == 2:
countDup += 1
while countDup > 0:
j += 1
counts[s[j]] -= 1
if counts[s[j]] == 1: # 用删除最右边一个字符后,该位置的值变成1判断是不是之前重复的字符,如果不是之前重复的,那会是-1
countDup -= 1
longest = max(i - j, longest)
return longest
哈希表+字符串 需要多次遍历整个哈希表
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)。需要多次遍历整个哈希表
def hasGreaterThan1(self, counts):
for count in counts.values():
if count > 1:
return True
return False
def lengthOfLongestSubstring(self, s: str) -> int:
counts = dict()
longest = 1 if len(s) > 0 else 0
j = -1
for i in range(len(s)):
counts[s[i]] = counts.get(s[i], 0) + 1
while self.hasGreaterThan1(counts):
j += 1
counts[s[j]] = counts.get(s[j], 0) - 1
longest = max(i - j, longest)
return longest
第06天 字符串
剑指 Offer II 017. 含有所有字符的最短字符串
哈希表+双指针
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def minWindow(self, s: str, t: str) -> str:
charToCount = dict()
for ch in t:
charToCount[ch] = charToCount.get(ch, 0) + 1
count = len(charToCount)
start = end = minStart = minEnd = 0
minLength = float('inf')
while end < len(s) or (count == 0 and end == len(s)):
if count > 0:
endCh = s[end]
if endCh in charToCount.keys():
charToCount[endCh] -= 1
if charToCount[endCh] == 0:
count -= 1
end += 1
else:
if end - start < minLength:
minLength = end - start
minStart = start
minEnd = end
startCh = s[start]
if startCh in charToCount.keys():
charToCount[startCh] += 1
if charToCount.get(startCh) == 1:
count += 1
start += 1
return s[minStart:minEnd] if minLength < float('inf') else ""
剑指 Offer II 018. 有效的回文
双指针
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def isPalindrome(self, s: str) -> bool:
i, j = 0, len(s) - 1
while i < j:
ch1 = s[i]
ch2 = s[j]
if not ch1.isalnum():
i += 1
elif not ch2.isalnum():
j -= 1
else:
if ch1.lower() != ch2.lower():
return False
i += 1
j -= 1
return True
剑指 Offer II 019. 最多删除一个字符得到回文
双指针
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def isPalindrome(self, s, start, end):
while start < end:
if s[start] != s[end]:
break
start += 1
end -= 1
return start >= end
def validPalindrome(self, s: str) -> bool:
start = 0
end = len(s) - 1
for _ in range(len(s) // 2):
if s[start] != s[end]:
break
start += 1
end -= 1
return start == len(s) // 2 or self.isPalindrome(s, start, end - 1) or self.isPalindrome(s, start + 1, end)
剑指 Offer II 020. 回文子字符串的个数
双指针,从里向外,考虑奇数和偶数
class Solution:
# 时间复杂度:O(n^2), 空间复杂度:O(1)
def countPalindrome(self, s, start, end):
count = 0
while start >= 0 and end < len(s) and s[start] == s[end]:
count += 1
start -= 1
end += 1
return count
def countSubstrings(self, s: str) -> int:
count = 0
for i in range(len(s)):
count += self.countPalindrome(s, i, i)
count += self.countPalindrome(s, i, i + 1)
return count
第07天 链表
剑指 Offer II 021. 删除链表的倒数第 n 个结点
前后双指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# 时间复杂度:O(n),空间复杂度:O(1)
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy = ListNode()
dummy.next = head
front, back = head, dummy
for i in range(n):
front = front.next
while front is not None:
front = front.next
back = back.next
back.next = back.next.next
return dummy.next
剑指 Offer II 022. 链表中环的入口节点
前后指针,不需要知道环中节点数目
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 时间复杂度:O(n),空间复杂度:O(1),不需要知道环中节点数目
def getNodeInloop(self, head):
if head is None or head.next is None:
return None
slow = head.next
fast = slow.next
while (slow is not None and fast is not None):
if slow == fast:
return slow
slow = slow.next
fast = fast.next
if fast is not None:
fast = fast.next
return None
def detectCycle(self, head: ListNode) -> ListNode:
inLoop = self.getNodeInloop(head) # 此时inloop的步数是环中节点数目的整数倍
if (inLoop is None):
return None
node = head
while node != inLoop: # 慢指针从头开始,快指针从inLoop开始,相遇时一定是环的入口节点
node = node.next
inLoop = inLoop.next
return node
前后指针,需要知道环中节点数目
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 时间复杂度:O(n),空间复杂度:O(1),需要知道环中节点数目
def getNodeInloop(self, head):
if head is None or head.next is None:
return None
slow = head.next
fast = slow.next
while (slow is not None and fast is not None):
if slow == fast:
return slow
slow = slow.next
fast = fast.next
if fast is not None:
fast = fast.next
return None
def detectCycle(self, head: ListNode) -> ListNode:
inLoop = self.getNodeInloop(head)
if (inLoop is None):
return None
loopCount = 1
n = inLoop
while n.next != inLoop:
loopCount += 1
n = n.next
fast = head
for i in range(loopCount):
fast = fast.next
slow = head
while fast!= slow:
fast = fast.next
slow = slow.next
return slow
剑指 Offer II 023. 两个链表的第一个重合节点
快慢指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 时间复杂度:O(m + n), 空间复杂度:O(1)
def Qiulength(self, head):
count = 0
while head != None:
count += 1
head = head.next
return count
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
count1 = self.Qiulength(headA)
count2 = self.Qiulength(headB)
delta = abs(count1 - count2)
longer = headA if count1 > count2 else headB
shorter = headB if count1 > count2 else headA
node1 = longer
for i in range(delta):
node1 = node1.next
node2 = shorter
while node1 != node2:
node2 = node2.next
node1 = node1.next
return node1
第08天 链表
剑指 Offer II 024. 反转链表
三个指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def reverseList(self, head: ListNode) -> ListNode:
pre = None
cur = head
while cur is not None:
next = cur.next
cur.next = pre
pre = cur
cur = next
return pre
剑指 Offer II 025. 链表中的两数相加
三个指针,反转链表的推广
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# 时间复杂度:O(m + n), 空间复杂度:O(1)
def addReversed(self, head1, head2):
dummy = ListNode(0)
sumNode = dummy
carry = 0
while head1 is not None or head2 is not None:
sum = (0 if head1 is None else head1.val) + (0 if head2 is None else head2.val) + carry
carry = 1 if sum >= 10 else 0
sum = sum - 10 if sum >= 10 else sum
newNode = ListNode(sum)
sumNode.next = newNode
sumNode = sumNode.next
head1 = None if head1 is None else head1.next
head2 = None if head2 is None else head2.next
if carry > 0:
sumNode.next = ListNode(carry)
return dummy.next
def reverseList(self, head):
pre = None
cur = head
while cur is not None:
next = cur.next
cur.next = pre
pre = cur
cur = next
return pre
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
head1 = self.reverseList(l1)
head2 = self.reverseList(l2)
reversedHead = self.addReversed(head1, head2)
return self.reverseList(reversedHead)
剑指 Offer II 026. 重排链表
快慢指针找到中点,反转链表推广(反转链表的一半)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# 时间复杂度:O(n),空间复杂度:O(1)
def link(self, node1, node2, head):
pre = head
while node1 is not None and node2 is not None:
temp = node1.next
pre.next = node1
node1.next = node2
pre = node2
node1 = temp
node2 = node2.next
if node1 is not None:
pre.next = node1
def reverseList(self, first):
pre = None
cur = first
while cur is not None:
next = cur.next
cur.next = pre
pre = cur
cur = next
return pre
def reorderList(self, head: ListNode) -> None:
dummy = ListNode()
dummy.next = head
fast = slow = dummy
while fast is not None and fast.next is not None:
slow = slow.next
fast = fast.next.next
temp = slow.next
slow.next = None
self.link(head, self.reverseList(temp), dummy)
第09天 链表
剑指 Offer II 027. 回文链表
快慢指针,反转链表的推广(反转链表的前半段并且跟后半段做比较)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# 时间复杂度:O(n),空间复杂度:O(1)
def isPalindrome(self, head: ListNode) -> bool:
if head is None or head.next is None:
return True
slow = head
fast = head.next
while fast.next is not None and fast.next.next is not None:
fast = fast.next.next
slow = slow.next
secondHalf = slow.next
if fast.next is not None:
secondHalf = slow.next.next
slow.next = None
return self.equals(secondHalf, self.reverseList(head))
def reverseList(self, head):
pre = None
cur = head
while cur is not None:
next = cur.next
cur.next = pre
pre = cur
cur = next
return pre
def equals(self, l1, l2):
while l1 is not None and l2 is not None:
if l1.val != l2.val:
return False
l1 = l1.next
l2 = l2.next
return True
剑指 Offer II 028. 展平多级双向链表
双向链表
"""
# Definition for a Node.
class Node:
def __init__(self, val, prev, next, child):
self.val = val
self.prev = prev
self.next = next
self.child = child
"""
class Solution:
# 时间复杂度:O(n),空间复杂度:O(k)。k是链表的层数
def flatten(self, head: 'Node') -> 'Node':
self.flattenGetTail(head)
return head
def flattenGetTail(self, head):
node = head
tail = None
while node is not None:
next = node.next
if node.child is not None:
child = node.child
childTail = self.flattenGetTail(node.child)
node.child = None
node.next = child
child.prev = node
childTail.next = next
if next is not None:
next.prev = childTail
tail = childTail
else:
tail = node
node = next
return tail
剑指 Offer II 029. 排序的循环链表
指针,循环链表