LeetCode卡片探索-中级算法

卡片探索-中级算法

  • 数组和字符串
    • 三数之和
    • 矩阵置零
    • 字母异位词分组
    • 无重复字符的最长子串
    • 最长回文子串
    • 递增的三元子序列
  • 链表
    • 两数相加
    • 奇偶链表
    • 相交链表
  • 树和图
    • 中序遍历二叉树
    • 二叉树的锯齿形层次遍历
    • 从前序和中序遍历序列构造二叉树
    • 填充每个节点的的下一个右侧节点
    • 二叉搜索树中第k小的元素
    • 岛屿数量
  • 回溯算法
    • 电话号码的字母组合
    • 括号生成
    • 全排列
    • 子集
    • 单词搜索
  • 排序和搜索
    • 颜色分类
    • 前 K 个高频元素
    • 数组中的第K个最大元素
    • 寻找峰值
    • 在排序数组中查找元素的第一个和最后一个位置
    • 合并区间
    • 搜索旋转排序数组
    • 搜索二维矩阵Ⅱ
  • 动态规划
    • 跳跃游戏
    • 不同路径
    • 零钱兑换
    • 最长上升子序列
  • 设计问题
    • 二叉树的序列化和反序列化
    • Insert Delete GetRandom O(1)
  • 数学
    • 快乐数
    • 阶乘后的0
    • Excel表列序号
    • Pow(x, n)
    • x的平方根
    • 两数相除
    • 分数到小数
  • 其他
    • 两整数之和
    • 逆波兰表达式求值
    • 多数元素
    • 任务调度器

LeetCode上知识卡片之 中级算法的刷题记录

数组和字符串

三数之和

把数组排序后,遍历数组,每次固定当前元素,然后用双指针再后续数组里面搜索与它相加为0的元素组合。

class Solution:
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        n=len(nums)
        res=[]
        nums.sort()
        for i in range(n):
            if nums[i] > 0:
            #因为排序了,遍历到大于0的数不可能有结果
                return res
            #重复元素只要第一个
            if(i>0 and nums[i]==nums[i-1]):
                continue
            l = i + 1
            r = n - 1
            while l<r:
                if nums[i] + nums[l] +nums[r] == 0:
                    res.append( [nums[i] , nums[l] , nums[r]])
                    #两个while过滤掉重复元素
                    while(l<r and nums[l]==nums[l+1]):
                        l += 1
                    while(l<r and nums[r]==nums[r-1]):
                        r -= 1
                    l -= 1
                    r -= 1
                elif(nums[i]+nums[l]+nums[r]>0):
                    r-=1
                else:
                    l+=1
        return res

矩阵置零

要求原地变换,所以用行列的第一个元素作为标记,来表示该行该列是否要变为0

class Solution:
    def setZeroes(self, matrix: List[List[int]]) -> None:
        """
        Do not return anything, modify matrix in-place instead.
        """
        #记录第一列
        is_col = False
        R = len(matrix)
        C = len(matrix[0])
        
        for i in range(R):
            #只要第一列出现0,标记为True
            if matrix[i][0] == 0:
                is_col = True
            for j in range(1, C):
                if matrix[i][j]  == 0:
                    matrix[0][j] = 0
                    matrix[i][0] = 0
        
        
        #根据行列开头标志改变元素
        for i in range(1, R):
            for j in range(1, C):
                if not matrix[i][0] or not matrix[0][j]:
                    matrix[i][j] = 0
        
        #单独处理第一列和第一行
        if matrix[0][0] == 0:
            for j in range(C):
                matrix[0][j] = 0
    
        if is_col:
            for i in range(R):
                matrix[i][0] = 0

字母异位词分组

本质上是要我们维护一个字典,字母异位词要对应着同一个key,我们可以把字符串排序后当做key,可以把字母计数结果当作key。

class Solution:
    def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
        ans = collections.defaultdict(list)
        #默认字典,索引不在key中,就新建一个空list对应
        for s in strs:
            count = [0] * 26
            for c in s:
                count[ord(c) - ord('a')] += 1
            ans[tuple(count)].append(s)
        return list(ans.values())

