Loading

刷题笔记 - 腾讯50题(上)

From: 🐧 腾讯精选练习 50 题 - 力扣(LeetCode),按通过率降序排列


上篇

237. Delete Node in a Linked List

  • 参数中的node就是要删除的节点。我们可以把下个节点的值存到当前节点,然后让当前节点的next直接指向下下个节点即可

    class Solution:
        def deleteNode(self, node):
            node.val = node.next.val
            node.next = node.next.next
    

78. Subsets

  • 库函数 (itertools.combinations)

    def subset(nums):
        res = []
        for i in range(len(nums)+1):
            for tmp in itertools.combinations(nums, i):
                res.append(tmp)
        return res
    
  • 迭代组合:

    def subset(nums):
        res = [[]]
        for i in nums:
            res = res + [[i] + num for num in res]
        return res
    
    subset([1,2,3]) 迭代过程:
    i = 1, res = [[]]
    	[[i] + num for num in res] = [[1]]
    i = 2, res = [[], [1]]
    	[[i] + num for num in res] = [[2], [2, 1]]
    i = 3, res = [[], [1], [2], [2, 1]]
    	[[i] + num for num in res] = [[3], [3, 1], [3, 2], [3, 2, 1]]
    

344. Reverse String

  • 双指针:

    def reverseString(s):
        j = len(s) - 1
        for i in range(len(s)):
            s[i], s[j] = s[j], s[i]
            j -= 1
            if j <= i:
                break
    

46. Permutations

  • itertools.permutations

    from itertools import permutations
    
    class Solution:
        def permute(self, nums: List[int]) -> List[List[int]]:
            return [list(i) for i in permutations(nums, len(nums))]
    
  • 回溯:

    class Solution:
    
        def backtrace(self, comb, pool):
            if len(pool) > 0:
                for i in pool:
                    new_pool = pool.copy()
                    new_pool.remove(i)
                    self.backtrace(comb + [i], new_pool)
            else:
                self.res.append(comb)
    
        def permute(self, nums: List[int]) -> List[List[int]]:
            self.res = []
            self.backtrace([], nums)
            return self.res
    

104. Maximum Depth of Binary Tree

  • 深度优先遍历:

    class Solution:
        def dfs(self, node, depth=1):
            if node.left:
                self.dfs(node.left, depth+1)
            if node.right:
                self.dfs(node.right, depth+1)
            if depth > self.max_depth:
                self.max_depth = depth
        
        def maxDepth(self, root: Optional[TreeNode]) -> int:
            self.max_depth = 0
            if root:
                self.dfs(root)
            return self.max_depth
    

230. Kth Smallest Element in a BST

  • BST(二叉搜索树)的性质:“左子树中的所有节点 < 当前节点 < 右子树中的所有节点”。因此解法就是中序遍历(左根右)这颗树,每次处理根节点时对排位进行计数,直到找到目标:

    class Solution:
        def verify(self, node):
            self.k -= 1
            if self.k < 1:
                self.res = node.val
    
        def search(self, node):
            if node.left and self.res is None:
                self.search(node.left)
            if self.res is None:
                self.verify(node)
            if node.right and self.res is None:
                self.search(node.right)
    
        def kthSmallest(self, root: Optional[TreeNode], k: int) -> int:
            self.res = None
            self.k = k
            self.search(root)
            return self.res
    
  • 以迭代的方式进行中序遍历:

    class Solution:
        def kthSmallest(self, root: TreeNode, k: int) -> int:
            stack = []
            while root or stack:
                while root:  # go left first
                    stack.append(root)
                    root = root.left
                root = stack.pop()  # go back and verify
                k -= 1
                if k == 0:
                    return root.val
                root = root.right
    

89. Gray Code

  • 归纳法(找规律法):

    观察:

    ['0', '1']
    ['00', '01', '11', '10']
    ['000', '001', '011', '010', '110', '111', '101', '100']

    发现:

    n位的格雷码可以由n-1位的格雷码序列推导出来,具体方法如下:

    • 先遍历n-1位的格雷码序列,在每个元素前补'0',得到n位格雷码的前半段序列
    • 倒序遍历n位格雷码的前半段序列,将其中每个元素开头的'0'改成'1',得到后半段序列
    • 连接前后两段序列,即得到n位的格雷码序列
    class Solution:
        def grayCode(self, n: int) -> List[int]:
            res = ['0', '1']
            for _ in range(n - 1):
                part1 = ["0"+i for i in res]  # 在n-1位的gray code前补0
                part2 = ["1"+i[1:] for i in part1[::-1]]  # 倒序遍历part1,把其中每个元素第一位的0改成1
                res = part1 + part2
            return [int(i, 2) for i in res]  # 二进制字串转十进制数字
    

