算法刷题笔记(19年12月)

每日算法题(12月)

作者 | Howard Wonanut

目录:

文章目录

  • 每日算法题(12月)
    • 0x01 三数之和
      • 题目描述
      • 分析与解答
      • 分析与解答2
    • 0x02 查询无效交易
      • 题目描述
    • 0x03 最接近的三数之和
      • 题目描述
    • 0x04 电话号码的字母组合
      • 题目描述
    • 0x06 验证二叉搜索树
      • 题目描述
    • 0x08 找树左下角的值
      • 题目描述
      • 我的解法
      • 其他解法
    • 0x09 交替位二进制数
      • 题目描述
      • 我的解法
      • 更高明的解法
    • 0x10 扁平化多级双向链表
      • 题目描述
    • 0x11 二叉树的最大深度
      • 题目描述
      • 说明
    • 0x12 二叉树的最小深度
      • 题目描述
      • 注意
    • 0x13 最长公共前缀[**⭐⭐⭐⭐]
      • 题目描述
      • 遇事不决,先上暴力解法
      • 看到的骚解法
      • 官方题解很有意思
    • 0x14 LeetCode 646
      • 题目描述
      • 此外这题用贪心好像更简单,运行效率也更高
    • 0x15 计数质数
      • 题目描述
    • 0x16 回文链表
      • 题目描述
      • 先放笨方法
      • 快慢指针+反转链表
        • 先复习一下反转链表的代码
        • 解法
      • 比较好嘚方法,不过空间复杂度不是1了
    • 0x17 元素和为目标值的子矩阵数量[⭐⭐⭐]
      • 题目描述
    • 0x18 元素和为目标值的子矩阵数量
      • 题目描述
    • 0x19 只出现一次的数字[⭐⭐]
      • 题目描述
    • 0x20 到达终点数字[⭐⭐⭐⭐⭐⭐]
      • 题目描述
      • 分析
    • 0x21 翻转字符串里的单词[⭐]
      • 题目描述
    • 0x22 重复的字符串[⭐⭐]
      • 题目描述
      • 分析
    • 0x23 从英文中重建数字
      • 题目描述
      • 分析
    • 0x24 回文数[⭐⭐⭐]
      • 题目描述
      • 分析
      • 进阶
    • 0x25 元素和小于等于阈值的正方形的最大边长[⭐⭐⭐]
      • 题目描述
    • 0x26 缺失数字[⭐]
      • 题目描述
      • 异或应用
    • 0x27 最大正方形[⭐]
      • 题目描述
      • 思路
    • 0x28 贴纸拼词[⭐⭐⭐⭐⭐]
      • 题目描述
    • 0x29 独一无二的出现次数[⭐]
      • 题目描述
    • 0x30 输出二叉树[⭐]
      • 题目描述
    • 0x31 最小覆盖子串[⭐⭐⭐⭐]
      • 题目描述
    • 0x32 链表随机节点[⭐⭐⭐⭐]
      • 题目描述
      • 题解
      • 题解2
      • 蓄水池抽样算法伪代码
    • 0x33 逆波兰表达式求值[⭐⭐]
      • 题目描述
      • 分析
      • 扩展:逆波兰表达式的生成过程
    • 0x34 不相交的线[⭐⭐⭐]
      • 题目描述
      • 分析
    • 0x35 连续差相同的数字[⭐⭐⭐]
      • 题目描述
      • 分析
    • 0x36 每日温度[⭐⭐⭐]
      • 题目描述
      • 分析:有点小难
    • 0x37 等价多米诺骨牌的数量[⭐⭐⭐]
      • 题目描述
      • 分析
      • 另外一种存储方法

尚未解决题目列表:

  • 0x05 四数之和
  • 0x07 树中距离之和
  • 0x20 到达终点数字
  • 0x24 回文数[⭐⭐⭐]
  • 0x25 元素和小于等于阈值的正方形的最大边长[⭐⭐⭐]
  • 0x28 贴纸拼词[⭐⭐⭐⭐⭐]
日期
2019-12-10

0x01 三数之和

双指针

题目描述

LeetCode15题 https://leetcode-cn.com/problems/3sum/

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组。

注意:答案中不可以包含重复的三元组。


分析与解答

首先对数组进行排序,时间复杂度 O ( n l o g n ) O(nlogn) O(nlogn),然后从左往右遍历排序数组。对于nums[i],设置初始指针j=0,k=len(nums)-1,如果sum = nums[i]+nums[j]+nums[k]=0则直接将其添加到结果中,i++结束当前循环;如果sum<0则j++;如果sum>0,则k–;直至i=j或者i=k,结束当前循环。整体时间复杂度 O ( n 2 ) O(n^2) O(n2)。按照上面的思路写出的代码如下

def threeSum(nums):
    """
    :type nums: List[int]
    :rtype: List[List[int]]
    """
    ans = []
    nums = sorted(nums)
    for i in range(len(nums)):
        j, k = 0, len(nums)-1
        while j < i and k > i:
            cur_sum = nums[i] + nums[j] + nums[k]
            if cur_sum == 0:
                ans.append([nums[i], nums[j], nums[k]])
                j += 1
                k -= 1
            elif cur_sum < 0:
                j += 1
            else:
                k -= 1
    return ans