无重复字符的最长子串

很明显要使用滑动窗口的思想,一边滑动一边判断就可。

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        if not s:
            return 0
        l = r = out = 0
        #窗口内字符
        pattern = set()
        while r < len(s):
            if s[r] not in pattern:
                pattern.add(s[r])
                out = max(out,r-l+1)
                r += 1
            else:
                pattern.remove(s[l])
                l += 1
        return out

最长回文子串

回文子串可以分解为对应的子问题,所以采用动态规划思想。要注意的是两个循环的写法,要保证子问题的求解顺序,不能先出现依赖还没求解子问题的高阶问题。

class Solution:
    def longestPalindrome(self, s: str) -> str:
        n = len(s)
        dp = [[False] * n for _ in range(n)]
        ans = ""
        # 枚举子串的长度 l+1
        for l in range(n):
        #子串起始位置
            for i in range(n):
                j = i + l
                if j >= len(s):
                    break
                if l == 0:
                    dp[i][j] = True
                elif l == 1:
                    dp[i][j] = (s[i] == s[j])
                else:
                    dp[i][j] = (dp[i + 1][j - 1] and s[i] == s[j])
                if dp[i][j] and l + 1 > len(ans):
                    ans = s[i:j+1]
        return ans

递增的三元子序列

问题只要求判断存在与否就可,我们可以用两个变量存储当前的最小值和次小值,如果新遍历到的值大于次小值,那么已经构成了一个三元组,可以返回True;如果不大于,则根据具体值更新最小次小值即可。

class Solution:
    def increasingTriplet(self, nums: List[int]) -> bool:
        n1 = float('inf')
        n2 = float('inf')
        for num in nums:
            if num <= n1:
                n1 = num
            elif num <= n2:
                n2 = num
            else:
                return True
        return False

链表

两数相加

模拟字符串数字加法即可

class Solution:
    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        res  = p = ListNode()
        carry = 0
        while l1 or l2 or carry:
        #优化边界判断
            x = l1.val if l1 else 0
            y = l2.val if l2 else 0
            p.next = ListNode((x+y+carry)%10)
            p = p.next
            carry = (x+y+carry)//10
            if l1:
                l1 = l1.next
            if l2:
                l2 = l2.next
        return res.next

奇偶链表

用两个指针分别记录奇链表尾和偶链表头,再用一个指针遍历原链表(顺便充当偶链表尾)即可

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, val=0, next=None):
#         self.val = val
#         self.next = next
class Solution:
    def oddEvenList(self, head: ListNode) -> ListNode:
        if not head :
            return head
        p1 = head
        p2 = p = head.next
        #P1,P2分别为奇偶链表尾
        while p2 and p2.next:
            p1.next = p2.next
            p1 = p1.next
            p2.next = p2.next.next
            p2 = p2.next
        p1.next = p
        return head

相交链表

把两个链表分别前后顺序拼接起来,如果是相交的,遍历的时候一定会到相同的节点(走过相同的路)。

class Solution:
    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        p1 = headA
        p2 = headB
        while p1 != p2:
            p1 = p1.next if p1 else headB
            p2 = p2.next if p2 else headA
        #如果两个链表不相交的话,p1和p2都是空值
        return p1

树和图

中序遍历二叉树

递归的解法很容易写出

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        def help(root,res):
            if not root:
                return
            help(root.left,res)
            res.append(root.val)
            help(root.right,res)
        help(root,res)
        return res

迭代需要考虑访问顺序

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        s = []
        x = root
        while s or x:
            while x:
                s.append(x)
                x = x.left
                #左子树全部进栈了
            #接下来弹出的就是根节点 
            x = s.pop()
            res.append(x.val)
            x = x.right
        return res

同时迭代的代码改成先序遍历很简单

    def preorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        s = []
        x = root
        while s or x:
            while x:
                res.append(x.val)
                #第一次遍历到节点就输出,而不是上面那样出栈时再输出
                s.append(x)
                x = x.left
            x = s.pop()
            x = x.right
        return res

后序遍历的迭代就稍微麻烦一点了