238. Product of Array Except Self

  • 从左往右遍历时,记录每个元素左侧元素的乘积。然后从右往左遍历,记录每个元素右侧元素的乘积。最后遍历乘积列表,得到每个元素左侧元素和右侧元素的乘积的积

    • 以[1,2,3,4]为例:先从左往右遍历,得到:[1,1,2,6]。接着从右往左遍历,得到:[24,12,4,1]。接着对[1,1,2,6]和[24,12,4,1]按位相乘,得到最终结果:[24, 12, 8, 6]。至此,一共完成了3次n位数组的遍历,算法时间复杂度为O(3n),空间复杂度为O(3n)
    • 实际上,从左往右遍历时,可以通过len(nums)-i得到反向坐标,进而同步计算从右向左的累乘结果,这样我们就节省了一次遍历,算法的时间复杂度可以从O(3n)降到O(2n)
    class Solution:
        def productExceptSelf(self, nums):
            left = [1]
            right = [1]
            for i in range(1, len(nums)):
                left.append(left[-1]*nums[i-1])
                right.append(right[-1]*nums[len(nums)-i])
            return [left[i] * right[len(nums)-1-i] for i in range(len(nums))]
    

557. Reverse Words in a String III

  • 基于序列表达式,分割单词,反转拼写,再用空格连接成句:

    class Solution:
        def reverseWords(self, s: str) -> str:
            return ' '.join([word[::-1] for word in s.split(' ')])
    

59. Spiral Matrix II

  • 就硬写:

    class Solution:
        def get_move_methods(self):
            go_right = lambda i, j: (i, j+1)
            go_down = lambda i, j: (i+1, j)
            go_left = lambda i, j: (i, j-1)
            go_up = lambda i, j: (i-1, j)
            return [go_right, go_down, go_left, go_up]
        
        def fill(self, res, n):
            i, j = 0, 0
            valid_ij = lambda i, j: (0 <= i < n) and (0 <= j < n)
            valid_res = lambda i, j: res[i][j] == 0
            counter = 1
            moves = self.get_move_methods()
            moves_i = 0
            move = moves[moves_i % len(moves)]
            while valid_res(i, j):
                res[i][j] = counter
                counter += 1
                next_i, next_j = move(i, j)
                if valid_ij(next_i, next_j) and valid_res(next_i, next_j):
                    i, j = next_i, next_j
                else:
                    moves_i += 1
                    move = moves[moves_i % len(moves)]
                    i, j = move(i, j)
            return res
        
        def generateMatrix(self, n):
            if n == 1:
                return [[1]]
            else:
                return self.fill([[0 for _ in range(n)] for _ in range(n)], n)
    

206. Reverse Linked List

  • 遍历节点时记录当前节点的前置节点和后置节点, 然后令当前节点的next指向之前保存的前置节点,接着用刚刚保存的后置节点来获得下一个遍历位置:

    # class ListNode:  # 链表定义
    #     def __init__(self, val=0, next=None):
    #         self.val = val
    #         self.next = next
    
    # def list2linkedlist(data):  # 调试用,构造链表
    #     last_node = None
    #     head = None
    #     for i in data:
    #         node = ListNode(i)
    #         if head is None:
    #             head = node
    #         if last_node:
    #             last_node.next = node
    #         last_node = node
    #     return head
       
    # def linkedlist2list(head):  # 调试用,解析链表
    #     cur = head
    #     res = list()
    #     while True:
    #         res.append(cur.val)
    #         if cur.next:
    #             cur = cur.next
    #         else:
    #             break
    #     return res
    
    class Solution:
        def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
            curr_node = head
            prev_node = None
            while curr_node:
                next_node = curr_node.next
                curr_node.next = prev_node
                prev_node = curr_node
                curr_node = next_node
            return prev_node
    

