1、两个链表的第一个公共节点
code1
首先分别求出两个链表的长度。若两链表长度差为 a ,让长的链表头节点先走 a 步,然后两链表头节点再一起走。这样当两节点相等时就是第一个公共节点。
code2
每一个指针遍历完一个链表就遍历下一个链表。两个指针访问的第一个相同节点即公共节点。
注意访问完一个链表之后要让指针访问一个空节点。(即判断链表结束的条件为p = None
而不是 p.next = None
)否则两个链表物公共节点的情况下,两个指针不会相等,退不出循环。
2、零钱兑换
code
给定可选择的几种硬币的面额,以及需要兑换的数量 amount,求需要的最少硬币数。无法兑换则返回-1.
设置一个dp数组,大小为 amount + 1,dp[i] 表示兑换 i 需要的最少硬币数。所有元素初始化为 amount + 1,因为后续需要判断最小的硬币数量。
dp[0]初始化为0,依次求 dp[1] 到 dp[amount] 。求 dp[i]时,对每种硬币 c,若 i >= c,dp[i] = min(dp[i], dp[i-c] + 1),即利用小于 i 的情况求解。
3、二叉搜索树的最近公共祖先
code1
可用递归,若所寻找的两个节点的值都大于当前节点的值,则在当前节点右子树寻找;
若所寻找的两个节点的值都小于当前节点的值,则在当前节点左子树寻找;
其他情况(有一个节点值等于当前节点或当前节点值位于两节点值的中间)则返回当前节点。
code2
另外可用循环实现。时间复杂度不变,空间复杂度由O(logN)减少至O(1)。
4、二叉树的最近公共祖先
code1
先递归分别找出两个节点的访问路径,存储至两个列表中。
然后同时遍历两个列表,第一个不同节点(可能某一个为空)的前一个节点即公共节点。
code2
直接在递归中找到公共节点。用一个函数 find(node, p, q) 表示 p 或 q 节点是否存在于 node 为根节点的子树中。
在递归查找的过程中,若find(node.left) 和 find(node.right)都为 True,则两个节点分别在node的左右;
或者 node == p或q,且 find(node.left) 或 find(node.right) 为 True,则node为p或q之一,且零一节点为其子节点。
这两种情况之一则node为最近公共节点。将其记录。
5、剪绳子
code
将长度为 number 的绳子剪成 若干段(大于1段),每一段都为整数,求各段绳子长度乘积的最大值。
- 该问题为动态规划问题,用一个大小为 number +1 的 dp 数组求解。
- 需要注意的是,当 n <= 4 时,dp[n] = n,即绳子不减时有最大乘积;
当 n > 4 时,遍历 i 从 1 到 n-1,dp[n] = max(dp[n], dp[i] * (n - i) )。 - 此外,由于绳子必须划分为大于 1 段,当 n = 2 时,结果为 1;当 n = 3 时,结果为 2。这两种情况要单独考虑。
卡住的地方
- 题目说明的是贪心问题,一开始没往 dp 想。
- 小于等于 4 时的情况要特殊处理,没想清楚这一点。
方法二:code2
dp[n] = max(dp[n], i(n-i), idp[n-i]),i从1到n-1。
即dp[n]表示划分后的最大成绩,那么可以遍历其中的某一段的长度i=1到n-1,其余的可以不划分,即n-i,也可以划分,即dp[n-i]。
这样不需要处理一些边界情况。
6、用两个栈实现队列
code
- push 时放到 栈1 末尾(栈顶)。
- pop 时若 栈2 空,则将 栈1 元素从后到前放入栈2。返回 栈2 末尾元素。
7、数据流中的中位数
code
- 由于只关心中间的两个元素,因此可用大顶堆存储前面一半元素,用小顶堆存储后面一半元素。
- 插入时先插入大顶堆,然后大顶堆 pop 的元素插入小顶堆,这样就完成了堆的调整。
- 为了让两个堆平衡,当小顶堆元素比大顶堆多时,小顶堆再 pop 出顶端元素并插入大顶堆,这样大顶堆的元素个数等于小顶堆,或者大顶堆多一个。
- 当两堆元素个数相等,中位数为两堆堆顶的均值,否则为大顶堆的堆顶元素。
8、二叉树的下一个节点
code
给定二叉树的一个节点,返回其中序遍历时的下一个节点。
- 节点的左子树已遍历过,无用处。
- 若节点存在右节点,先让 p 指向右节点,然后一直向左子树遍历,知道不存在左子树,便是下一个节点。
- 否则下一个节点需要利用父节点寻找。设 cur 为当前节点,令 father 指向父节点,若 cur 为 father 的左节点,则 father 即下一个节点;否则令 cur = father, father = father.father,重复该过程,若向上遍历时 cur 一直是 father 的右节点,说明当前节点已经是树的最后一个节点,最终会到达根节点,退出循环,返回 None。
9、环形链表 II
code
返回链表中环的入口节点。
用快慢指针。当快慢指针相遇时,设首节点到入口节点的距离为a,入口节点到相遇节点的距离为b,相遇节点到入口节点的距离为c。
则fast指针走过的路程为:
a + k(b+c) + b
slow指针走过的路程为:
a + b
而fast走过的路程为slow的2倍,即:
2 * (a+b) = a + k(b+c) + b
化简得:
a = (k-1)(b+c) + c
即首节点到入口节点的距离=相遇节点到入口节点的距离+n倍的环的长度。
因此让一个指针从head开始走,另一个从相遇节点走,每次都走一步,两节点相遇时即入口节点。
10、岛屿数量
code
出现两次错误,应该是i,j>=0却写成>0,应该是(y,x-1), (y,x+1)却写成(x,y-1),(x,y+1)
11、搜索旋转数组
code
数组中可能存在重复元素,题目要求若数组中存在target,则返回元素为target的最左边的下标,否则返回-1。题目中说数组可能被旋转过多次,但是多次旋转(旋转即把数组末端的一部分元素移到前面)后,数组仍是或者有序,或者前边一部分较大,后面一部分较小。
根据mid指向元素与数组首元素的大小关系,以及target与mid指向元素的大小关系分情况讨论。注意当mid指向元素与数组首元素相等时,应该让left+=1,而不是right-=1,因为每次循环对left指向元素与target是否相等做了判断,而没有对right指向元素进行判断。
542、01 矩阵
code
求每个元素到最近的0的距离。用广度优先搜索做,初始时把所有元素为0的坐标加入队列。
LCP 41、黑白翻转棋
code
遍历每一个空位置下棋,并遍历每一个方向,若沿某方向可经过白棋可达到黑棋,则将经过的白棋翻转为黑棋。由于翻转之后的白棋可能导致其相邻的白棋变成黑棋,因此将翻转的白棋加入队列,重复以上过程,知道队列为空。
LCP 40、心算挑战
N张牌中抽出M张,要求M张牌的数字和为偶数,求和的最大值。
方法一:暴力
递归遍历,每次抽一张牌。时间复杂度O(N^M)
方法二:动态规划
dp[i][j][0]、dp[i][j][1]分别表示在前j张牌中抽i张牌,和为偶数、奇数的最大值。
时间复杂度O(N*M)
方法三:[贪心]
先从小到大排序,前M个数若和为偶数,则直接返回;否则尝试将前M个的最小奇数换成第M+1个开始的最大偶数,或者将前M个的最小偶数换成第M+1个开始的最大奇数,选其中的较大的一个,两个都没有则返回0.
46、全排列
方法一:code1
回溯过程中通过形参添加以及删除元素。
方法二:code2
通过形参添加元素,实参数组记录访问过的元素。递归返回之后要把当前访问的元素标记为未访问。
方法三:code3
通过实参添加元素,实参数组记录访问过的元素。递归返回之后要把当前访问的元素标记为未访问,并pop出添加的当前的元素。
由于通过实参添加元素,注意在向results中添加结果时,要将当前结果复制再添加进去。
方法四:code4
将访问的元素与未遍历的第一个元素(下标从first开始)交换,从而保证first之前的都已访问。递归返回后再交换回去。