class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        s = []
        x = root
        while s or x:
            while x:
                s.append(x)
                x = x.left if x.left is not None else x.right
                #这里不光是上面那样先访问左子树,没有左子树但是有右子树的也要先访问。
            x = s.pop()
            res.append(x.val)
            #退栈时只是左子树完了的话还需要访问右子树,因为后序时唯一一个右在根节点之前的遍历方式。
            if s and s[-1].left == x: #若栈不为空且当前节点是栈顶元素的左节点
                x = s[-1].right   ## 则转向遍历右节点
            else:
                x = None  # 没有左子树或右子树,强迫退栈
        return res

二叉树的锯齿形层次遍历

可以简单的按照一般层次遍历结果再按奇偶反转,也可以用双端队列花哨一点,但是本质上复杂度是一样的。。。

class Solution:
    def zigzagLevelOrder(self, root: TreeNode) -> List[List[int]]:
        if not root:
            return []
        q = queue.Queue()
        q.put(root)
        res = []
        is_left = False
        while q.qsize():
            tmp = collections.deque()
            for _ in range(q.qsize()):
                node = q.get()
                if is_left:
                    tmp.appendleft(node.val)
                else:
                    tmp.append(node.val)
                if node.left:
                    q.put(node.left)
                if node.right:
                    q.put(node.right)

            is_left = not is_left
            res.append(list(tmp))
        return res

从前序和中序遍历序列构造二叉树

先在前序序列找到根节点,再根据根节点在中序序列中划分开左右子树的节点,递归构造子树。

class Solution:
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        if len(inorder) == 0:
            return None
        # 前序遍历第一个值为根节点
        root = TreeNode(preorder[0])
        # 利用根节点划开左右子树
        mid = inorder.index(preorder[0])
        root.left = self.buildTree(preorder[1:mid+1], inorder[:mid])
        root.right = self.buildTree(preorder[mid+1:], inorder[mid+1:])
        return root

同时有一个迭代的方法,不过比较复杂,大家可以看 官方题解.

填充每个节点的的下一个右侧节点

相当于一种增加了操作的层次遍历。

class Solution:
    def connect(self, root: 'Node') -> 'Node':
        if not root:
            return
        q = []
        q.append(root)
        while q:
            tmp = []
            for i in range(len(q)):

                node = q[i]
                node.next = q[i+1] if i <len(q)-1 else None

                if node.left:
                    tmp.append(node.left)
                if node.right:
                    tmp.append(node.right)
            q = tmp[:]
            #记得返回根节点,开始看原地还以为不用返回
        return root

二叉搜索树中第k小的元素

利用二叉搜索树自身的良好性质,先遍历中序遍历,每遍历一个节点就计数器k-1,直到输出k=0的节点。

class Solution:
    def kthSmallest(self, root: TreeNode, k: int) -> int:
        self.k = k
        def dfs(root):
            if not root:
                return
            dfs(root.left)
            self.k -= 1
            if self.k == 0:
                self.res = root.val
            dfs(root.right)
        dfs(root)
        return self.res

岛屿数量

用dfs一次把一个岛屿的1清空即可,但是注意题目输入时字符串,不是数字0和1

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        direcs = [[0,1],[-1,0],[0,-1],[1,0]]
        res = 0
        def dfs(x,y):
            grid[x][y] = '0'
            for direc in direcs:
                new_x = x +direc[0]
                new_y = y +direc[1]
                #判断是否更深度搜索
                if 0 <= new_x < len(grid) and 0 <= new_y < len(grid[0]) and grid[new_x][new_y] == '1':
                    dfs(new_x,new_y)
        
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '1':
                    dfs(i,j)
                    res += 1
        return res

回溯算法

电话号码的字母组合

按照字典映射,保存当前已有的字母组合后缀添加数字即可。

