173. 二叉搜索树迭代器
实现一个二叉搜索树迭代器。你将使用二叉搜索树的根节点初始化迭代器。
调用 next() 将返回二叉搜索树中的下一个最小的数。
BSTIterator iterator = new BSTIterator(root);
iterator.next(); // 返回 3
iterator.next(); // 返回 7
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 9
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 15
iterator.hasNext(); // 返回 true
iterator.next(); // 返回 20
iterator.hasNext(); // 返回 false
提示:
next() 和 hasNext() 操作的时间复杂度是 O(1),并使用 O(h) 内存,其中 h 是树的高度。
你可以假设 next() 调用总是有效的,也就是说,当调用 next() 时,BST 中至少存在一个下一个最小的数。
第一种思路:
利用BST的性质:BST的中序遍历为升序数组。
所以直接在init里用中序遍历然后存进数组里就好了。
时间复杂度:next() 和 hasNext()为O(1)
空间复杂度:O(N)
class BSTIterator(object):
def __init__(self, root):
"""
:type root: TreeNode
"""
self.res = list()
def inorder(node):
if not node:
return
inorder(node.left)
self.res.append(node.val)
inorder(node.right)
inorder(root)
self.index = 0
def next(self):
"""
@return the next smallest number
:rtype: int
"""
self.index += 1
# print self.index - 1, self.res
return self.res[self.index - 1]
def hasNext(self):
"""
@return whether we have a next smallest number
:rtype: bool
"""
return self.index < len(self.res)
第二种思路:
利用一个stack来存。
pushLeft这个函数的功能是把 一个节点和它的左孩子以及左孩子的左孩子… 压入栈。
时间复杂度:next() 和 hasNext()为O(1)
空间复杂度:O(h)
class BSTIterator(object):
def __init__(self, root):
"""
:type root: TreeNode
"""
self.stack = []
self.pushLeft(root)
def next(self):
"""
@return the next smallest number
:rtype: int
"""
popedNode = self.stack.pop()
self.pushLeft(popedNode.right)
return popedNode.val
def hasNext(self):
"""
@return whether we have a next smallest number
:rtype: bool
"""
return len(self.stack) != 0
def pushLeft(self, node):
while(node):
self.stack.append(node)
node = node.left
第三种思路:
类似二叉树的迭代法实现中序遍历。
时间复杂度:next() 和 hasNext()为O(1)
空间复杂度:O(h)
class BSTIterator(object):
def __init__(self, root):
"""
:type root: TreeNode
"""
self.stack = []
self.cur = root
def next(self):
"""
@return the next smallest number
:rtype: int
"""
while self.cur or self.stack:
if self.cur:
self.stack.append(self.cur)
self.cur = self.cur.left
else:
self.cur = self.stack.pop()
res = self.cur.val
self.cur = self.cur.right
return res
def hasNext(self):
"""
@return whether we have a next smallest number
:rtype: bool
"""
return self.cur or self.stack
767. 重构字符串
题目描述
给定一个字符串 s ,检查是否能重新排布其中的字母,使得两相邻的字符不同。
返回 s 的任意可能的重新排列。若不可行,返回空字符串 “” 。
示例 :
示例 1:
输入: s = “aab”
输出: “aba”
示例 2:
输入: s = “aaab”
输出: “”
思路
抽屉原理
c > n/2上取整,一定无解;
c <= n/2上取整,一定有解
-当n为奇数,当c > n/2上取整 ,先放偶数位置;
-当c <= n/2, 先放奇数,放满后再放偶数;
解题思路: 设置一个大顶堆,每次取出现最多的字符和次多的字符。将这两个字符排入结果。每次重构完后要重新将更新的字符数量push进入大顶堆,直到大顶堆为空。注意python的heapq是小顶堆,所有对字符出现的次数取负,可以实现大顶堆。
题解:
import heapq
class Solution:
def reorganizeString(self, S: str) -> str:
if len(S) < 2:
return ""
count = collections.Counter(S)
# 只有一个元素且数量大于1
if len(count) == 1 and len(S) > 1:
return ""
# 出现的次数取负,构建大顶堆
queue = [(-x[1], x[0]) for x in count.items()]
heapq.heapify(queue)
res = ""
while len(queue) > 1:
# 出现次数最多和次多的
_, c1 = heapq.heappop(queue)
_, c2 = heapq.heappop(queue)
res += c1 + c2
# 出现次数减一
count[c1] -= 1
count[c2] -= 1
# 重新构建大顶堆
if count[c1] > 0:
heapq.heappush(queue, (-count[c1], c1))
if count[c2] > 0:
heapq.heappush(queue, (-count[c2], c2))
# print(count)
print(queue)
if queue:
if (queue[0][0]) == -1:
res += queue[0][1]
else:
# 最后剩余的数量大于1,返回""
return ""
return res
补充题24. 双栈排序
剑指 Offer 59 - I. 滑动窗口的最大值
给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
示例:
输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7]
解释:
滑动窗口的位置 最大值
[1 3 -1] -3 5 3 6 7 3
1 [3 -1 -3] 5 3 6 7 3
1 3 [-1 -3 5] 3 6 7 5
1 3 -1 [-3 5 3] 6 7 5
1 3 -1 -3 [5 3 6] 7 6
1 3 -1 -3 5 [3 6 7] 7
提示:
你可以假设 k 总是有效的,在输入数组不为空的情况下,1 ≤ k ≤ 输入数组的大小。
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
# 创建一个队列
deque = collections.deque()
res,n = [], len(nums)
for i,j in zip(range(1-k, n + 1 - k), range(n)):
# 因为 i > 0,时候在进行处理的,则i - 1是窗口的尾部,j是窗口的头部,
# 如果队列中首个数字等于窗口尾部的要弹出的数字,则进行删除.
if i > 0 and deque[0] ==nums[i - 1]:
# 删除deque中对应的nums[i-1],popleft将队列左侧的数字进行删除
deque.popleft()
# 如果deque不为空,且如果队列的尾部小于窗口的首的数字,则进行删除,如果一直小直到删除结束为止
while deque and deque[-1] < nums[j]:
deque.pop()
# 将窗口首部的数字放入队列中
deque.append(nums[j])
# i >= 0,这就说明窗口尾部已经位于nums列表上了
if i >= 0:
# 这个时候队列的首部就是这个窗口中的最大值
res.append(deque[0])
return res
class Solution(object):
def maxSlidingWindow(self, nums, k):
"""
:type nums: List[int]
:type k: int
:rtype: List[int]
"""
if not nums or not k:
return []
result = []
for i in range(len(nums) - k + 1):
result.append(max(nums[i: i + k]))
return result
836. 矩形重叠
矩形以列表 [x1, y1, x2, y2] 的形式表示,其中 (x1, y1) 为左下角的坐标,(x2, y2) 是右上角的坐标。
如果相交的面积为正,则称两矩形重叠。需要明确的是,只在角或边接触的两个矩形不构成重叠。
给出两个矩形,判断它们是否重叠并返回结果。
示例 1:
输入:rec1 = [0,0,2,2], rec2 = [1,1,3,3]
输出:true
示例 2:
输入:rec1 = [0,0,1,1], rec2 = [1,0,2,1]
输出:false
class Solution(object):
def isRectangleOverlap(self, rec1, rec2):
"""
:type rec1: List[int]
:type rec2: List[int]
:rtype: bool
"""
x1, y1, x2, y2 = rec1
x3, y3, x4, y4 = rec2
return (x4 - x1) * (x2 - x3) > 0 and (y4 - y1) * (y2 - y3) > 0
875. 爱吃香蕉的珂珂
珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。
珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。
珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。
示例 1:
输入: piles = [3,6,7,11], H = 8
输出: 4
示例 2:
输入: piles = [30,11,23,4,20], H = 5
输出: 30
示例 3:
输入: piles = [30,11,23,4,20], H = 6
输出: 23
思路:
二分查找,已知左右极值为ceil(sum(piles)//H),max(piles),
然后可以用二分法缩小范围。跟1014非常相似。
class Solution(object):
def minEatingSpeed(self, piles, H):
"""
:type piles: List[int]
:type H: int
:rtype: int
"""
import math
if len(piles) == 1:
return int(math.ceil(piles[0] // H) + 1)
lo, hi = math.ceil(sum(piles)/H), max(piles)
while(lo < hi):
mid = (lo + hi)// 2
cnt = 0
for pile in piles:
cnt += math.ceil(pile / mid)
# print k, cnt
if cnt > H:# 吃得慢了
lo = mid + 1
elif cnt <= H:
hi = mid
return int(lo)
772. 基本计算器 III
127. 单词接龙
1206. 设计跳表
题目
不使用任何库函数,设计一个 跳表 。
跳表 是在 O(log(n)) 时间内完成增加、删除、搜索操作的数据结构。跳表相比于树堆与红黑树,其功能与性能相当,并且跳表的代码长度相较下更短,其设计思想与链表相似。
例如,一个跳表包含 [30, 40, 50, 60, 70, 90] ,然后增加 80、45 到跳表中,以下图的方式操作:
跳表中有很多层,每一层是一个短的链表。在第一层的作用下,增加、删除和搜索操作的时间复杂度不超过 O(n)。跳表的每一个操作的平均时间复杂度是 O(log(n)),空间复杂度是 O(n)。
了解更多 : https://en.wikipedia.org/wiki/Skip_list
在本题中,你的设计应该要包含这些函数:
bool search(int target) : 返回target是否存在于跳表中。
void add(int num): 插入一个元素到跳表。
bool erase(int num): 在跳表中删除一个值,如果 num 不存在,直接返回false. 如果存在多个 num ,删除其中任意一个即可。
注意,跳表中可能存在多个相同的值,你的代码需要处理这种情况。
示例
示例 1:
输入
[“Skiplist”, “add”, “add”, “add”, “search”, “add”, “search”, “erase”, “erase”, “search”]
[[], [1], [2], [3], [0], [4], [1], [0], [1], [1]]
输出
[null, null, null, null, false, null, true, false, true, false]
解释
Skiplist skiplist = new Skiplist();
skiplist.add(1);
skiplist.add(2);
skiplist.add(3);
skiplist.search(0); // 返回 false
skiplist.add(4);
skiplist.search(1); // 返回 true
skiplist.erase(0); // 返回 false,0 不在跳表中
skiplist.erase(1); // 返回 true
skiplist.search(1); // 返回 false,1 已被擦除
提示:
0 <= num, target <= 2 * 104
调用search, add, erase操作次数不大于 5 * 104
思路
维持一个哈希表,键为要存储的值,值为要存储值的数量
详细见代码
class Skiplist:
def __init__(self):
# 维持哈希表
self.temp = {}
def search(self, target: int) -> bool:
# 看target是否在哈希表的键中,或哈希表的值为0
return target in self.temp.keys() and self.temp.get(target) >= 1
def add(self, num: int) -> None:
# 哈希表中存在,数量加一
if num in self.temp.keys():
self.temp[num] += 1
else:
# 哈希表中不存在,直接添加
self.temp[num] = 1
def erase(self, num: int) -> bool:
# 检查num是否在哈希表的键中,或者数量已经减为零
if num not in self.temp.keys() or self.temp.get(num) < 1:
return False
# 存在的情况,哈希表中对应的值减一
self.temp[num] -= 1
return True
1233. 删除子文件夹
你是一位系统管理员,手里有一份文件夹列表 folder,你的任务是要删除该列表中的所有 子文件夹,并以 任意顺序 返回剩下的文件夹。
如果文件夹 folder[i] 位于另一个文件夹 folder[j] 下,那么 folder[i] 就是 folder[j] 的 子文件夹 。
文件夹的「路径」是由一个或多个按以下格式串联形成的字符串:‘/’ 后跟一个或者多个小写英文字母。例如,“/leetcode” 和 “/leetcode/problems” 都是有效的路径,而空字符串和 “/” 不是。
二、示例
2.1> 示例 1:
【输入】folder = [“/a”,“/a/b”,“/c/d”,“/c/d/e”,“/c/f”]
【输出】[“/a”,“/c/d”,“/c/f”]
【解释】“/a/b” 是 “/a” 的子文件夹,而 “/c/d/e” 是 “/c/d” 的子文件夹。
2.2> 示例 2:
【输入】folder = [“/a”,“/a/b/c”,“/a/b/d”]
【输出】[“/a”]
【解释】文件夹 “/a/b/c” 和 “/a/b/d” 都会被删除,因为它们都是 “/a” 的子文件夹。
2.3> 示例 3:
【输入】 folder = [“/a/b/c”,“/a/b/ca”,“/a/b/d”]
【输出】 [“/a/b/c”,“/a/b/ca”,“/a/b/d”]
class Solution:
def removeSubfolders(self, folder: List[str]) -> List[str]:
ans = []
last = " "
folder.sort()
for f in folder:
if not f.startswith(last):
ans.append(f)
last = f + "/"
return ans
面试题 02.02. 返回倒数第 k 个节点
class Solution:
def kthToLast(self, head: ListNode, k: int) -> int:
cur = head
cnt = 1
while cur.next:
cnt += 1
cur = cur.next
cur = head
for i in range(cnt - k):
cur = cur.next
return cur.val
class Solution:
def kthToLast(self, head: ListNode, k: int) -> int:
fast = head
slow = head
while k > 0:
fast = fast.next
k -= 1
while fast != None:
fast = fast.next
slow = slow.next
return slow.val
1658. 将 x 减到 0 的最小操作数
一.题目
给你一个整数数组 n u m s numsnums和一个整数 x xx 。每一次操作时,你应当移除数组 n u m s numsnums 最左边或最右边的元素,然后从 x xx 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。
如果可以将 x xx 恰好减到0 00,返回最小操作数 ;否则,返回− 1 -1−1 。
示例 1:
输入:nums = [1,1,4,2,3], x = 5
输出:2
解释:最佳解决方案是移除后两个元素,将 x 减到 0 。
示例 2:
输入:nums = [5,6,7,8,9], x = 4
输出:-1
示例 3:
输入:nums = [3,2,20,1,1,3], x = 10
输出:5
解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。
题解
from typing import List
class Solution:
def minOperations(self, nums: List[int], x: int) -> int:
#初始化满足条件的最长子序列长res为0
s,res = 0,0
#计算sum -x
k = sum(nums) - x
#初始化字典
map = {}
map[0]=-1
for i in range(len(nums)):
#累加计算total[i]
s += nums[i]
#寻找是否存在键为total[i] - (sum -x)的元素
if map.get(s - k) != None:
res = max(i - map.get(s-k),res)
#存储<total[i],i>
if map.get(s) == None:
map[s] = i
#排除nums数组和为x的情况,这种情况下,res的值也为0
if sum(nums) == x:
return len(nums)
return len(nums) - res if res != 0 else -1
905. 按奇偶排序数组
题目描述:
给你一个整数数组 nums,将 nums 中的的所有偶数元素移动到数组的前面,后跟所有奇数元素。
返回满足此条件的 任一数组 作为答案。
示例 1:
输入:nums = [3,1,2,4]
输出:[2,4,3,1]
解释:[4,2,3,1]、[2,4,1,3] 和 [4,2,1,3] 也会被视作正确答案。
示例 2:
输入:nums = [0]
输出:[0]
提示:
1 <= nums.length <= 5000
0 <= nums[i] <= 5000
该问题任务较为清晰简单,可以使用双指针法,类似于快速排序算法的思想,两个指针分别从头和尾向尾和头遍历,分别找到第一个奇数和最后一个偶数,将两者交换位置,直至两个指针的相对位置改变。
class Solution:
def sortArrayByParity(self, nums: List[int]) -> List[int]:
left, right = 0, len(nums)-1
while left<right:
while left<right and nums[left]%2==0:
left += 1
while right>left and nums[right]%2:
right -= 1
if left==right:
break
nums[left], nums[right] = nums[right], nums[left]
return nums
解题思路一:双指针,用last指向数组尾部,用i指向当前遍历位置,若遇到奇数与last交换,若交换过来的仍然是奇数,则让–i。注意for循环需要i<n且i<last
class Solution {
public:
vector<int> sortArrayByParity(vector<int>& nums) {
int n = nums.size();
int last = n-1;
for(int i=0;i<n&&i<last;++i){
if(nums[i]%2==1){
int temp=nums[i];
nums[i]=nums[last];
nums[last]=temp;
--last;
}
if(nums[i]%2==1) --i;
}
return nums;
}
};
解题思路二:两次遍历,第一次加入偶数,第二次加入奇数
class Solution {
public:
vector<int> sortArrayByParity(vector<int>& nums) {
vector<int> res;
for (auto & num : nums) {
if (num % 2 == 0) {
res.push_back(num);
}
}
for (auto & num : nums) {
if (num % 2 == 1) {
res.push_back(num);
}
}
return res;
}
};
解题思路三:双指针 + 一次遍历。遇到偶数,保存在数组前端,遇到奇数保存在数组后端
class Solution {
public:
vector<int> sortArrayByParity(vector<int>& nums) {
int n = nums.size();
vector<int> res(n);
int left = 0, right = n - 1;
for (auto & num : nums) {
if (num % 2 == 0) {
res[left++] = num;
} else {
res[right--] = num;
}
}
return res;
}
};
解题思路四:原地交换,左边找一个奇数,右边找一个偶数。然后交换,注意left一直要小于right
class Solution {
public:
vector<int> sortArrayByParity(vector<int>& nums) {
int left = 0, right = nums.size() - 1;
while (left < right) {
while (left < right and nums[left] % 2 == 0) {
left++;
}
while (left < right and nums[right] % 2 == 1) {
right--;
}
if (left < right) {
swap(nums[left++], nums[right--]);
}
}
return nums;
}
};
补充题20. 立方根
1280. 学生们参加各科测试的次数
select st.student_id,st.student_name,st.student_name,count(ex.student_id) attended_exams
from students st
join subjects su
left join examinations ex
on st.student_id = ex.student_id and su.subject_name = ex.subject_name
group by st.student_id,su.subject_name;