136. Single Number

  • 利用异或运算的4个性质:

    1. 归零律:a xor a = 0
    2. 恒等律:a xor 0 = a
    3. 交换律:a xor b = b xor a
    4. 结合律:a xor b xor c = a xor (b xor c) = (a xor b) xor c
    class Solution:
        def singleNumber(self, nums: List[int]) -> int:
            res = nums[0]
            if len(nums) > 1:
                for i in nums[1:]:
                    res = res ^ i  # 异或运算
            return res
    

122. Best Time to Buy and Sell Stock II

  • 贪心策略:由于可以在同一天进行买入和卖出,因此我们可以在遍历数组的同时记录当前的最低价,一旦发现当前价格高于之前记录的低价,就累加利润,然后用当前价格作为新的低价(换句话说,不用等到最高点再卖出,只要发现赚钱了,就马上卖掉然后原地再“买入”,后续如果发现了更低的价格,就用更低的价格作为“买点”)

    class Solution:
        def maxProfit(self, prices: List[int]) -> int:
            low_price = prices[0]
            profit = 0
            if len(prices) > 1:
                for cur_price in prices[1:]:
                    if cur_price < low_price:
                        low_price = cur_price
                    if cur_price > low_price:
                        profit += (cur_price - low_price)
                        low_price = cur_price
            return profit
    

292. Nim Game

  • 从n=1开始枚举,找出先后手在胜负上的规律:

    1 2 3 先手必胜
    4 后手必胜(任取1-3枚石头,都会进入先手必胜的情形)
    5 6 7 先手必胜(取1/2/3枚石头,让对手面对后手必胜的情形)
    8 后手必胜

    ...

    class Solution:
        def canWinNim(self, n: int) -> bool:
            return (n % 4) > 0
    

236. Lowest Common Ancestor of a Binary Tree

  • DFS

    # Definition for a binary tree node.
    # class TreeNode:
    #     def __init__(self, x):
    #         self.val = x
    #         self.left = None
    #         self.right = None
    
    class Solution:
        def find_answer(self, curr_node, stack):
            if curr_node.val == self.lca_father_val:
                self.lca = curr_node
                self.lca_father_val = stack[-1].val if stack else None
            if curr_node.val in self.targets:
                self.targets.remove(curr_node.val)
                if not self.targets:
                    return True
                self.lca = curr_node
                self.lca_father_val = stack[-1].val if stack else None
            return False
        
        def dfs(self, root):
            stack = []
            curr_node = root
            while curr_node or stack:
                while curr_node:
                    stack.append(curr_node)
                    curr_node = curr_node.left
                curr_node = stack.pop()
                if self.find_answer(curr_node, stack):
                    break
                curr_node = curr_node.right
    
        def lowestCommonAncestor(self, root, p, q):
            self.targets = {p.val, q.val}
            self.lca = None
            self.lca_father_val = None
            self.dfs(root)
            return self.lca
    

235. Lowest Common Ancestor of a Binary Search Tree

  • 利用二叉搜索树的性质:

    • 树中的每一个节点,其左子树中的值都比该节点值小,右子树中的值都比该节点值大
  • 因此,我们可以从根节点开始搜索:

    • 如果当前节点的值介于两个目标节点之间,则当前节点为lca
    • 如果当前节点的值大于两个目标节点,则继续检索左子树
    • 如果当前节点的值小于两个目标节点,则继续检索右子树
    class Solution:
        def lowestCommonAncestor(self, root, p, q):
            small_val = min([p.val, q.val])
            large_val = max([p.val, q.val])
            while root:
                if small_val <= root.val <= large_val:
                    return root
                elif large_val < root.val:
                    root = root.left
                else:
                    root = root.right
    

62. Unique Paths

  • 动态规划:

    • 状态定义:dp[i][j] 抵达坐标 (i, j)所需的不同路径数
    • 递推关系:dp[i][j] = dp[i-1][j] + dp[i][j-1]
    • 边界条件:dp[i][1] = 1, dp[1][j] = 1
    class Solution:
        def uniquePaths(self, m: int, n: int) -> int:
            dp = [[1 for _ in range(n)] for _ in range(m)]
            for i in range(1, m):
                for j in range(1, n):
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]
            return dp[-1][-1]
    