class Solution:
    def letterCombinations(self, digits: str) -> List[str]:
        phone = {'2': ['a', 'b', 'c'],
                 '3': ['d', 'e', 'f'],
                 '4': ['g', 'h', 'i'],
                 '5': ['j', 'k', 'l'],
                 '6': ['m', 'n', 'o'],
                 '7': ['p', 'q', 'r', 's'],
                 '8': ['t', 'u', 'v'],
                 '9': ['w', 'x', 'y', 'z']}
        if not digits:
            return []
        res = ['']
        #已有组合
        for c in digits:
            temp = []
            #保存新组合
            for pre in res:
                for alpha in phone[c]:
                    temp.append(pre+alpha)
            res = temp
        return res

括号生成

如果我们把第一个的’(‘对应的’)‘看作要改变的量,那么’)'左边会有i对括号,右边有n-i-1对括号,调用递归即可。

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        if n == 0:
            return ['']
        out = []
        for i in range(n):
            for l in self.generateParenthesis(i):
                for r in self.generateParenthesis(n -1 - i):
                    out.append('(' + l + ')' + r)
        return out

也可以符合官方思想的迭代

class Solution:
    def generateParenthesis(self, n: int) -> List[str]:
        if n == 0:
            return ['']
        out = []
        def backtrack(s,l,r):
            if len(s) == 2*n:
                out.append(''.join(s))
                return 
            if l < n:
                s.append('(')
                backtrack(s,l+1,r)
                #回溯,记得出栈
                s.pop()
            if l > r:
                s.append(')')
                backtrack(s,l,r+1)
                #回溯,记得出栈
                s.pop()                
        backtrack([],0,0)
        return out

全排列

生成排列时把数字分为已使用和未使用

class Solution:
    def permute(self, nums: List[int]) -> List[List[int]]:
    #first就是未使用数字的开头
        def backtrack(first = 0):
            # 所有数都填完了
            if first == n:  
                res.append(nums[:])
            for i in range(first,n):
                nums[i],nums[first] = nums[first],nums[i]
                backtrack(first + 1)
                #回溯的撤销操作
                nums[i],nums[first] = nums[first],nums[i]
        n = len(nums)
        res = []
        backtrack()
        return res

子集

一个子集可以用依次对于每个元素考虑包含和不包含的方法来生成。

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        out = [[]]
        for x in nums:
            n = len(out)
            for i in range(n):
                temp = out[i][:]
                #保留原来那个不包含x的子集
                temp.append(x)
                out.append(temp)
        return out

也可以就用回溯思想

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        def backtrack(first = 0, curr = []):
            #满足长度k
            if len(curr) == k:  
                output.append(curr[:])
            for i in range(first, n):
                #dfs
                curr.append(nums[i])
                backtrack(i + 1, curr)
                #回溯撤销
                curr.pop()
        
        output = []
        n = len(nums)
        for k in range(n + 1):
            #因为子集长度不一,每个for求出一种长度的
            backtrack()
        return output

单词搜索

如果没有说不能重复使用字符,那么简单的dfs就可以,但是说了就用用到回溯法,用一个同样大小的矩阵标记元素有没有被访问过。

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        direcs = [[0,1],[1,0],[-1,0],[0,-1]]
        m = len(board)
        n = len(board[0])
        
        def dfs(x,y,k):
            if k == len(word)-1:
                return True
            flag = False
            #访问过的标志
            visid[x][y] = False
            for direc in direcs:
                new_x = x + direc[0]
                new_y = y + direc[1]
                if 0 <= new_x < m and 0 <= new_y < n and board[new_x][new_y] == word[k+1] and visid[new_x][new_y]:
                    flag = flag or dfs(new_x,new_y,k+1)
            #回溯撤销
            visid[x][y] = True
            return flag
        #标记访问过没有
        visid = [[True]*n for _ in range(m)]
        
        for i in range(m):
            for j in range(n):
                if board[i][j] == word[0]:
                    if dfs(i,j,0):
                        return True
        return False

排序和搜索

颜色分类

除了遍历用的指针外,再用两个指针定位当前插入0和插入2的位置。

