450. Delete Node in a BST

450. Delete Node in a BST

Problem

Given a root node reference of a BST and a key, delete the node with the given key in the BST. Return the root node reference (possibly updated) of the BST.

Basically, the deletion can be divided into two stages:

  1. Search for a node to remove.

  2. If the node is found, delete the node.

Solution

二叉搜索树(BST)的递增排序的访问顺序是中序遍历。(Left -> Root -> Right)

因此二叉搜索树的前驱者(小于当前节点的最大值)和继任者(大于当前节点的最小值)对于修改二叉树至关重要。

辅助方法getPredecessor() 和getSuccessor() 可以获得前驱者和继任者的值。
分别为当前根节点的左子节点的最右子节点(二叉搜索树下的最大值)和当前根节点右子节点的最左子节点。(二叉搜索树下的最小值)

deleteNode() 方法搜索key。如果当前值大于搜索值则搜索并修改其左子节点,反之搜索并修改右子节点。(由于要修改,因此向下递归子节点时需要将返回的结果传入该子节点。)

当搜索值等于当前值时,存在三种情况:
1.该子节点存在右子节点。此时我们将当前值设置为继任者的值,然后递归搜索右子节点中继任者的值进行删除。
2.该子节点存在左子节点。此时我们将当前值设置为前驱者的值,然后递归搜索左子节点中前驱者的值进行删除。
3.该子节点是叶子节点。直接删除当前节点。

最后返回修改后的根节点即可。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode deleteNode(TreeNode root, int key) {
if(root == null) return null;
if(root.val > key) root.left = deleteNode(root.left, key); //如果当前值大于搜索值则搜索左子节点,否则搜索右子节点
else if(root.val < key) root.right = deleteNode(root.right, key);
else{ //如果当前值等于搜索值,根据子节点情况删除节点
if(root.right != null){ //右子节点不为空则将当前root的值设置为继任者的值,然后向下递归删除继任者
root.val = getSuccessor(root);
root.right = deleteNode(root.right, root.val);
}
else if(root.left != null){ //左子节点不为空则将当前root的值设置为前驱者的值,然后向下递归删除继任者
root.val = getPredecessor(root);
root.left = deleteNode(root.left, root.val);
}
else{
root = null;
}
}
return root;
}

private int getSuccessor(TreeNode root){
root = root.right;
while(root.left != null){
root = root.left;
}
return root.val;
}
private int getPredecessor(TreeNode root){
root = root.left;
while(root.right != null){
root = root.right;
}
return root.val;
}
}
1209. Remove All Adjacent Duplicates in String II

1209. Remove All Adjacent Duplicates in String II

Question

You are given a string s and an integer k, a k duplicate removal consists of choosing k adjacent and equal letters from s and removing them, causing the left and the right side of the deleted substring to concatenate together.

We repeatedly make k duplicate removals on s until we no longer can.

Return the final string after all such duplicate removals have been made. It is guaranteed that the answer is unique.

Solution

用一个栈保存当前的字符,同时建立一个cnt[]数组记录当前字符连续出现的次数。

遍历字符串。

如果当前的字符与栈顶的字符相同时,获取数组中记录的当前连续次数lastCount并加一。
如果lastCount等于k,则匹配到了足够的字符,去掉栈顶的k - 1个元素。
否则将字符串加入栈顶,记录当前位置的出现次数lastCount。

如果当前字符与栈顶字符不同,则将字符直接加入栈顶,记录当前位置的cnt[dq.size()]为1。

最后将栈内元素倒序组成字符串。由于需要这一步操作,因此之前的栈采用双端队列实现。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class Solution {
public String removeDuplicates(String s, int k) {
Deque<Character> dq = new LinkedList<Character>();
int[] cnt = new int[s.length()+1];

for(int i = 0; i < s.length(); i++){
char c = s.charAt(i);
if(i > 0 && !dq.isEmpty() && dq.peek() == c){
int lastCount = cnt[dq.size()];
lastCount++;
if(lastCount == k){
while(lastCount != 1){
dq.removeFirst();
lastCount--;
}
}
else{
dq.offerFirst(c);
cnt[dq.size()] = lastCount;
}
}
else{
dq.offerFirst(c);
cnt[dq.size()] = 1;
}
}

StringBuffer sb = new StringBuffer();
while(!dq.isEmpty()){
sb.append(dq.removeLast());
}
return sb.toString();
}
}
149. Max Points on a Line

149. Max Points on a Line

Question

Given an array of points where points[i] = [x<sub>i</sub>, y<sub>i</sub>] represents a point on the X-Y plane, return the maximum number of points that lie on the same straight line.

Solution

首先选取一个点,然后创建哈希表。记录这个点与其他点的斜率关系与出现次数。记录一个出现次数最多的max值,如果当前次数大于max则更新max。

注意计算斜率时对y == 0和 x == 0时要返回无穷大和0,否则会有精度问题。
最后返回max + 1。