169. Majority Element

  • 哈希

    class Solution:
        def majorityElement(self, nums: List[int]) -> int:
            counter = dict()
            majority_element = nums[-1]
            max_count = 1
            while nums:
                item = nums.pop()
                if item in counter:
                    counter[item] += 1
                    if counter[item] > max_count:
                        majority_element = item
                        max_count = counter[item]
                else:
                    counter[item] = 1
            return majority_element
    

21. Merge Two Sorted Lists

  • 双指针

    # class ListNode:  # 链表定义
    #     def __init__(self, val=0, next=None):
    #         self.val = val
    #         self.next = next
    
    # def list2linkedlist(data):  # 调试用,构造链表
    #     last_node = None
    #     head = None
    #     for i in data:
    #         node = ListNode(i)
    #         if head is None:
    #             head = node
    #         if last_node:
    #             last_node.next = node
    #         last_node = node
    #     return head
       
    # def linkedlist2list(head):  # 调试用,解析链表
    #     cur = head
    #     res = list()
    #     while cur:
    #         res.append(cur.val)
    #         if cur.next:
    #             cur = cur.next
    #         else:
    #             break
    #     return res
    
    class Solution:
        def mergeTwoLists(self, list1, list2):
            tail_node = None
            new_head = None
            while list1 or list2:
                if list2 is None or (list1 and list1.val <= list2.val):
                    if tail_node:
                        tail_node.next = list1
                        tail_node = list1
                    else:
                        tail_node = list1
                    list1 = list1.next
                elif list1 is None or (list2 and list2.val <= list1.val):
                    if tail_node:
                        tail_node.next = list2
                        tail_node = list2
                    else:
                        tail_node = list2
                    list2 = list2.next
                if not new_head:
                    new_head = tail_node
            return new_head
    

148. Sort List

  • 双指针,一个针对旧链表,一个针对新链表。针对每个旧链表中的节点,遍历新链表,将它插入到合适的位置

    • 时间复杂度:\(O(n)\sim O(n^2)\),会超时
    class Solution:
        def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]:
            head_new = ListNode(val=head.val) if head else None
            cur_old = head.next if head else None
            while cur_old:
                cur_new = head_new
                previous_cur_new = None
                while cur_new:
                    if cur_old.val < cur_new.val:  # insert cur_old between previous_cur_new and cur_new
                        if previous_cur_new:
                            previous_cur_new.next = cur_old
                        next_cur_old = cur_old.next
                        cur_old.next = cur_new
                        if head_new == cur_new:
                            head_new = cur_old
                        cur_old = next_cur_old
                        break
                    else:
                        if not cur_new.next:
                            tail_new = cur_new
                        previous_cur_new = cur_new
                        cur_new = cur_new.next
                else:
                    tail_new.next = cur_old
                    next_cur_old = cur_old.next
                    cur_old.next = None
                    cur_old = next_cur_old
            return head_new
    
  • 转成list,然后排序,然后再转回链表

    • 时间复杂度:O(2n+nlogn)
    class Solution:
        @staticmethod
        def list2linkedlist(data):
            last_node = None
            head = None
            for i in data:
                node = ListNode(i)
                if head is None:
                    head = node
                if last_node:
                    last_node.next = node
                last_node = node
            return head
    
        @staticmethod
        def linkedlist2list(head):
            cur = head
            res = list()
            while cur:
                res.append(cur.val)
                if cur.next:
                    cur = cur.next
                else:
                    break
            return res
        
        def sortList(self, head):
            return self.list2linkedlist(sorted(self.linkedlist2list(head)))
    

215. Kth Largest Element in an Array

  • 快速排序原理(时间复杂度nlogn)
    • 分解:不断地将数组分成两半,使其一半较小,一半较大
      • 这里的划分方法具体为:从数组中任选一个元素作为主元,调整子数组的元素使得左边的元素都小于它,右边的元素都大于它
    • 合并:递归调用快速排序,对分成的两半进行排序
    • 如果需要将时间复杂度降为O(n),则需要引入随机性,详见《算法导论》9.2:期望为线性的选择算法
  • 优先队列原理
    • 普通的队列具有先进先出的特性,元素追加在队尾,如果删除的话,从队头删除。而在优先队列中,队列中的数据被赋予了优先级。当访问元素时,优先级最高的会先被删除。所以说优先队列是最高级数据先出
    • 优先队列支持的操作:
      1. 定义优先级,从而在删除时直接弹出最小(最大)元素
      2. 插入一个元素
    • 优先队列一般通过维护一个堆来实现,堆的原理这里暂时就先不展开了