class Solution:
    def sortColors(self, nums: List[int]) -> None:
    	#插入0的位置
        p0 = 0
        #遍历指针
        p1 = 0
        #插入2的位置
        p2 = len(nums)-1
        while p1 <= p2:
            if nums[p1] == 0:
                nums[p1],nums[p0] = nums[p0],nums[p1]
                p0 += 1
                p1 += 1
            elif nums[p1] == 2:
                nums[p1],nums[p2] = nums[p2],nums[p1]
                p2 -= 1
            else:
                p1 += 1

前 K 个高频元素

统计一下各个元素出现次数,再根据这个排序输出结果即可。

class Solution:
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
    #元素-出现次数字典
        n_f = {}
    #出现次数-元素list字典
        f_n = {}
        for i in nums:
            n_f[i] = n_f.get(i,0) + 1
        for n,f in n_f.items():
            temp = f_n.get(f,[])
            temp.append(n)
            f_n[f] = temp
    
        arr = []
        for x in range(len(nums),0,-1):
        #把出现次数从大到小检索,而不是排序频率的字典了
            if x in f_n:
                for i in f_n[x]:
                    arr.append(i)
        return arr[:k]

数组中的第K个最大元素

借助快排里面的划分函数,O(n)时间完成查询操作。

class Solution:
    def findKthLargest(self, nums: List[int], k: int) -> int:
        def partition(l,r):
        #常规的划分算法
            if l >= r:
                return
            mid = l + (r-l)//2
            index = nums[mid]
            nums[mid],nums[r] = nums[r],nums[mid]
            parti = l
            for i in range(l,r):
                if nums[i] < index:
                    nums[i],nums[parti] = nums[parti],nums[i]
                    parti += 1
            nums[parti],nums[r] = nums[r],nums[parti]
            return parti
        
        def select(l,r,k):
        #这里k是指第k小,所以输入的k要反过来算
            if l == r:
                return nums[l]
            pivot = partition(l, r)
            
            if k == pivot:
                return nums[k]
            #另外的区间寻找
            elif k < pivot:
                return select(l,pivot-1,k)
            else:
                return select(pivot + 1,r,k)
            
        return select(0,len(nums)-1,len(nums)-k)
            

寻找峰值

看到要求的 O ( l o g 2 n ) O(log_2n) O(log2n)就只要要用二分法。

class Solution:
    def findPeakElement(self, nums: List[int]) -> int:
        l = 0
        r = len(nums)-1
        while l < r:
            mid = l + (r-l)//2
            if nums[mid] > nums[mid+1]:
            #该情况波峰在左边,并且有可能是mid,所以不能r=mid-1
                r = mid
            else:
            #该情况波峰在右边,并且nums[mid+1]的值大于nums[mid]的,所以l=mid+1,mid没有机会成为峰值
                l = mid + 1
        return l

在排序数组中查找元素的第一个和最后一个位置

利用两趟二分查找即可。

class Solution:
    def searchRange(self, nums: List[int], target: int) -> List[int]:
        l = 0
        r = len(nums)-1
        #寻找左边界
        while l  <= r:
            mid = l + (r-l)//2
            if nums[mid] < target:
                l = mid + 1 
            else:
                r = mid - 1
        lout = r
        l = 0
        r = len(nums)-1
        #寻找右边界
        while l <= r:
            mid = l + (r-l)//2
            if nums[mid] <= target:
                l = mid + 1
            else:
                r = mid - 1
        rout = l
        return [lout+1,rout-1] if rout - lout > 1 else [-1,-1]

合并区间

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        out = []
        if not intervals:
            return out
        intervals.sort(key = lambda x: x[0])
        s = -1
        e = -1
        for tmp in intervals:
            if tmp[0] > e:
            #应该新建的区间
                out.append([s,e])
                s = tmp[0]
                e = tmp[1]
            else:
            #应该合并,结束时间取最大
                e = max(e,tmp[1])
        else:
        #尾处理最后一个区间
            out.append([s,e])
        #消除第一个[-1,-1]
        return out[1:]

搜索旋转排序数组

发现这道题不要想的太复杂把自己绕进去了,二分搜索下再细分两种情况就可以,当前位置左边有序还有右边有序。

