['1', '2', '3', '4']
pop 4
pop 3
['1', '2']
使用辅助栈的方式,冗余的添加min_stack的数据
class Solution:
node_map = {}
def copyRandomList(self, head: 'Node') -> 'Node':
if not head:
return head
if head not in self.node_map:
new = Node(head.val)
self.node_map[head] = new
new.next = self.copyRandomList(head.next)
new.random = self.copyRandomList(head.random)
return self.node_map[head]
A -> B -> C -> D
拆分为
A -> A’ -> B -> B’ -> C -> C’ -> D -> D’
再把random指针从挪到random’上
再拆开
A -> B -> C -> D
A’ -> B’ -> C’ -> D’
得到一个复制的A’
return s[n:] + s[:n]
return s.replace(" ", “%20”)
用hash set
两次二分查找,寻找左边界和右边界,最后得到结果
首先是0-n-1,即:
因此只需要二分查找到下标不一致的位置即可
线性查找,从右上角开始查(同理也可以从左下角开始查,只要列和行是相反的位置,就能进行比较)
代码:easy_find_min_number_from_rotate_array.py
方法一:暴力法,即如果 i 比 i+1大,那么i+1就是最小值,如果不存在这样的一个i,那么选第0个数
方法二:二分法
代码:easy_find_first_only_one_time_number.py
方法1:hash,遍历两次
方法2:hash + 队列
把数据放进hash里,如果没有出现过的字符就放进队列里,然后弹出队列,确认hash中的元素是只出现一次的。
之所以加一个队列,而不是重新遍历元素,是去除重复元素的影响
知识点:
用队列,把左右节点放进队列里,先进先出则可以遍历
用队列 + curr_size,一次性清空一层的数据,而不是一个个清
基于上一题,使用双端队列collections.deque(),可以从两边插入的:
当我们遍历树的时候,一般的方法递归
代码设计分为三部分:
从叶子节点开始互换,递归遍历左右子树之后,互换子树。
镜像对称的树:
方法1:递归,树的遍历就离不开递归,递归什么时候结束:
方法2:迭代 + 队列,迭代的话一定会用到队列
只需要用到一个队列,交替的弹出左右节点即可
f[n] = f[n-1] + f[n-2]
也可以用
a, b = b, a + b
当然也可以用斐波那契通项公式:(sqrt_5 / 5) * (math.pow((1 + sqrt_5) / 2, n) - math.pow((1 - sqrt_5) / 2, n))
实际上就是变相的,斐波那契数列
理解一下,青蛙可以跳一台阶,也能跳两个台阶:
设跳上 n 级台阶有 f(n) 种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上 1 级或 2 级台阶。
因此对于f(n) = f(n-1) + f(n-2)
只是初始值不同,初始值是1,因为不会有0个台阶的情况
代码:dp__mid_max_profit_of_stock.py
动态规划(暴力法):遍历两次,记录每一个点与之后各个点的差值,然后记录最大值
抄底法:记录最低点,然后在最低点之后开始记录每一天的收益,就像买股票一样天天想着发财。然后直到最低点的再次出现,就重新刷新最低点。
子数组:字数组也是数组,需要区别于子序列
-1, -2, -3, 1
其中前三个-1, -2 -3的和是-6,那还比不上第四个1大,那其实就没必要加了,直接从1开始计算即可。
根据动态规划的思想,定义f(i)表示以i结尾的最大子数组和:
f(i) = max{f(i-1) + nums[i], nums[i]}
因此我们可以记录一个pre表示f(i-1),使pre + nums[i]和当前nums[i]比对,得到
pre = max{pre + nums[i], nums[i]}
f(i) = max{pre, max}
动态规划问题最难的是定义:
首先,题目求的是能拿到礼物的最大价值,我们可以考虑求出到达每一个格子能够拿到的最多的价值的礼物是多少。
我们设f(i, j)表示的是在i, j位置最大的礼物价值
因为只能向右或者向下选择礼物,而且每个格子上都有礼物,可以得到两个必然发生的事情:
则可以得到状态转移方程:
f(i, j) = max{f(i - 1, j), f(i, j - 1)} + gift(i, j)
当然这个还需要考虑边界的情况,即:
判断条件优化
在循环当中,真正的操作耗时主要在于if语句的判断,因此考虑上述的边界情况,在循环中每次都要判断的问题。
优化思路可以是先把边界值先for循环算出来,则在判断的时候,就只需要计算i>0且j>0的情况即可。
空间复杂度优化
一般的动态规划算法,都需要自己事先定义一个dp数组去接收动态规划的数据。
这个地方因为是从上往下的计算,因此可以直接修改原数组的数值,这样就省去了开辟数组的空间。
当然这个地方还是提供一下各类语言初始化数组的语句:
// 开辟一维数组
int[] arr1 = new int[5]; // 数组长度不一定要一个常量,但是数组长度固定在new的时候后的值
// 开辟二维数组
int[][] arr2 = new int[4][3];
// 开辟一维数组
// 开辟二维数组
# 开辟一维数组
arr1 = [0] * 5 # python由于没有数组只有链表,因此要初始化数组就需要用0进行填充
# 开辟二维数组
# error: arr2 =
arr2 = [[0]*5 for _ in range(5)]
相似题目:打家劫舍
动态规划的题目最主要的是搞明白,dp的状态转移方程,也就是说f(i)表示的是什么东西。
在这里,可以有两种翻译:
则设f(i)表示的是以i结尾有多少种字符串排序方法
考虑到以下两种情况:
f(i) = f(i-1)
f(i) = f(i-1) + f(i - 2) // 条件是0 < i-1 <=2, 0 <= i < 6
则可以得到状态转移方程:
f(i) = f(i-1)
= f(i-1) + f(i - 2) // 条件是0 < i-1 <=2, 0 <= i < 6
这个题使用的滑动窗口的方式
双指针,用一个指针在前面,一个指针在后面,中间相隔k个值,等遍历完后把后面的指针输出
代码:doublepoint__easy_merge_two_sort_linklist.py
迭代
递归,好理解,不好想到
代码:doublepoint__easy_first_public_node_to_linklist.py
使用hash,遍历list1,存入所有节点到set中,然后遍历list2的时候如果在set中则直接输出
使用双指针:
为啥呢?
因为p1指针和p2指针遍历的长度都是:list1 + list2,因此碰撞的点一定是公共节点
双指针,左右指针,
代码:doublepoint__easy_sum_is_s_number.py
hashmap
左右指针
代码:doublepoint__easy_reverse_words.py
使用内置reversed方法
实现reversed方法
双端队列