threeSum([-1,0,1,2,-1,-4])
>>> [[-1, -1, 2], [0, -1, 1]]
threeSum([-2,-1,0,1,2,3])
>>> [[-1, -2, 3], [0, -2, 2], [0, -1, 1]]
threeSum([0,0,0,0,0])
>>> [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

分析与解答2

不过上面的答案不能AC,因为没有去除重复结果,如数组中的元素全为0的情况,会有多个[0,0,0]结果,如何解决重复的问题呢?把ans改成set就通过了~

def threeSum(nums):
    """
    :type nums: List[int]
    :rtype: List[List[int]]
    """
    ans = set()
    nums = sorted(nums)
    for i in range(len(nums)):
        j, k = 0, len(nums)-1
        while j < i and k > i:
            cur_sum = nums[i] + nums[j] + nums[k]
            if cur_sum == 0:
                ans.add((nums[i], nums[j], nums[k]))
                j += 1
                k -= 1
            elif cur_sum < 0:
                j += 1
            else:
                k -= 1
    return list(ans)
日期
2019-12-11

0x02 查询无效交易

双指针

题目描述

LeetCode1169题 https://leetcode-cn.com/problems/invalid-transactions/

如果出现下述两种情况,交易 可能无效:

  • 交易金额超过 ¥1000
  • 或者,它和另一个城市中同名的另一笔交易相隔不超过 60 分钟(包含 60 分钟整)

每个交易字符串 transactions[i] 由一些用逗号分隔的值组成,这些值分别表示交易的名称,时间(以分钟计),金额以及城市。

给你一份交易清单 transactions,返回可能无效的交易列表。你可以按任何顺序返回答案。


直接暴力解就好了,AC

def invalidTransactions(transactions):
    """
    :type transactions: List[str]
    :rtype: List[str]
    """
    ret = []
    t = [x.split(',') for x in transactions]
    for idx1, tran1 in enumerate(transactions):
        for idx2, tran2 in enumerate(transactions):
            if idx2 == idx1:
                continue
            if int(t[idx1][2]) > 1000:
                ret.append(tran1)
                break
            if t[idx1][0] == t[idx2][0] and t[idx1][3] != t[idx2][3] and abs(int(t[idx1][1])-int(t[idx2][1])) <= 60:
                ret.append(tran1)
                break
    return ret
invalidTransactions(["alice,20,800,mtv","alice,50,100,beijing"])
>>> ['alice,20,800,mtv', 'alice,50,100,beijing']

0x03 最接近的三数之和

双指针

题目描述

LeetCode 16题 https://leetcode-cn.com/problems/3sum-closest/

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。


下面的答案和0x02题目思路一样,使用双指针,时间复杂度 O ( n 2 ) O(n^2) O(n2)

def threeSumClosest(nums, target):
    """
    :type nums: List[int]
    :type target: int
    :rtype: int
    """
    ans = 999999
    nums = sorted(nums)
    for i in range(len(nums)):
        j, k = 0, len(nums) - 1
        while j < i and k > i:
            cur_sum = nums[i] + nums[j] + nums[k]
            if cur_sum == target:
                return target
            elif abs(cur_sum - target) < abs(ans - target):
                ans = cur_sum 
            elif cur_sum < target:
                j += 1
            else:
                k -= 1
    return ans

0x04 电话号码的字母组合

题目描述

LeetCode 17题 https://leetcode-cn.com/problems/letter-combinations-of-a-phone-number/

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
算法刷题笔记(19年12月)_第1张图片

def letterCombinations(digits):
    """
    :type digits: str
    :rtype: List[str]
    """
    if len(digits) == 0:
        return []

	char_dict = {"2":"abc", "3":"def", "4":"ghi", "5":"jkl", "6":"mno", "7":"pqrs", "8":"tuv", "9":"wxyz"}
	ans = [char for char in char_dict[digits[0]]]
	digits = digits[1:]
	for digit in digits:
	    cur_list, ans = ans, []
	    for seq in cur_list:
	        for char in char_dict[digit]:
	            ans.append(seq+char)
	return ans




## 0x05 四数之和

###  题目描述

LeetCode 18题 https://leetcode-cn.com/problems/4sum/

给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。

注意:

答案中不可以包含重复的四元组。


​```python # TODO

0x06 验证二叉搜索树

题目描述

LeetCode 98题 https://leetcode-cn.com/problems/validate-binary-search-tree/

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。


利用二叉搜索树中序遍历有序的特点进行中序遍历并检测序列是否单调上升即可 ```python # Definition for a binary tree node. # class TreeNode(object): # def __init__(self, x): # self.val = x # self.left = None # self.right = None

class Solution(object):
def init(self):
self.prev = None

def helper(self, root):
    if not root:
        return True

    left = self.helper(root.left)
    if self.prev == None:
        self.prev = root.val
    elif root.val <= self.prev:
        return False
    else:
        self.prev = root.val
    right = self.helper(root.right)
    return right and left

def isValidBST(self, root):
    """
    :type root: TreeNode
    :rtype: bool
    """
    return self.helper(root)




日期
2019-12-12

## 0x07 树中距离之和 `困难` ### 题目描述 LeetCode 834题 https://leetcode-cn.com/problems/sum-of-distances-in-tree/ 给定一个无向、连通的树。树中有 N 个标记为 0...N-1 的节点以及 N-1 条边 。 第 i 条边连接节点 edges[i][0] 和 edges[i][1] 。 返回一个表示节点 i 与其他所有节点距离之和的列表 ans。
​```python # TODO
日期
2019-12-14

0x08 找树左下角的值

简单

题目描述

LeetCode 513题 https://leetcode-cn.com/problems/find-bottom-left-tree-value/

给定一个二叉树,在树的最后一行找到最左边的值。


我的解法

最简单的层序遍历即可

def findBottomLeftValue(root):
    que1, que2 = [], []
    ans = root.val
    que1.append(root)
    while len(que1) != 0 or len(que2) != 0:
        if len(que1) != 0:
            ans = que1[0].val
        while len(que1)!= 0:
            cur_node = que1[0]
            que1.pop(0)
            if cur_node.left:
                que2.append(cur_node.left)
            if cur_node.right:
                que2.append(cur_node.right)

        if len(que2) != 0:
            ans = que2[0].val
        while len(que2)!= 0:
            cur_node = que2[0]
            que2.pop(0)
            if cur_node.left:
                que1.append(cur_node.left)
            if cur_node.right:
                que1.append(cur_node.right)
    return ans

其他解法

使用DFS

日期
2019-12-15

0x09 交替位二进制数

简单

题目描述

LeetCode 693题 https://leetcode-cn.com/problems/binary-number-with-alternating-bits/

给定一个正整数,检查他是否为交替位二进制数:换句话说,就是他的二进制数相邻的两个位数永不相等。


我的解法

逐位右移检查

def hasAlternatingBits(n):
    """
    :type n: int
    :rtype: bool
    """
    flag = n & 0x1
    while n != 0:
        if n & 0x1 != flag:
            return False
        n = n >> 1
        flag = not flag
    return True

更高明的解法

将n和右移一位的n做异或,检查所有位是否都为1(异或:不同为1)

class Solution(object):
    def hasAlternatingBits(self, n):
        """
        :type n: int
        :rtype: bool
        """
        temp = n^(n>>1)
        return temp&(temp+1) == 0
日期
2019-12-16

0x10 扁平化多级双向链表

中等

题目描述

LeetCode 430题 https://leetcode-cn.com/problems/flatten-a-multilevel-doubly-linked-list/submissions/

您将获得一个双向链表,除了下一个和前一个指针之外,它还有一个子指针,可能指向单独的双向链表。这些子列表可能有一个或多个自己的子项,依此类推,生成多级数据结构,如下面的示例所示。

扁平化列表,使所有结点出现在单级双链表中。您将获得列表第一级的头部。

"""
# Definition for a Node.
class Node(object):
    def __init__(self, val, prev, next, child):
        self.val = val
        self.prev = prev
        self.next = next
        self.child = child
"""
class Solution(object):
    def dfs(self, head):
        if head == None:
            return None
        head_copy = head
        while head:
            if head.child != None:
                temp_next = head.next
                temp_flat = self.dfs(head.child)
                head.child = None
                temp_flat.prev = head
                head.next = temp_flat
                while temp_flat.next:
                    temp_flat = temp_flat.next
                if temp_next:
                    temp_flat.next = temp_next
                    temp_next.prev = temp_flat
                head = temp_next
            else:
                head = head.next
        return head_copy

	def flatten(self, head):
	    """
	    :type head: Node
	    :rtype: Node
	    """
	    return self.dfs(head)

2019-12-17

0x11 二叉树的最大深度

简单

题目描述

LeetCode 104题 https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/

给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。


说明

像此类二叉树的问题,大部分都可以直接使用递归解决,但是递归一定要写的漂亮,简洁!

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):
    def maxDepth(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        return 0 if root == None else max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1

0x12 二叉树的最小深度

简单

题目描述

LeetCode 111题 https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/

给定一个二叉树,找出其最小深度。

最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明: 叶子节点是指没有子节点的节点。


注意

这道题和上一题很像,很容易进坑写成这样:

return 0 if root == None else min(self.minDepth(root.left), self.minDepth(root.right)) + 1

但是最小深度定义为:根节点到最近叶子节点,也就是说只有到达叶子节点(左右子树均为空)才算到底!

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):
    def minDepth(self, root):
        """
        :type root: TreeNode
        :rtype: int
        """
        if root == None:
            return 0 
        
        if root.left and root.right:
            return min(self.minDepth(root.left), self.minDepth(root.right)) + 1
        elif root.left and not root.right:
            return self.minDepth(root.left) + 1
        elif root.right and not root.left:
            return self.minDepth(root.right) + 1
        else:
            return 1

0x13 最长公共前缀[**⭐⭐⭐⭐]

简单

题目描述

LeetCode 14题 https://leetcode-cn.com/problems/longest-common-prefix/

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 “”。


遇事不决,先上暴力解法

class Solution(object):
    def longestCommonPrefix(self, strs):
        """
        :type strs: List[str]
        :rtype: str
        """
        ans = ""
        if len(strs) == 0:
            return ans
            
        min_len = 9999
        for st in strs:
            min_len = min(min_len, len(st))
        for i in range(min_len):
            ch = strs[0][i]
            for j in range(1,len(strs)):
                if strs[j][i] != ch:
                    return ans
            ans += ch
        return ans

看到的骚解法

利用python的max()和min(),在Python里字符串是可以比较的,按照ascII值排,举例abb, aba,abac,最大为abb,最小为aba。所以只需要比较最大最小的公共前缀就是整个数组的公共前缀

class Solution(object):
    def longestCommonPrefix(self, strs):
        """
        :type strs: List[str]
        :rtype: str
        """
        if not strs:
            return ""
        s1 = min(strs)
        s2 = max(strs)
        for i,x in enumerate(s1):
            if x != s2[i]:
                return s2[:i]
        return s1

官方题解很有意思

https://leetcode-cn.com/problems/longest-common-prefix/solution/zui-chang-gong-gong-qian-zhui-by-leetcode/

0x14 LeetCode 646

LeetCode 模拟题

题目描述

给出 n 个数对。 在每一个数对中,第一个数字总是比第二个数字小。

现在,我们定义一种跟随关系,当且仅当 b < c 时,数对(c, d) 才可以跟在 (a, b) 后面。我们用这种形式来构造一个数对链。

给定一个对数集合,找出能够形成的最长数对链的长度。你不需要用到所有的数对,你可以以任何顺序选择其中的一些数对来构造。

示例 :

输入: [[1,2], [2,3], [3,4]]

输出: 2

解释: 最长的数对链是 [1,2] -> [3,4]

注意:

给出数对的个数在 [1, 1000] 范围内。


### 动态规划解
class Solution(object):
    def findLongestChain(self, pairs):
        """
        :type pairs: List[List[int]]
        :rtype: int
        """
        pairs.sort(key=lambda pair: pair[0])
        dp = [1 for i in range(len(pairs) + 1)]
        for i in range(1, len(pairs) + 1):
            cur_max = 0
            for j in range(1, i+1):
                if pairs[i-1][0] > pairs[j-1][1]:
                    cur_max = max(cur_max, dp[j] + 1)
                else:
                    cur_max = max(cur_max, dp[j])
            dp[i] = cur_max
        return dp[len(pairs)]

此外这题用贪心好像更简单,运行效率也更高

对pair[1]从小到大排序

class Solution(object):
    def findLongestChain(self, pairs):
        """
        :type pairs: List[List[int]]
        :rtype: int
        """
        pairs.sort(key=lambda pair: pair[1])
        end, cnt = -sys.maxsize, 0
        for pair in pairs:
            if pair[0] > end:
                cnt += 1
                end = pair[1]
        return cnt

0x15 计数质数

质数判断

LeetCode204: https://leetcode-cn.com/problems/count-primes/

题目描述

统计所有小于非负整数 n 的质数的数量。


### 最好的求质数算法:埃拉托斯特尼筛法 性能不行,有待提升
class Solution(object):
    def countPrimes(self, n):
        """
        :type n: int
        :rtype: int
        """
        arr = [True for i in range(n)]
        ans = 0
        for i in range(2, n):
            if arr[i]:
                ans += 1
                for j in range(i*i, n, i):
                    arr[j] = False
        
        return ans

日期
2019-12-19

0x16 回文链表

LeetCode234: https://leetcode-cn.com/problems/palindrome-linked-list/

题目描述

请判断一个链表是否为回文链表。

示例 1:

输入: 1->2

输出: false

复杂度要求: O(n) 时间复杂度和 O(1) 空间复杂度

先放笨方法

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def isPalindrome(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
        arr = []
        while head:
            arr.append(head.val)
            head = head.next
        i, j = 0, len(arr) - 1
        while i < j:
            if arr[i] != arr[j]:
                return False
            i += 1
            j -= 1
        return True

快慢指针+反转链表

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( 1 ) O(1) O(1)

先复习一下反转链表的代码

class ListNode(object):
    def __init__(self, x):
        self.val = x
        self.next = None

def print_list(p, c):
    arr = []
    prev, cur = p, c
    while prev:
        arr.insert(0, prev.val)
        prev = prev.next
    arr.append('prev | cur')
    while cur:
        arr.append(cur.val)
        cur = cur.next
    print(arr)
    
def reverse_linklist(head):
    prev, cur = None, head
    print_list(prev, cur)
    while cur:
        cur.next, cur, prev = prev, cur.next, cur
        print_list(prev, cur)
    return prev

node1 = ListNode(1)
node2 = ListNode(2)
node3 = ListNode(3)
node4 = ListNode(4)
node5 = ListNode(5)
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
reverse_linklist(node1)

['prev | cur', 1, 2, 3, 4, 5]
[1, 'prev | cur', 2, 3, 4, 5]
[1, 2, 'prev | cur', 3, 4, 5]
[1, 2, 3, 'prev | cur', 4, 5]
[1, 2, 3, 4, 'prev | cur', 5]
[1, 2, 3, 4, 5, 'prev | cur']

解法

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def isPalindrome(self, head):
        if not head or not head.next:
            return True
        fast, cur, prev = head.next, head, None
        while fast and fast.next:
            fast = fast.next.next
            cur.next, cur, prev = prev, cur.next, cur
        cur.next, cur, prev = prev, cur.next, cur
        if fast:
            left, right = prev, cur
        else:
            left, right = prev.next, cur
        while left and right:
            if left.val != right.val:
                return False
            left = left.next
            right = right.next
        return True

比较好嘚方法,不过空间复杂度不是1了

class Solution(object):
    def isPalindrome(self, head):
        arr = []
        while head:
            arr.append(head.val)
            head = head.next
        return arr == arr[::-1]

0x17 元素和为目标值的子矩阵数量[⭐⭐⭐]

困难

LeetCode1074: https://leetcode-cn.com/problems/number-of-submatrices-that-sum-to-target/

题目描述

给出矩阵 matrix 和目标值 target,返回元素总和等于目标值的非空子矩阵的数量。

子矩阵 x1, y1, x2, y2 是满足 x1 <= x <= x2 且 y1 <= y <= y2 的所有单元 matrix[x][y] 的集合。

如果 (x1, y1, x2, y2) 和 (x1’, y1’, x2’, y2’) 两个子矩阵中部分坐标不同(如:x1 != x1’),那么这两个子矩阵也不同。

# TODO
日期
2019-12-20

0x18 元素和为目标值的子矩阵数量

中等

LeetCode1138: https://leetcode-cn.com/problems/alphabet-board-path/

题目描述

我们从一块字母板上的位置 (0, 0) 出发,该坐标对应的字符为 board[0][0]。

在本题里,字母板为board = [“abcde”, “fghij”, “klmno”, “pqrst”, “uvwxy”, “z”].

我们可以按下面的指令规则行动:

如果方格存在,‘U’ 意味着将我们的位置上移一行;

如果方格存在,‘D’ 意味着将我们的位置下移一行;

如果方格存在,‘L’ 意味着将我们的位置左移一列;

如果方格存在,‘R’ 意味着将我们的位置右移一列;

‘!’ 会把在我们当前位置 (r, c) 的字符 board[r][c] 添加到答案中。

返回指令序列,用最小的行动次数让答案和目标 target 相同。你可以返回任何达成目标的路径。


### 思路 因为给出的字母板本身是一个矩形比较规整,可以使用除法和取余定位下一个字母的位置。由于字母'z'的位置比较特殊,每次从前一个字母移动到下一个字母的时候需要按照”左上右下“的顺序更新位置即可。

注意:

字符转ASCII码:ord(‘a’)

ord('a')
>>> 97

class Solution(object):
    def alphabetBoardPath(self, target):
        """
        :type target: str
        :rtype: str
        """
        i, j = 0, 0
        ans = ""
        for ch in list(target):
            row, col = int(ord(ch) - ord('a')) // 5, int(ord(ch) - ord('a')) % 5
            while j > col:
                j -= 1
                ans += "L"
            while i > row:
                i -= 1
                ans += "U"
            while j < col:
                j += 1
                ans += "R"
            while i < row:
                i += 1
                ans += "D"
            ans += '!'
        return ans


0x19 只出现一次的数字[⭐⭐]

简单 异或

LeetCode136: https://leetcode-cn.com/problems/single-number/

题目描述

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?


### 异或的应用啊
class Solution(object):
    def singleNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        ans = 0
        for num in nums:
            ans ^= num
        return ans

日期
2019-12-21

0x20 到达终点数字[⭐⭐⭐⭐⭐⭐]

中等 数学

LeetCode754: https://leetcode-cn.com/problems/reach-a-number/

题目描述

在一根无限长的数轴上,你站在0的位置。终点在target的位置。

每次你可以选择向左或向右移动。第 n 次移动(从 1 开始),可以走 n 步。

返回到达终点需要的最小移动次数。

分析

这道题可以说是极其恶心了,好好学习

0x21 翻转字符串里的单词[⭐]

字符翻转

LeetCode151: https://leetcode-cn.com/problems/reverse-words-in-a-string/

题目描述

给定一个字符串,逐个翻转字符串中的每个单词。


### 解法 虽然解出来了,但是性能不是很好 > Python `strip()` 方法用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。 > > 注意:该方法只能删除开头或是结尾的字符,不能删除中间部分的字符。
class Solution(object):
    def reverseWords(self, s):
        """
        :type s: str
        :rtype: str
        """
        return ' '.join([x.strip() for x in s.split()][::-1])

日期
2019-12-22

0x22 重复的字符串[⭐⭐]

简单

LeetCode459 https://leetcode-cn.com/problems/repeated-substring-pattern/

题目描述

给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。

分析

假设母串S是由子串s重复N次而成, 则 S+S则有子串s重复2N次, 现在S=Ns, S+S=2Ns 因此S在(S+S)[1:-1]中必出现一次以上。

class Solution(object):
    def repeatedSubstringPattern(self, s):
        return s in (s+s)[1:-1]

类似的操作还有:

  • 检查字符串s是否全为1:return s & (s >> 1) == s
  • 检查字符串s是否为回文串:retuan s == s[::-1]
日期
2019-12-23

0x23 从英文中重建数字

中等 数学

LeetCode423: https://leetcode-cn.com/problems/reconstruct-original-digits-from-english/

题目描述

给定一个非空字符串,其中包含字母顺序打乱的英文单词表示的数字0-9。按升序输出原始的数字。

注意:

输入只包含小写英文字母。

输入保证合法并可以转换为原始的数字,这意味着像 “abc” 或 “zerone” 的输入是不允许的。

输入字符串的长度小于 50,000。

分析

找到每个数字的英文单词中的独特字母,即可

class Solution(object):
    def originalDigits(self, s):
        n0 = s.count('z')
        n2 = s.count('w')
        n8 = s.count('g')
        n6 = s.count('x')
        n3 = s.count('t') - n2 - n8
        n4 = s.count('r') - n3 - n0
        n7 = s.count('s') - n6
        n1 = s.count('o') - n4 - n2 - n0
        n5 = s.count('v') - n7
        n9 = s.count('i') - n8 - n6 - n5
        
        ns = (n0,n1,n2,n3,n4,n5,n6,n7,n8,n9)
        return "".join((str(i)*n for i, n in enumerate(ns)))

0x24 回文数[⭐⭐⭐]

简单 回文数

LeetCode9: https://leetcode-cn.com/problems/palindrome-number/

题目描述

判断一个整数是否是回文数。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。

分析

虽然这是道简单题,可以用一行代码结束战斗,但是如果不把整数看作字符串如何解决这个问题呢?不简单了

class Solution(object):
    def isPalindrome(self, x):
        """
        :type x: int
        :rtype: bool
        """
        return str(x) == str(x)[::-1]

进阶

你能不将整数转为字符串来解决这个问题吗?

0x25 元素和小于等于阈值的正方形的最大边长[⭐⭐⭐]

中等

LeetCode1292: https://leetcode-cn.com/problems/maximum-side-length-of-a-square-with-sum-less-than-or-equal-to-threshold/

题目描述

给你一个大小为 m x n 的矩阵 mat 和一个整数阈值 threshold。

请你返回元素总和小于或等于阈值的正方形区域的最大边长;如果没有这样的正方形区域,则返回 0 。

日期
2019-12-24

0x26 缺失数字[⭐]

简单 异或

LeetCode268: https://leetcode-cn.com/problems/missing-number/submissions/

题目描述

给定一个包含 0, 1, 2, …, n 中 n 个数的序列,找出 0 … n 中没有出现在序列中的那个数。

异或应用

class Solution(object):
    def missingNumber(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        ans = len(nums)
        for idx, num in enumerate(nums):
            ans ^= idx
            ans ^= num
        return ans

0x27 最大正方形[⭐]

中等 动态规划

LeetCode221: https://leetcode-cn.com/problems/maximal-square/

题目描述

在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积。

思路

dp[i][j]代表从(0,0)到(i,j)的最大正方形大小,

初始 dp[i][j]= matrix[i][j] == ‘1’?1:0

一般情况:dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) + 1也就是当前位置是1且上边,左边,左上位置都是1,就能构成更大的矩形

class Solution(object):
    def maximalSquare(self, matrix):
        """
        :type matrix: List[List[str]]
        :rtype: int
        """
        if len(matrix) == 0 or len(matrix[0]) == 0:
            return 0

        ans = 0
        m, n = len(matrix), len(matrix[0])
        dp = [[int(matrix[i][j]) for j in range(n)] for i in range(m)]
        for i in range(m):
            for j in range(n):
                if i > 0 and j > 0 and dp[i][j]:
                    dp[i][j] = min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1]) + 1
                ans = max(ans, dp[i][j])
        return ans * ans

日期
2019-12-25

0x28 贴纸拼词[⭐⭐⭐⭐⭐]

困难 动态规划

LeetCode691: https://leetcode-cn.com/problems/stickers-to-spell-word/

题目描述

我们给出了 N 种不同类型的贴纸。每个贴纸上都有一个小写的英文单词。

你希望从自己的贴纸集合中裁剪单个字母并重新排列它们,从而拼写出给定的目标字符串 target。

如果你愿意的话,你可以不止一次地使用每一张贴纸,而且每一张贴纸的数量都是无限的。

拼出目标 target 所需的最小贴纸数量是多少?如果任务不可能,则返回 -1。

def minStickers(stickers, target):
    ch_dict = {}
    for index, sticker in enumerate(stickers):
        for ch in sticker:
            if ch not in ch_dict:
                ch_dict[ch] = []
            ch_dict[ch].append(index)
    for ch in ch_dict:
        if len(ch_dict[ch]) == 1:
            idx = ch_dict[ch][0]
            sticker = stickers[idx]
            for s in sticker:
                ch_dict[s]

minStickers(["with", "example", "science"],"target")

日期
2019-12-26

0x29 独一无二的出现次数[⭐]

简单

LeetCode1207: https://leetcode-cn.com/problems/unique-number-of-occurrences/submissions/

题目描述

给你一个整数数组 arr,请你帮忙统计数组中每个数的出现次数。

如果每个数的出现次数都是独一无二的,就返回 true;否则返回 false。

class Solution(object):
    def uniqueOccurrences(self, arr):
        """
        :type arr: List[int]
        :rtype: bool
        """
        a = [arr.count(num) for num in set(arr)]
        return len(set(a)) == len(set(arr))

日期
2019-12-27

0x30 输出二叉树[⭐]

中等 递归

LeetCode655: https://leetcode-cn.com/problems/print-binary-tree/

题目描述

在一个 m*n 的二维字符串数组中输出二叉树,并遵守以下规则:

行数 m 应当等于给定二叉树的高度。

列数 n 应当总是奇数。

根节点的值(以字符串格式给出)应当放在可放置的第一行正中间。根节点所在的行与列会将剩余空间划分为两部分(左下部分和右下部分)。你应该将左子树输出在左下部分,右子树输出在右下部分。左下和右下部分应当有相同的大小。即使一个子树为空而另一个非空,你不需要为空的子树输出任何东西,但仍需要为另一个子树留出足够的空间。然而,如果两个子树都为空则不需要为它们留出任何空间。

每个未使用的空间应包含一个空的字符串""。

使用相同的规则输出子树。


# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):
    def getHeight(self, root):
        if not root:
            return 0
        return max(self.getHeight(root.left), self.getHeight(root.right)) + 1

    def fillTree(self, root, ans, row, left, right):
        if root:
            mid = left + (right - left) / 2
            ans[row][mid] = str(root.val)
            self.fillTree(root.left, ans, row + 1, left, mid - 1)
            self.fillTree(root.right, ans, row + 1, mid + 1, right)

    def printTree(self, root):
        """
        :type root: TreeNode
        :rtype: List[List[str]]
        """
        tree_height = self.getHeight(root)
        tree_width = 2**tree_height - 1
        ans = [["" for j in range(tree_width)] for i in range(tree_height)]
        self.fillTree(root, ans, 0, 0, tree_width - 1)
        return ans


0x31 最小覆盖子串[⭐⭐⭐⭐]

困难 快慢指针

LeetCode76: https://leetcode-cn.com/problems/minimum-window-substring/

题目描述

给你一个字符串 S、一个字符串 T,请在字符串 S 里面找出:包含 T 所有字母的最小子串。


### 分析(重新整理一遍) 很容易想到解法,快慢指针,但是实现的时候会有很多细节问题。
class Solution(object):
    def minWindow(self, s, t):
        from collections import defaultdict
        lookup = defaultdict(int)
        for c in t:
            lookup[c] += 1
        start = 0
        end = 0
        min_len = float("inf")
        counter = len(t)
        res = ""
        while end < len(s):
            if lookup[s[end]] > 0:
                counter -= 1
            lookup[s[end]] -= 1
            end += 1
            while counter == 0:
                if min_len > end - start:
                    min_len = end - start
                    res = s[start:end]
                if lookup[s[start]] == 0:
                    counter += 1
                lookup[s[start]] += 1
                start += 1
        return res

日期
2019-12-28

0x32 链表随机节点[⭐⭐⭐⭐]

中等 蓄水池抽样

LeetCode382: https://leetcode-cn.com/problems/linked-list-random-node/

题目描述

给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。

进阶:

如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现?


题解

乍一看这道题很简单:首先把单链表遍历一遍存起来,然后调用getRandom函数的时候随机生成一个索引返回即可,其解法如下:

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):

    def __init__(self, head):
        """
        @param head The linked list's head.
        Note that the head is guaranteed to be not null, so it contains at least one node.
        :type head: ListNodeQ
        """
        self.arr = []
        while head:
            self.arr.append(head.val)
            head = head.next
        

    def getRandom(self):
        """
        Returns a random node's value.
        :rtype: int
        """
        return random.choice(self.arr)
        

# Your Solution object will be instantiated and called as such:
# obj = Solution(head)
# param_1 = obj.getRandom()

题解2

但是如果用上面的方法不能满足进阶的要求:首先,链表长度未知,链表很大,可能无法一次全部装入内存,因此上面的方法无效了。其次要求使用常数级空间复杂度实现,也是直接否决了上面的方法。我百思不得其解,最终发现需要使用到蓄水池抽样算法,并且被这个算法的巧妙深深折服了。本题算得上12月题目里面很有意思的一道题了,解法如下:

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):

    def __init__(self, head):
        """
        @param head The linked list's head.
        Note that the head is guaranteed to be not null, so it contains at least one node.
        :type head: ListNodeQ
        """
        self.head = head
        

    def getRandom(self):
        """
        Returns a random node's value.
        :rtype: int
        """
        ans, node, index = self.head, self.head.next, 1
        while node:
            if random.randint(0, index) == 0:
                ans = node
            node = node.next
            index += 1
        return ans.val
        


# Your Solution object will be instantiated and called as such:
# obj = Solution(head)
# param_1 = obj.getRandom()

蓄水池抽样算法伪代码

# 问题背景:假如现在有海量数据,无法一次将其读入内存,
# 数据长度也不可得知,暂且用N表示其长度。
# 现在需要从中抽样k个数据,要求抽样得到的k个数据,每个数据被选中的概率均为k/N.

def sample(Data, k):
    # 先将Data中的前k个数据放入蓄水池pool
    pool = [Data[i] for i in range(k)]
    
    # 从第k+1个数据开始遍历(由于我们是从0开始的,因此索引为k的数就是第k+1个数了)
    for i in range(k, len(Data)):
        # 生成一个随机数,范围为[0,i]
        r = random.randint(0, i)
        
        # 如果生成的随机数落在[0,k)之间,则将当前位置的数据替换掉pool中索引为r+1的数
        if r < k:
            pool[r] = Data[i]
    
    # 返回pool即为所求
    return pool
    

0x33 逆波兰表达式求值[⭐⭐]

中等 逆波兰表达式

LeetCode150: https://leetcode-cn.com/problems/evaluate-reverse-polish-notation/

题目描述

根据逆波兰表示法,求表达式的值。

有效的运算符包括 +, -, *, / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

说明:

整数除法只保留整数部分。

给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。


分析

1.逆波兰表达式求解,定义一个栈辅助计算;

2.当遇到运算符"+"、"-"、"*"、"/"时,从栈中pop出两个数字计算,否则将数字入栈;

class Solution(object):
    def evalRPN(self, tokens):
        """
        :type tokens: List[str]
        :rtype: int
        """
        stack = []
        for token in tokens:
            if token in "+-*/":
                b = stack.pop()
                a = stack.pop()
                if token == '+':
                    stack.append(a+b)
                elif token == '-':
                    stack.append(a-b)
                elif token == '*':
                    stack.append(a*b)
                else:
                    if a*b < 0:
                        stack.append(abs(a)//abs(b)*(-1))
                    else:
                        stack.append(a//b)
            else:
                stack.append(int(token))
        return stack[-1]

扩展:逆波兰表达式的生成过程

1、首先构造一个运算符栈,此运算符在栈内遵循越往栈顶优先级越高的原则。

2、读入一个用中缀表示的简单算术表达式,为方便起见,设该简单算术表达式的右端多加上了优先级最低的特殊符号“#”。

3、从左至右扫描该算术表达式,从第一个字符开始判断,如果该字符是数字,则分析到该数字串的结束并将该数字串直接输出。

4、如果不是数字,该字符则是运算符,此时需比较优先关系。
具体做法是:将该字符与运算符栈顶的运算符的优先关系相比较。如果该字符优先关系高于此运算符栈顶的运算符,则将该运算符入栈。若不是的话,则将栈顶的运算符从栈中弹出,直到栈项运算符的优先级低于当前运算符,将该字符入栈。

5、重复步骤1~2,直至扫描完整个简单算术表达式,确定所有字符都得到正确处理,便可以将中缀式表示的简单算术表达式转化为逆波兰表示的简单算术表达式。

日期
2019-12-29

0x34 不相交的线[⭐⭐⭐]

中等 动态规划

LeetCode1035: https://leetcode-cn.com/problems/uncrossed-lines/

题目描述

我们在两条独立的水平线上按给定的顺序写下 A 和 B 中的整数。

现在,我们可以绘制一些连接两个数字 A[i] 和 B[j] 的直线,只要 A[i] == B[j],且我们绘制的直线不与任何其他连线(非水平线)相交。

以这种方法绘制线条,并返回我们可以绘制的最大连线数。


分析

当我自己从头到尾把这道题轻松的写出来,且一次提交直接通过了之后,我发现:这一个多月的学习真的没有白费!!!
动态规划即可解决,状态转移方程:

  • dp[i][j] = dp[i-1][j-1] + 1 if A[i] == B[j]
  • dp[i][j] = max(dp[i][j-1], dp[i-1][j])
class Solution(object):
    def maxUncrossedLines(self, A, B):
        """
        :type A: List[int]
        :type B: List[int]
        :rtype: int
        """
        m, n = len(A), len(B)
        dp = [[0 for j in range(n+1)] for i in range(m+1)]
        for i in range(1, m+1):
            for j in range(1, n+1):
                if A[i - 1] == B[j - 1]:
                    dp[i][j] = dp[i - 1][j - 1] + 1
                else:
                    dp[i][j] = max(dp[i][j - 1], dp[i - 1][j])
        return dp[m][n]

日期
2019-12-30

0x35 连续差相同的数字[⭐⭐⭐]

中等 DFS

LeetCode967: https://leetcode-cn.com/problems/numbers-with-same-consecutive-differences/

题目描述

返回所有长度为 N 且满足其每两个连续位上的数字之间的差的绝对值为 K 的非负整数。

请注意,除了数字 0 本身之外,答案中的每个数字都不能有前导零。例如,01 因为有一个前导零,所以是无效的;但 0 是有效的。

你可以按任何顺序返回答案。


分析

我是用深度优先搜索dfs首先,代码写的有写丑陋,发现有人使用迭代的方法实现也挺好的。

class Solution(object):
    def __init__(self):
        self.ans = []

    def helper(self, N, k, cur):
        if len(cur) == N:
            self.ans.append(int(cur))
            return
        
        if N == 1:
            self.ans = [i for i in range(10)]
            return

        if len(cur) == 0:
            for i in range(1, 10):
                self.helper(N, k, str(i))
        else:
            prev = cur[-1]
            for i in range(0, 10):
                if abs(i - int(prev)) == k:
                    self.helper(N, k, cur+str(i))
            

    def numsSameConsecDiff(self, N, k):
        """
        :type N: int
        :type K: int
        :rtype: List[int]
        """
        self.helper(N, k, "")
        return self.ans

# TODO 使用dfs的方法实现
# 参考:https://leetcode-cn.com/problems/numbers-with-same-consecutive-differences/solution/zhi-jie-sheng-cheng-fa-by-amchor-3/

日期
2019-12-31

0x36 每日温度[⭐⭐⭐]

中等

LeetCode739: https://leetcode-cn.com/problems/daily-temperatures/

题目描述

根据每日 气温 列表,请重新生成一个列表,对应位置的输入是你需要再等待多久温度才会升高超过该日的天数。如果之后都不会升高,请在该位置用 0 来代替。

例如,给定一个列表 temperatures = [73, 74, 75, 71, 69, 72, 76, 73],你的输出应该是 [1, 1, 4, 2, 1, 1, 0, 0]。

提示:气温 列表长度的范围是 [1, 30000]。每个气温的值的均为华氏度,都是在 [30, 100] 范围内的整数。


分析:有点小难

这道题暴力无法通过,如何使用栈来完成还是挺难想到的,反正我没想到。

参考题解:https://leetcode-cn.com/problems/daily-temperatures/solution/cheng-xu-yuan-de-zi-wo-xiu-yang-739-daily-temperat/

class Solution(object):
    def dailyTemperatures(self, T):
        """
        :type T: List[int]
        :rtype: List[int]
        """
        res = [0 for i in range(len(T))]
        stack = []
        for i in range(len(T)):
            while len(stack) and T[i] > T[stack[-1]]:
                res[stack[-1]] = i - stack[-1]
                stack.pop()
            stack.append(i)
        return res

0x37 等价多米诺骨牌的数量[⭐⭐⭐]

简单

LeetCode1128: https://leetcode-cn.com/problems/number-of-equivalent-domino-pairs/

题目描述

给你一个由一些多米诺骨牌组成的列表 dominoes。

如果其中某一张多米诺骨牌可以通过旋转 0 度或 180 度得到另一张多米诺骨牌,我们就认为这两张牌是等价的。

形式上,dominoes[i] = [a, b] 和 dominoes[j] = [c, d] 等价的前提是 ac 且 bd,或是 ad 且 bc。

在 0 <= i < j < dominoes.length 的前提下,找出满足 dominoes[i] 和 dominoes[j] 等价的骨牌对 (i, j) 的数量。


分析

很容易想到建立哈希索引

class Solution(object):
    def numEquivDominoPairs(self, dominoes):
        """
        :type dominoes: List[List[int]]
        :rtype: int
        """
        dic = {i:[] for i in range(1,10)}
        for domino in dominoes:
            mini, maxi = min(domino), max(domino)
            dic[mini].append(maxi)
        
        print(dic)
        ans = 0
        for d in dic:
            for i in range(1,10):
                count = dic[d].count(i)
                count = count * (count - 1) // 2
                ans += count
        return ans


另外一种存储方法

将每个多米诺加密成一个两位数后建立hash,不过不知道为什么这个运行时间比上面的还要长

class Solution(object):
    def numEquivDominoPairs(self, dominoes):
        """
        :type dominoes: List[List[int]]
        :rtype: int
        """
        dic = collections.defaultdict(int)   
        for i,j in dominoes:
            cur = 10 * i + j if i < j else 10 * j+ i
            dic[cur] += 1
        
        ans = 0
        for d in dic.values():
            ans += d * (d - 1) / 2
        return ans

你可能感兴趣的:(算法刷题)