class Solution:
    def search(self, nums: List[int], target: int) -> int:
        if not nums:
            return -1
        l = 0
        r = len(nums) - 1
        while l <= r:
            mid = l + (r-l)//2
            if nums[mid] == target:
                return mid 
            #左半有序,记得加等号,因为mid可能等于l
            if nums[l] <= nums[mid]:
            #在左半有序区间,好处理
                if nums[l] <= target < nums[mid]:
                    r = mid -1
                #在右边未知顺序的数组里面,就给下一个循环判断
                else:
                    l = mid + 1
            #右半有序
            else:
            #在右半有序区间,好处理
                if nums[mid] < target <= nums[r] :
                    l = mid + 1
                else:
                #在左边未知顺序的数组里,交给下一个循环判断
                    r = mid -1
        #上面查找很完备,找不到则是不存在
        return -1
                

搜索二维矩阵Ⅱ

利用矩阵自身性质,从左下角或者右上角开始搜索。

class Solution:
    def searchMatrix(self, matrix, target):

        if not matrix:
            return False
            
        m = len(matrix)
        n = len(matrix[0])
        i = m-1
        j = 0
        while i >=0 and j < n:
            if matrix[i][j] == target:
                return True
                #往右,变大
            elif matrix[i][j] < target:
                j += 1
                #往上,变小
            else:
                i -= 1
        return False

动态规划

又到了我最喜欢的dp环节了

跳跃游戏

借用一句话‘贪心是每个状态只使用一次的动态规划。’,这道题用贪心的思路,维护一个最大跳跃距离即可。

class Solution:
    def canJump(self, nums: List[int]) -> bool:
    #最大距离
        maxl = 0
    #当前位置
        i = 0
        while i <= maxl and i < len(nums):
            maxl = max(maxl,i+nums[i])
            i += 1
        return i == len(nums)

不同路径

一个方块只能由上或者右到达。

class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
    #压缩空间,只要一行记录就行,遍历是一行行来的。
        dp = [1] * n
        for i in range(1,m):
            for j in range(1,n):
                dp[j] += dp[j-1]
        return dp[-1]

零钱兑换

class Solution:
    def coinChange(self, coins: List[int], amount: int) -> int:
    #记录硬币个数
        dp = [0] + [float('inf')] * (amount)
        for i in range(amount+1):
            for coin in coins:
                if i >= coin:
                #新的组合是否更好
                    dp[i] = min(dp[i],dp[i-coin] + 1)
        return dp[-1] if dp[-1] != float('inf') else -1

最长上升子序列

用dp思想很容易写出代码,因为子序列明显可以划分子问题。

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        if not nums:
            return 0
        dp = [1] * len(nums)
        for i in range(len(nums)):
            for j in range(i):
                if nums[i] > nums[j]:
                    dp[i] = max(dp[i],dp[j]+1)
        return max(dp)
            

但是这样时间复杂度是 O ( n 2 ) O(n^2) O(n2)可以进一步优化一下,可以换一种贪心策略,则我们需要让序列上升得尽可能慢,因此我们希望每次在上升子序列最后加上的那个数尽可能的小。

class Solution:
    def lengthOfLIS(self, nums: List[int]) -> int:
        #维护一个上升序列
        res = []
        for num in nums:
            if not res or num > res[-1]:
                res.append(num)
            else:
                #找到应该替换的位置
                l, r = 0, len(res) - 1
                loc = r
                #除非替换到了最后一个,不然不影响长度,同时最
                while l <= r:
                    mid = (l + r) // 2
                    if res[mid] >= num:
                        loc = mid
                        r = mid - 1
                    else:
                        l = mid + 1
                res[loc] = num
                
        return len(res)

设计问题

二叉树的序列化和反序列化

熟练运用递归思路构建。

class Codec:

    def serialize(self, root):
        """Encodes a tree to a single string.
        
        :type root: TreeNode
        :rtype: str
        """
        def help(root,s):
            if not root:
                s += 'None,'
            else:
                s += str(root.val) + ','
                s = help(root.left,s)
                s = help(root.right,s)
            return s
        
        return help(root,'')

    def deserialize(self, data):
        """Decodes your encoded data to tree.
        
        :type data: str
        :rtype: TreeNode
        """
        def help(data_list):
            x = data_list.pop(0)
            if x == 'None':
                return None
            root = TreeNode(x)
            root.left = help(data_list)
            root.right = help(data_list)
            return root
        data_list = data.split(',')
        root = help(data_list)
        return root

