leetcode热题系列17

面试题 01.07. 旋转矩阵

给你一幅由 N × N 矩阵表示的图像,其中每个像素的大小为 4 字节。请你设计一种算法,将图像旋转 90 度。
不占用额外内存空间能否做到?

示例 1:
给定 matrix =
[
[1,2,3],
[4,5,6],
[7,8,9]
],

原地旋转输入矩阵,使其变为:
[
[7,4,1],
[8,5,2],
[9,6,3]
]

示例 2:
给定 matrix =
[
[ 5, 1, 9,11],
[ 2, 4, 8,10],
[13, 3, 6, 7],
[15,14,12,16]
],

原地旋转输入矩阵,使其变为:
[
[15,13, 2, 5],
[14, 3, 4, 1],
[12, 6, 8, 9],
[16, 7,10,11]
]

class Solution(object):
    def rotate(self, matrix):
        """
        :type matrix: List[List[int]]
        :rtype: None Do not return anything, modify matrix in-place instead.
        """
        n = len(matrix)
        #上下镜面翻转
        for i in range(n//2):
            for j in range(n):
                matrix[i][j],matrix[n-1-i][j] = matrix[n-1-i][j],matrix[i][j]
        #主对角线翻转
        for i in range(n):
            for j in range(i):
                matrix[j][i],matrix[i][j] = matrix[i][j],matrix[j][i]

36. 有效的数独

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。
在这里插入图片描述
上图是一个部分填充的有效的数独。

数独部分空格内已填入了数字,空白格用 ‘.’ 表示。

示例 1:

输入:
[
  ["5","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出: true


输入:
[
  ["8","3",".",".","7",".",".",".","."],
  ["6",".",".","1","9","5",".",".","."],
  [".","9","8",".",".",".",".","6","."],
  ["8",".",".",".","6",".",".",".","3"],
  ["4",".",".","8",".","3",".",".","1"],
  ["7",".",".",".","2",".",".",".","6"],
  [".","6",".",".",".",".","2","8","."],
  [".",".",".","4","1","9",".",".","5"],
  [".",".",".",".","8",".",".","7","9"]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
     但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。

说明:
一个有效的数独(部分已被填充)不一定是可解的。
只需要根据以上规则,验证已经填入的数字是否有效即可。
给定数独序列只包含数字 1-9 和字符 ‘.’ 。
给定数独永远是 9x9 形式的。

思路:迭代,哈希表

先看数独有效的规则:

数字 1-9 在每一行只能出现一次。
数字 1-9 在每一列只能出现一次。
数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

那么我们可以根据这个规则,遍历给定的数独三次,分别去判断每行,每列,每个 3x3 宫内是否有重复的数字。如果有重复的数字,可以直接返回 False;否则,直至遍历结束,返回 True。

但是在这里,可以考虑遍历一次,同时去判断每行,每列,每个 3x3 宫内是否有重复的数字。

在这里,我们使用哈希表去存储每个数字出现的次数(包括每行,每列,每个 3x3 宫)。在这里主要的问题是:因为数独是 9x9 的,每行每列很容易枚举,但是枚举每个 3x3 宫格会有一下麻烦。

在这里,枚举每个 3x3 宫,使用以下的式子:

box_index = (row // 3) * 3 + col // 3
在这里插入图片描述
根据上面的图示,我们稍微说明一下,如何得到上面的式子。

先以列的角度看,标记为 0, 1, 2 的 3 个 3x3 宫格。可以看出这里每个 3x3 宫格的索引更多取决于列索引。

先看标记为 0 的 3x3 宫格,这里的列索引分别为 [0, 1, 2], 1 的 3x3 宫格,这里的列索引分别为 [3, 4, 5], 2 的 3x3 宫格,这里的列索引分别为 [6, 7, 8],也就是 col // 3。

再以行的角度去看,标记为 0, 3, 6 的 3 个 3x3 宫格的索引跨度为 3,也就是说标记索引为 0 宫格如这样得到的,0 * 3 + col // 3,标记为 3 的宫格为:1 * 3 + col // 3。

在这里,上面公式的 [0, 1] 求得的公式与前面的类似,就是由 row // 3 获得。那么最终的式子为:

(row // 3) * 3 + col // 3

这里稍微提一下,题目中说明,有效数独并不一定可解,这里不考虑是否可解,要求验证的是已经填入的数字是否有效。

具体的算法思路:

遍历数独,检查数字在每行,每列,每个 3x3 宫格中是否有重复:
    如果重复,返回 False
    否则,记录该数字,往下继续遍历
最终遍历结束,返回 True。
class Solution:
    def isValidSudoku(self, board: List[List[str]]) -> bool:
        # 定义哈希表存储数字出现在每行,每列,每个 3x3 的宫格的次数
        # 题目中说明,给定的数独一定是 9x9 的
        rows = [{} for _ in range(9)]
        cols = [{} for _ in range(9)]
        boxes = [{} for _ in range(9)]

        for i in range(9):
            for j in range(9):
                # 题目中要验证是已经填入的数字,如果出现 '.' 表示留空,不作处理
                if board[i][j] != '.':
                    # 取出数字,存入哈希表中(需要转换,给定的二维数组数字是字符串格式)
                    num = int(board[i][j])

                    rows[i][num] = rows[i].get(num, 0) + 1
                    cols[j][num] = cols[j].get(num, 0) + 1
                    # 代入枚举 3x3 宫格的公式
                    boxes[(i // 3) * 3 + j // 3][num] = boxes[(i // 3) * 3 + j // 3].get(num, 0) + 1

                    # 行,列,3x3 宫格,任意一个如果出现数字重复,则返回 False
                    if rows[i][num] > 1 or cols[j][num] > 1 or boxes[(i // 3) * 3 + j // 3][num] > 1:
                        return False
        return True


60. 第k个排列

第k个排列】给定参数n,从1到n会有n个整数:1,2,3,…,n,这n个数字共有n!种排列。
按大小顺序升序列出所有排列情况,并一一标记,当n=3时,所有排列如下:
“123”
“132”
“213”
“231”
“312”
“321”
给定n和k,返回第k个排列。
输入描述:
输入两行,第一行为n,第二行为k,给定n的范围是[1,9],给定k的范围是[1,n!]。

输出描述:
输出排在第k位置的数字。

示例1:
输入:
3
3

输出:
213

import itertools
def getPernumtation(n,k):
    list1 = range(1,n+1)
    templist = list(itertools.permutations(list1))
    for i,temp in enumerate(templist):
        if k == i+1:
            return ''.join([str(j) for j in temp])
n = input()
k = input()
print(getPernumtation(int(n),int(k)))
def getPermutation(n,k):
	def get_n(n):
		res = 1
		for i in range(1,n+1):
			res *= i
		return res
	ret,nums = '',list(map(str,list(range(1,n+1))))
	for i in range(n,0,-1):
		res = get_n(i-1)
		index = (k-1)//res
		ret += nums.pop(index)
		k -= res*index
	return ret
n = input()
k = input()
print(getPermutation(int(n),int(k)))

698. 划分为k个相等的子集

给定一个整数数组 nums 和一个正整数 k,找出是否有可能把这个数组分成 k 个非空子集,其总和都相等。
输入: nums = [4, 3, 2, 3, 5, 2, 1], k = 4
输出: True
说明: 有可能将其分成 4 个子集(5),(1,4),(2,3),(2,3)等于总和。

class Solution(object):
	def canPartitionKSubsets(self, nums, k):
		"""
		:type nums: List[int]
		:type k: int
		:rtype: bool
		"""
		sum_nums = sum(nums)
		avg = sum_nums // k
		if k <= 0 or sum_nums % k != 0 or max(nums) > avg:
			return False
		n = len(nums)
		visited = [0] * n
		nums = sorted(nums, reverse=True)
		return self.canPartition(nums, visited, 0, k, 0, 0, avg)

	def canPartition(self, nums, visited, start_index, k, cur_sum, cur_num, target):
		if k == 1:
			return True
		if cur_sum == target and cur_num > 0:
			return self.canPartition(nums, visited, 0, k - 1, 0, 0, target)
		if cur_sum > target:
			return False
		for i in range(start_index, len(nums)):
			if visited[i] == 0:
				visited[i] = 1
				if self.canPartition(nums, visited, i + 1, k, cur_sum + nums[i], cur_num + 1, target):
					return True
				visited[i] = 0
		return False

252. 会议室

给定一个会议时间安排的数组,每个会议时间都会包括开始和结束的时间 [[s1,e1],[s2,e2],…] (si < ei),请你判断一个人是否能够参加这里面的全部会议。

示例 1:
输入:
[[0,30],[5,10],[15,20]]
输出: false

示例 2:
输入: [[7,10],[2,4]]
输出: true

思路:
先按开始时间排好序,然后看后一个的开始的时间是不是在前一个会议结束之前。

class Solution(object):
    def canAttendMeetings(self, intervals):
        """
        :type intervals: List[List[int]]
        :rtype: bool
        """
        intvs = sorted(intervals, key = lambda x: x[0])
        for idx, intv in enumerate(intvs):
            if idx > 0:
                if intv[0] < intvs[idx - 1][1]:
                    return False
        return True

845. 数组中的最长山脉

我们把数组 A 中符合下列属性的任意连续子数组 B 称为 “山脉”:
B.length >= 3
存在 0 < i < B.length - 1 使得 B[0] < B[1] < … B[i-1] < B[i] > B[i+1] > … > B[B.length - 1]
(注意:B 可以是 A 的任意子数组,包括整个数组 A。)
给出一个整数数组 A,返回最长 “山脉” 的长度。
如果不含有 “山脉” 则返回 0。

示例 1:
输入:[2,1,4,7,3,2,5]
输出:5
解释:最长的 “山脉” 是 [1,4,7,3,2],长度为 5。

示例 2:
输入:[2,2,2]
输出:0
解释:不含 “山脉”。

提示:
0 <= A.length <= 10000
0 <= A[i] <= 10000

思路:
先从左到右扫描一次,得到从左到右递增子数组的长度,
再从右往左扫描一次,得到从右往左递增子数组的长度,
最后再从左到右扫描一次,计算以每个点作为山顶的山脉长度即可。
时间复杂度:O(N)
空间复杂度:O(N)

class Solution(object):
    def longestMountain(self, A):
        """
        :type A: List[int]
        :rtype: int
        """
        l, r = [0 for _ in A], [0 for _ in A]
 
        for i in range(1, len(A)):
            if A[i] > A[i - 1]:
                l[i] = l[i - 1] + 1
        
        for i in range(len(A) - 2, -1, -1):
            if A[i] > A[i + 1]:
                r[i] = r[i + 1] + 1
        
        res = 0
        for i in range(len(A)):
            if l[i] and r[i] and l[i] + r[i] > 1:
                res = max(l[i] + r[i] + 1, res)
        return res

159. 至多包含两个不同字符的最长子串

140. 单词拆分 II

451. 根据字符出现频率排序

给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

示例 1:
输入:
“tree”

输出:
“eert”

解释:
'e’出现两次,'r’和’t’都只出现一次。
因此’e’必须出现在’r’和’t’之前。此外,"eetr"也是一个有效的答案。

示例 2:
输入:
“cccaaa”

输出:
“cccaaa”

解释:
'c’和’a’都出现三次。此外,"aaaccc"也是有效的答案。
注意"cacaca"是不正确的,因为相同的字母必须放在一起。

示例 3:
输入:
“Aabb”

输出:
“bbAa”

解释:
此外,"bbaA"也是一个有效的答案,但"Aabb"是不正确的。
注意’A’和’a’被认为是两种不同的字符。

第一种思路:
class Solution(object):
    def frequencySort(self, s):
        """
        :type s: str
        :rtype: str
        """
        return ''.join([char*freq for char,freq in collections.Counter(s).most_common()])
第二种思路:

半调库+ 手动实现字典的按值排序。

class Solution(object):
    def frequencySort(self, s):
        """
        :type s: str
        :rtype: str
        """
        from collections import Counter
        dic = Counter(s)
        
        res = ""
        dic = sorted(dic.items(), key = lambda x: x[1], reverse = True)
 
        for key, value in dic:
            res += value * key
            
        return res

1171. 从链表中删去总和值为零的连续节点

给你一个链表的头节点 head,请你编写代码,反复删去链表中由总和值为0 的连续节点组成的序列,直到不存在这样的序列为止。删除完毕后,请你返回最终结果链表的头节点。你可以返回任何满足题目要求的答案。(注意,下面示例中的所有序列,都是对 ListNode 对象序列化的表示)

示例 1:
输入:head = [1,2,-3,3,1]
输出:[3,1]
提示:答案 [1,2,1] 也是正确的。

示例 2:
输入:head = [1,2,3,-3,4]
输出:[1,2,4]

示例 3:
输入:head = [1,2,3,-3,-2]
输出:[1]

  1. 思路分析:
    ① 一开始的时候没有啥思路,于是看了一下领扣中关于python的解法,其中发现一个很不错的思路,我结合了题解的代码使用测试用例理解了一下,下面是我对于题解代码思路的一些理解,主要的思路如下:其中比较关键是利用一个累加到当前位置的总和如果出现一个与之前一样的结果那么说明中间肯定是存在连续的总和为0的序列,这个时候我们只需要将这个连续总和为0的序列的前面与后面连接起来即可,这里可以可以借助于python中的字典来做辅助的作用,因为使用字典的时候当结果相同的时候回保存最新的一个节点的值

② 这里需要借助于一个虚拟的一个值为0的节点做辅助可以避免链表为空的情况,①中字典可以保存对应节点的值,当出现相同键的时候这个时候保存的是最新节点的值,这样就可以使中间总和为0的连续的序列跳过去了,所以我们最后使用一个for循环来连接这些节点即可,感觉这个思路还是非常巧妙的,可以结合pycharm中的debug测试一下理解一下其中巧妙的思路

class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
 
class Solution:
    def removeZeroSumSublists(self, head: ListNode) -> ListNode:
        seen = dict()
        profix = 0
        dummy = ListNode(0)
        dummy.next = head
        seen[0] = dummy
        while head:
            profix += head.val
            seen[profix] = head
            head = head.next
 
        head = dummy
        profix = 0
 
        while head:
            profix += head.val
            head.next = seen[profix].next
            head = head.next
        return dummy.next
 
 
if __name__ == '__main__':
    head = ListNode(1)
    n1 = ListNode(2)
    next1 = ListNode(2)
    nnext = ListNode(-3)
    nnnext = ListNode(3)
    nnnnext = ListNode(1)
    head.next = n1
    n1.next = next1
    next1.next = nnext
    nnext.next = nnnext
    nnnext.next = nnnnext
    nnnnext.next = None
    res = Solution().removeZeroSumSublists(head)
    while res:
        print(res.val)
        res = res.next

164. 最大间距

给定一个无序的数组,找出数组在排序之后,相邻元素之间最大的差值。

如果数组元素个数小于 2,则返回 0。

示例 1:

输入: [3,6,9,1]
输出: 3
解释: 排序后的数组是 [1,3,6,9], 其中相邻元素 (3,6) 和 (6,9) 之间都存在最大差值 3。
示例 2:

输入: [10]
输出: 0
解释: 数组元素个数小于 2,因此返回 0。
说明:

你可以假设数组中所有元素都是非负整数,且数值在 32 位有符号整数范围内。
请尝试在线性时间复杂度和空间复杂度的条件下解决此问题。

第一种思路:

如果没有要求线性时间复杂度,那么很简单,直接排序然后遍历排序后的数组即可。

class Solution(object):
    def maximumGap(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) < 2:
            return 0
        nums.sort()
        res = 0
        for i, num in enumerate(nums):
            if i > 0:
                res = max(res, num - nums[i - 1])
        return res

第二种思路:

要处理线性复杂度的排序,可以用桶排序。

开三个数组,exist, max_num, min_num分别表示一个桶是否为空,桶里元素的最大值和桶里元素的最小值,

将nums里的每个数映射入桶之后,找到相邻两个非空桶的最大最小值之差。

class Solution(object):
    def maximumGap(self, nums):
        if len(nums) < 2:
            return 0
        min_val, max_val = min(nums), max(nums)
        if min_val == max_val:
            return 0
        
        n = len(nums) + 1 # 桶的个数
        step = (max_val - min_val) // n
        
        exist = [0 for _ in range(n + 1)]  #表示桶是否为空
        max_num = [0 for _ in range(n + 1)]#表示桶里元素的最大值
        min_num = [0 for _ in range(n + 1)]#表示桶离元素的最小值
        
        for num in nums: #把所有的数入桶
            idx = self.findBucketIndex(num, min_val, max_val, n) 
            max_num[idx] = num if not exist[idx] else max(num, max_num[idx])
            min_num[idx] = num if not exist[idx] else min(num, min_num[idx])
            exist[idx] = 1
        res = 0
        pre = max_num[0]
        for i in range(1, n + 1):
            if exist[i]:
                res = max(res, min_num[i] - pre)
                pre = max_num[i]
        return res
                        
    def findBucketIndex(self, num, min_val, max_val, n):
        return int((num - min_val) * n / (max_val - min_val))

735. 行星碰撞

给定一个整数数组 asteroids,表示在同一行的行星。

对于数组中的每一个元素,其绝对值表示行星的大小,正负表示行星的移动方向(正表示向右移动,负表示向左移动)。每一颗行星以相同的速度移动。

找出碰撞后剩下的所有行星。碰撞规则:两个行星相互碰撞,较小的行星会爆炸。如果两颗行星大小相同,则两颗行星都会爆炸。两颗移动方向相同的行星,永远不会发生碰撞。

示例 1:

输入:
asteroids = [5, 10, -5]
输出: [5, 10]
解释:
10 和 -5 碰撞后只剩下 10。 5 和 10 永远不会发生碰撞。
示例 2:

输入:
asteroids = [8, -8]
输出: []
解释:
8 和 -8 碰撞后,两者都发生爆炸。
示例 3:

输入:
asteroids = [10, 2, -5]
输出: [10]
解释:
2 和 -5 发生碰撞后剩下 -5。10 和 -5 发生碰撞后剩下 10。
示例 4:

输入:
asteroids = [-2, -1, 1, 2]
输出: [-2, -1, 1, 2]
解释:
-2 和 -1 向左移动,而 1 和 2 向右移动。
由于移动方向相同的行星不会发生碰撞,所以最终没有行星发生碰撞。

解题思路
栈模拟,维护一个栈,栈中的元素为保留下来的星球,遍历数组,分为以下几种情况
当前元素 > 0 直接入栈,因为栈顶元素无论正负都不会撞到当前元素
当前元素 < 0 在栈顶元素为正且绝对值小于当前元素的情况下出栈,出栈完毕后,如果栈顶元素为正直接入栈,如果为负且绝对值与当前元素相等,那么栈顶出栈且不入栈,若大于当前元素,也不入栈。

class Solution:
    def asteroidCollision(self, asteroids):
        s = []
        for i in asteroids:
            if not s or i > 0 or s[-1] < 0:
                s.append(i)
            else:
                while s and s[-1] > 0 and s[-1] < -i:
                    s.pop()
                if s and s[-1] == -i:
                    s.pop()
                elif not s or s[-1] < 0:
                    s.append(i)
 
        return s

895. 最大频率栈

实现 FreqStack,模拟类似栈的数据结构的操作的一个类。

FreqStack 有两个函数:

push(int x),将整数 x 推入栈中。
pop(),它移除并返回栈中出现最频繁的元素。
如果最频繁的元素不只一个,则移除并返回最接近栈顶的元素。

示例:
输入:
["FreqStack","push","push","push","push",
 "push","push","pop","pop","pop","pop"],
[[],[5],[7],[5],[7],[4],[5],[],[],[],[]]
输出:[null,null,null,null,null,null,null,5,7,5,4]
解释:
执行六次 .push 操作后,栈自底向上为 [5,7,5,7,4,5]。然后:

pop() -> 返回 5,因为 5 是出现频率最高的。
栈变成 [5,7,5,7,4]。

pop() -> 返回 7,因为 57 都是频率最高的,但 7 最接近栈顶。
栈变成 [5,7,5,4]。

pop() -> 返回 5 。
栈变成 [5,7,4]。

pop() -> 返回 4 。
栈变成 [5,7]。
 
提示:
对 FreqStack.push(int x) 的调用中 0 <= x <= 10^9。
如果栈的元素数目为零,则保证不会调用  FreqStack.pop()。
单个测试样例中,对 FreqStack.push 的总调用次数不会超过 10000。
单个测试样例中,对 FreqStack.pop 的总调用次数不会超过 10000。
所有测试样例中,对 FreqStack.push 和 FreqStack.pop 的
				总调用次数不会超过 150000
  1. 解题
    哈希表1记录每个数的频数
    哈希表2记录频数下,对应有哪些元素,value为栈,保证出栈时是靠近栈顶的
    记录最大频数,实现O(1)查找
class FreqStack {
	unordered_map<int,int> freq;//num,freq
	unordered_map<int, stack<int>> stk;//freq,栈,一个数有这个频数时,存入
	int maxfreq = 0;//最大频数
	int x;
public:
    FreqStack() {

    }
    
    void push(int x) {
    	freq[x]++;
    	maxfreq = max(maxfreq, freq[x]);
    	stk[freq[x]].push(x);
    }
    
    int pop() {
    	x = stk[maxfreq].top();
        freq[x]--;
    	stk[maxfreq].pop();
    	if(stk[maxfreq].empty())
    		maxfreq--;
    	return x;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值