可以优化的地方是,当max大于总点数的一半,或者max大于剩余的点时,因为已经找到了最大的max值,可以直接返回答案。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class Solution {
public int maxPoints(int[][] points) {
int n = points.length;
if(n == 1) return 1;
else if(n == 2) return 2;
int max = 0;

for(int i = 0; i < points.length; i++){
if(max > points.length / 2) break;
if(max > points.length - i) break;
HashMap<Double, Integer> map = new HashMap<>();

for(int j = i+1; j < points.length; j++){
double k = getSlope(points[i], points[j]);
int count = map.getOrDefault(k, 0) + 1;
max = Math.max(max, count);
map.put(k, count);
}
}
return max + 1 ;

}

private double getSlope(int[] p1, int[] p2){
double x1 = p1[0], y1 = p1[1], x2 = p2[0], y2 = p2[1];
if(y1-y2 == 0) return Double.MAX_VALUE;
if(x1-x2 == 0) return 0;
double k = (x1 - x2) / (y1 - y2);
return k;
}
}

叉乘计算可以判断两个向量是否重合。
由于叉乘乘积($|a||b|sinθ$)的模等于两个向量组成的平行四边形的面积,因此当两者乘积为0时,则两个向量重合。



点乘乘积得到的是向量在另一个向量上的投影的乘积,$|a||b|cosθ$

202. Happy Number

202. Happy Number

Question

Write an algorithm to determine if a number n is happy.

A happy number is a number defined by the following process:

  • Starting with any positive integer, replace the number by the sum of the squares of its digits.
  • Repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1.
  • Those numbers for which this process ends in 1 are happy.

Return true if n is a happy number, and false if not.

Solution

首先证明,快乐数的计算不会趋近于无穷大。
我们可以考虑每一个位数的最大数字(9999…999)。
其下一个数值等于(81*n)。
对于三位数以下,其最大的值得下一个结果不会大于243。
对于四位数以上,下一个结果会快速收缩到三位数。
因此计算结果不会趋近于无穷。

当我们计算快乐数的值时,形成了一个隐式链表。
因此我们可以用快慢指针的方式,查询链表上是否有环。
快指针比慢指针初始值快1个单位,且移动速度为慢指针的一倍。
如果链表中有环,则两者终能相遇。
如果fast指针先走到1,等于走到了链表的终点。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
public boolean isHappy(int n) {
int slow = n, fast = getNext(n);
while(fast != 1 && fast != slow){
fast = getNext(getNext(fast));
slow = getNext(slow);
}
return fast == 1;
}

private int getNext(int n){
int sum = 0;
while(n != 0){
int digit = n % 10;
sum += (digit * digit);
n = n / 10;
}
return sum;
}
}
673. Number of Longest Increasing Subsequence

673. Number of Longest Increasing Subsequence

Question

Given an integer array nums, return the number of longest increasing subsequences.

Notice that the sequence has to be strictly increasing.

Solution

本题还有贪心算法+前缀和+二分查找的算法。

本题是300. Longest Increasing Subsequence的拓展。
同样采用动态规划,数组dp[i]记录到i为止最长递增数列长度。
可以用一个新的数组cnt[i]记录到i为止可以组成的最长递增数列的数量。

对于每个新位置i,cnt[i]的最小值为i。
遍历i之前的所有位置j。如果nums[j] < nums[i],则i可以比dp[j]组成更长的递增数列,其长度为dp[j]+1。
如果dp[i] < dp[j]+1。则可以更新dp[i]。同时,cnt[i]可以从cnt[j]继承其计数。
如果dp[i] == dp[j]+1。则之前已经更新过dp[i]。说明有新的组合同样可以组成更长的递增数列。此时将cnt[j]加入当前的cnt[i]。

遍历完成i以内的所有j后,如果dp[i]大于当前的最长递增数列长度,则更新max。
同时更新长度的总数count为cnt[i]。
如果dp[i]等于max,则将cnt[i]加入计数count。
最后返回count。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
class Solution {
public int findNumberOfLIS(int[] nums) {
int n = nums.length;
int[] dp = new int[n];
int[] cnt = new int[n];
int max = 0;
int count = 0;

for(int i = 0; i < n; i++){
dp[i] = 1;
cnt[i] = 1;
for(int j = 0; j < i; j++){
if(nums[i] > nums[j]){
if(dp[j] + 1 > dp[i]){
dp[i] = dp[j] + 1;
cnt[i] = cnt[j]; //如果后面的数字大于前面的,且可以组成更长的数列,则继承之前的计数。
}
else if(dp[j] + 1 == dp[i]){ //如果之前已经更新过dp[i],则有新的组合长度一直,加和之前的计数。
cnt[i] += cnt[j];
}
}
}
if(dp[i] > max){ //如果当前的长度大于之前的最大值,则更新。
max = dp[i];
count = cnt[i]; //同时将之前计算的计数记录。
}
else if(dp[i] == max){ //如果有同样达到最大值的情况,则加和计数。
count += cnt[i];
}
}
return count;
}
}