Insert Delete GetRandom O(1)

除了正常的数组存储数据之外,还要一个字典保存索引和数值的对应关系。整个功能最关键的地方在于删除操作,为了进行最小程度的修改,用数组的最后一个元素填充到删除的位置,这样只要更改一个索引即可。

class RandomizedSet:

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.lst = []
        self.idx = {}

    def insert(self, val: int) -> bool:
        """
        Inserts a value to the set. Returns true if the set did not already contain the specified element.
        """
        if val not in self.idx:
            self.idx[val] = len(self.lst)
            self.lst.append(val)
            return True
        return False


    def remove(self, val: int) -> bool:
        """
        Removes a value from the set. Returns true if the set contained the specified element.
        """
        if val not in self.idx.keys():
            return False
        index = self.idx[val]
        fill = self.lst[-1]
        #最后一个数值填到删除的位置
        self.lst[index] = fill
        self.idx[fill] = index
        self.lst.pop()
        del self.idx[val]
        return True
        

    def getRandom(self) -> int:
        """
        Get a random element from the set.
        """
        return random.choice(self.lst)

数学

快乐数

本质上相当于判断链表是否有环。

class Solution:
    def isHappy(self, n: int) -> bool:
        #定义一个next方法
        def next(number):
            sum = 0
            while number > 0:
                number, digit = divmod(number, 10)
                sum += digit ** 2
            return sum
        
        #快慢指针
        slow_runner = n
        fast_runner = next(n)
        #双指针判断链表是否有环
        while fast_runner !=  1 and slow_runner != fast_runner:
            slow_runner = next(slow_runner)
            fast_runner = next(next(fast_runner))
        return fast_runner == 1

阶乘后的0

需要知道,阶乘中出现0的情况只能是2*5,所以0的个数是为阶乘元素因子分解中2和5中最小出现次数那么多。实际上5比2少得到,所以5的个数就是乘完后0的个数。

class Solution:
    def trailingZeroes(self, n: int) -> int:
        res = 0
        while n:
            n //= 5
            #因为数字可能是25,125之类含有多个5的因子
            res += n
        return res

Excel表列序号

当作26进制的数,逐位转化

class Solution:
    def titleToNumber(self, s: str) -> int:
        res = 0
        #位数
        power = 0
        for c in reversed(s):
            res += (ord(c) - ord('A') + 1) * pow(26,power)
            power += 1
        return res
            

Pow(x, n)

二分思想拆分,可以减少一般递归次数。