160. Intersection of Two Linked Lists

  • 分别遍历两个链表,将其转化为list,然后反向遍历两个list,比对找出最后一个公用节点

    class Solution:
        @staticmethod
        def linkedlist2list(head):
            cur = head
            res = list()
            while cur:
                res.append(cur)
                if cur.next:
                    cur = cur.next
                else:
                    break
            return res
        
        def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> Optional[ListNode]:
            listA = self.linkedlist2list(headA)
            listB = self.linkedlist2list(headB)
            intersec_node = None
            while listA and listB:
                nodeA = listA.pop()
                nodeB = listB.pop()
                if nodeA is nodeB:
                    intersec_node = nodeA
                else:
                    break
            return intersec_node
    
  • 另外,看了下题解里的NB思路,感觉很值得学习,所以这里也记录一下:

    我先走我的路,再走你的路,你先走你的路,再走我的路,这样咱俩走的路程就一样了,速度一样,那么肯定在咱俩两条路的交叉口相遇,并一起走向终点。

    class Solution:
        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
    

    换一种更有文采的描述(from:sylin):

    走到尽头见不到你,于是走过你来时的路,等到相遇时才发现,你也走过我来时的路。


11. Container With Most Water

  • 暴力搜索,时间复杂度O(nlogn),超时:

    class ForceSearch:
        def maxArea(self, height):
            max_v = 0
            for i in range(len(height)):
                for j in range(i+1, len(height)):
                    v = min(height[i], height[j]) * abs(i-j)
                    max_v = v if v > max_v else max_v
            return max_v
    
  • 双指针:

    • 由于水槽面积 = 短板高度×底边长度,因此,当我们让双指针从两端向中间逼近时:
      • 如果移动长板,那么下一轮的 短板高度 必然不变或变小,由于底边长度减短了1,因此水槽面积必然缩小
      • 如果移动短板,那么下一轮的 短板高度 才有可能变大,因此水槽面积也就有变大的可能
    • 因此,我们的策略是:初始化双指针分列水槽左右两端,循环每轮将短板向内移动一格,并更新面积最大值,直到两指针相遇时跳出,即可获得最大面积:
    class DoublePoint:
        def maxArea(self, height):
            max_v = 0
            i, j = 0, len(height)-1
            while i != j:
                v = min(height[i], height[j]) * abs(i-j)
                max_v = v if v > max_v else max_v
                if height[i] < height[j]:
                    i += 1
                else:
                    j -= 1
            return max_v
    

155. Min Stack

  • 时间复杂度要求严格,但空间复杂度不限。那么思路就应该转到:“以空间换时间”这条路线上:

    • 在维护栈时,将栈中的元素由val 换成(val, min_val),其中val是当前插入的数据,min_val是插入当前数据时,栈中数据的最小值
    class MinStack:
    
        def __init__(self):
            self.stack = []
    
        def push(self, val: int) -> None:
            if not self.stack:
                min_val = val
            else:
                min_val = min(val, self.getMin())
            self.stack.append((val, min_val))
    
        def pop(self) -> None:
            val, _ = self.stack.pop()
            return val
    
        def top(self) -> int:
            val, _ = self.stack[-1]
            return val
    
        def getMin(self) -> int:
            _, min_val = self.stack[-1]
            return min_val
    

121. Best Time to Buy and Sell Stock

  • 在遍历数组时,寻找当前最小值之后的极大值,针对每个符合前述条件的极大值,计算当前利润,最后返回过程中得到的最大利润:

    class Solution:
        def maxProfit(self, prices: List[int]) -> int:
            min_p, max_p = prices[0], prices[0]
            min_p_i = 0
            max_profit = 0
            for i in range(1, len(prices)):
                if prices[i] < min_p:
                    min_p, max_p = prices[i], prices[i]
                if prices[i] > max_p:
                    max_p = prices[i]
                    profit = max_p - min_p
                    max_profit = profit if profit > max_profit else max_profit
            return max_profit
    
posted @ 2023-04-02 10:22  云野Winfield  阅读(18)  评论(0编辑  收藏  举报