class Solution:
    def myPow(self, x: float, n: int) -> float:
        def quickMul(N):
            if N == 0:
                return 1.0
            y = quickMul(N // 2)
            #奇数多余一个x
            return y * y if N % 2 == 0 else y * y * x

        #考虑负指数
        return quickMul(n) if n >= 0 else 1.0 / quickMul(-n)

把n拆成2进制考虑也是一个道理

class Solution:
    def myPow(self, x: float, n: int) -> float:
        
        def quickMul(x,N):
            res = 1.0
            while N:
            #对应二进制位值为1,考虑这个倍数
                if N%2:
                    res *= x
                x *= x
                N = N >> 1
            return res

        
        return quickMul(x,n) if n >= 0 else 1.0 / quickMul(x,-n)

x的平方根

利用牛顿法求解

class Solution:
    def mySqrt(self, x: int) -> int:
        if x == 0:
            return 0
        
        C, x0 = float(x), float(x)
        while True:
            #牛顿法,泰勒一阶展开令其等于0推出迭代公式,注意和优化器里的牛顿法区分一下,那是利用牛顿法求解极值问题,是二阶展开并对x求导。
            xi = 0.5 * (x0 + C / x0)
            if abs(x0 - xi) < 1e-7:
                break
            x0 = xi
        
        return int(x0)

两数相除

在二进制数据上模拟以前的短除法达到要求。

class Solution:
    def divide(self, dividend: int, divisor: int) -> int:
        #边界处理
        sign = (dividend > 0) ^ (divisor > 0)
        dividend = abs(dividend)
        divisor = abs(divisor)
        count = 0
        while dividend >= divisor:
            count += 1
            divisor <<= 1
        result = 0
        while count:
            count -= 1
            divisor >>= 1 
            if divisor <= dividend:
                #这里的移位运算是把二进制(第count+1位上的1)转换为十进制
                result += 1 << count 
                dividend -= divisor
        if sign: 
            result = -result
        #输出边界限制
        return result if -(1<<31) <= result <= (1<<31)-1 else (1<<31)-1 

分数到小数

模拟实际运算的操作。

class Solution:
    def fractionToDecimal(self, numerator: int, denominator: int) -> str:
        #处理边界条件
        sign = (numerator > 0) ^ (denominator > 0)

        numerator = abs(numerator)
        denominator = abs(denominator)
        if numerator == 0:
            return '0'
        #结果变量,先存正数部分
        res = str(numerator // denominator) + '.'
        numerator %= denominator
        numerator *= 10
        numerators = []
        #一边算小数部分,一边判断是否有重复出现
        while numerator != 0 and numerator not in numerators:
            numerators.append(numerator)
            cur = numerator // denominator
            res += str(cur)
            numerator %= denominator
            numerator *= 10
        if numerator in numerators:
            #寻找循环出现的位置
            idx = len(numerators) - numerators.index(numerator)
            res = res[:-idx] + '(' + res[-idx:] + ')'
        if res[-1] == '.':
            res =  res[:-1]
        return '-' + res if sign else  res
        

其他

两整数之和

两数异或可以模拟无进位的加法,且再进位可以模拟进位结果,重复即可,考虑python整型问题还要一点特殊处理。

class Solution:
    def getSum(self, a: int, b: int) -> int:
        MASK = 0x100000000
        # 整型最大值
        MAX_INT = 0x7FFFFFFF
        MIN_INT = MAX_INT + 1

        while b:
            carry = (a & b) << 1
            a = (a ^ b) % MASK
            b = carry % MASK
        #模拟32位整型,负数先把后32位取反,再整个数统一取反
        return a if a <= MAX_INT else ~((a % MIN_INT) ^ MAX_INT) 

逆波兰表达式求值

利用栈模拟真实操作

class Solution:
    def evalRPN(self, tokens: List[str]) -> int:
        PopList = []
        for i in tokens:
            if i in "+-*/":
                tmp = PopList.pop()
                tmp2 = PopList.pop()
                #eval把括号里面的式子当输入运行
                PopList.append(str(int(eval(tmp2+i+tmp))))
            else:
                PopList.append(i)
        return int(PopList[0])

多数元素

多数元素因为出现次数大于一半,保存当前出现次数大于其他数字之和的结果即可。

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        res = 0
        count = 0
        for num in nums:
            if count == 0:
                res = num
                count = 1
                continue
            if num != res:
                count -= 1
            else:
                count += 1
        return res   

任务调度器

我们一轮轮的安排任务,假设一轮是n+1的运行时间,一共有max_num - 1轮,然后把其他任务插入到这些空闲时间,看是否能够排列。

class Solution:
    def leastInterval(self, tasks: List[str], n: int) -> int:
        times = [0]*26;
        for c in tasks:
            times[ord(c) - ord("A")] +=1
        times.sort(reverse=True)
        #最大任务排列轮数
        max_val = times[0] - 1
        #紧密的一轮,能产生的空闲
        idle = max_val * n
        for i in range(1,26):
            if times[i] == 0:
                break
            #注意最大是max_val,最大出现次数减一
            #列优先运行的话,不管怎么样两个任务的间隔都大于n了
            idle -= min(times[i],max_val)
        #如果还有剩余,证明需要专门腾出时间等待;如果没有说明紧密的安排可以完成
        return len(tasks) + idle if idle >0 else len(tasks)

你可能感兴趣的:(LeetCode)