1770. Max Score from Multiplication Operations

1770. Max Score from Multiplication Operations

Question

You are given two integer arrays nums and multipliers** **of size n and m respectively, where n >= m. The arrays are 1-indexed.

You begin with a score of 0. You want to perform exactly m operations. On the i<sup>th</sup> operation (1-indexed), you will:

  • Choose one integer x from **either the start or the end **of the array nums.
  • Add multipliers[i] * x to your score.
  • Remove x from the array nums.

Return *the maximum score after performing *m operations.

Solution

动态规划,dp[][]数组记录左边取的个数和右边取的个数。

从取1开始到取multipliers的长度位置开始遍历。
然后从left取0个开始,直到left取i个为止遍历。
计算对应的right指针位置。

注意访问数组时需要访问left和right的上一个位置。

如果left为0,则只能取右侧的上一个位置加上右侧的上一个数值乘以mul。
如果right为0,则只能取左侧的上一个位置加上左侧的上一个数值乘以mul。
否则取两者之间的最大值。

最后遍历数组中left + right和为m的位置,并返回最大值。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int maximumScore(int[] nums, int[] multipliers) {
int n = nums.length, m = multipliers.length;
int[][] dp = new int[m+1][m+1];

for(int i = 1; i <= m; i++){
int mul = multipliers[i-1];
for(int l = 0; l <= i; l++){
int r = i - l;
int iL = l - 1, iR = n - r;
if(l == 0) dp[l][r] = dp[l][r-1] + mul * nums[iR];
else if(r == 0) dp[l][r] = dp[l-1][r] + mul * nums[iL];
else dp[l][r] = Math.max(dp[l-1][r] + mul * nums[iL], dp[l][r-1] + mul * nums[iR]);
}
}
int ans = Integer.MIN_VALUE;
for(int l = 1; l <= m; l++){
int r = m - l;
ans = Math.max(ans, dp[l][r]);
}
return ans;
}
}
1996. The Number of Weak Characters in the Game

1996. The Number of Weak Characters in the Game

Question

You are playing a game that contains multiple characters, and each of the characters has two main properties: attack and defense. You are given a 2D integer array properties where properties[i] = [attack<sub>i</sub>, defense<sub>i</sub>] represents the properties of the i<sup>th</sup> character in the game.

A character is said to be weak if any other character has both attack and defense levels strictly greater than this character’s attack and defense levels. More formally, a character i is said to be weak if there exists another character j where attack<sub>j</sub><span> </span>> attack<sub>i</sub> and defense<sub>j</sub><span> </span>> defense<sub>i</sub>.

Return the number of weak characters.

Solution

本题与354. Russian Doll Envelopes相近。

首先对数组进行排序,根据角色的attack数值降序排序,如果两者的attack数值相等,则根据两者的defence升序排序。

记录一个遍历过的最大defence数值maxDefence,遍历数组,如果当前maxDef大于角色的防御值,则此时当前遍历角色的attack与defence均严格小于上一个计算的角色,因此count加一。
否则更新maxDef。

*由于attack是降序的,因此可以确定遍历时下一组数组的attack一定更弱。(由于相同attack的角色是根据defence升序排列,因此记录maxDef时会逐个更新maxDef的值。)

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution {
public int numberOfWeakCharacters(int[][] properties) {
int count = 0, maxDef = Integer.MIN_VALUE;

Arrays.sort(properties, new Comparator<int[]>(){
public int compare(int[] a, int[] b){
if(a[0] == b[0]) return a[1] - b[1];
return b[0] - a[0];
}
});

for(int[] character : properties){
if(maxDef > character[1]) count++;
else maxDef = character[1];
}
return count;
}
}
869. Reordered Power of 2

869. Reordered Power of 2

Question

You are given an integer n. We reorder the digits in any order (including the original order) such that the leading digit is not zero.

Return true if and only if we can do this so that the resulting number is a power of two.

Solution

打表,将所有二的指数全部计算出来,并用数组统计各个数字出现的频率。
然后同样统计n中各个数字出现的频率。

如果两者中有频率完全相同的情况,则返回true,反之返回false。

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 boolean reorderedPowerOf2(int n) {
int[] reorders = new int[10];
int m = 1, digits = 0;
for(int i = n; i > 0; i/=10){
reorders[i % 10]++;
digits++;
}

int size = 0;
List<Integer[]> powerOfTwo = new ArrayList<>();

while(m > 0 && size <= digits){
size = 0;
Integer[] bin = new Integer[10];
Arrays.fill(bin, 0);
for(int i = m; i > 0; i/=10){
size++;
bin[i % 10]++;
}
if(size == digits) powerOfTwo.add(bin);
m *= 2;
}

for(Integer[] bin : powerOfTwo) if(check(bin, reorders)) return true;
return false;
}

private boolean check(Integer[] bin, int[] reorders){
for(int i = 0; i < bin.length; i++) if(bin[i] != reorders[i]) return false;
return true;
}
}

Solution 2

回溯,遍历计算可以组成的所有数字。
打表记录所有二的指数并记录在哈希表内,如果所有组成数字中有哈希表内的数字则返回true,反之返回false。

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
class Solution {
HashSet<Integer> visited, set, numbers;
List<Integer> arr, res;
public boolean reorderedPowerOf2(int n) {
int m = 1;
set = new HashSet<>();
visited = new HashSet<>();
numbers = new HashSet<>();
res = new ArrayList<Integer>();
while(m > 0){
set.add(m);
m *= 2;
}

arr = new ArrayList<>();

while(n > 0){
arr.add(n % 10);
n /= 10;
}

for(int i = 0; i< arr.size(); i++){
if(arr.get(i) == 0) continue;
visited.add(i);
reorder(arr.get(i));
visited.remove(i);
}


for(int num : res) if(set.contains(num)) return true;
return false;
}

private void reorder(int num){
if(visited.size() == arr.size()){
res.add(num);
return;
};

if(numbers.contains(num)) return;
numbers.add(num);

for(int i = 0; i < arr.size(); i++){
if(visited.contains(i)) continue;
int next = num * 10 + arr.get(i);
visited.add(i);
reorder(next);
visited.remove(i);
}
}
}
659. Split Array into Consecutive Subsequences

659. Split Array into Consecutive Subsequences

Question

You are given an integer array nums that is sorted in non-decreasing order.

Determine if it is possible to split nums into one or more subsequences such that both of the following conditions are true:

  • Each subsequence is a consecutive increasing sequence (i.e. each integer is exactly one more than the previous integer).
  • All subsequences have a length of 3** or more**.

Return true* if you can split nums according to the above conditions, or false otherwise*.

A subsequence of an array is a new array that is formed from the original array by deleting some (can be none) of the elements without disturbing the relative positions of the remaining elements. (i.e., [1,3,5] is a subsequence of [<u>1</u>,2,<u>3</u>,4,<u>5</u>] while [1,3,2] is not).

Solution

用优先级队列储存每个可组成的列表。
优先级队列根据列表的长度排列。

用哈希表记录每个列表的尾数,和其对应的优先级队列。
遍历所有数字,如果存在当前数字num-1为尾数的队列,则获取长度最小的列表,并添加当前数字num在列表中。
然后将新的优先级队列放入哈希表中。

遍历整个哈希表中的数组,如果有数组的长度小于3,则返回false,否则返回true。

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 boolean isPossible(int[] nums) {
HashMap<Integer, PriorityQueue<List<Integer>>> map = new HashMap<>();

for(int num : nums){
PriorityQueue<List<Integer>> last = map.getOrDefault(num - 1, null);
PriorityQueue<List<Integer>> curr = map.getOrDefault(num, new PriorityQueue<List<Integer>>((a,b) -> a.size() - b.size()));

if(last == null || last.size() == 0){
List<Integer> arr = new ArrayList<>();
arr.add(num);
curr.add(arr);
map.put(num, curr);
}
else{
List<Integer> arr = last.poll();
arr.add(num);
curr.add(arr);
map.put(num, curr);
}
}

for(int last : map.keySet()){
for(List<Integer> arr : map.get(last)){
if(arr.size() < 3) return false;
}
}
return true;

}
}

Solution 2

不需要保存整个列表,只需要保存对应末尾数字的列表长度即可。

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
class Solution {
public boolean isPossible(int[] nums) {
HashMap<Integer, PriorityQueue<Integer>> map = new HashMap<>();

for(int num : nums){
PriorityQueue<Integer> last = map.getOrDefault(num - 1, null);
PriorityQueue<Integer> curr = map.getOrDefault(num, new PriorityQueue<Integer>());

if(last == null || last.size() == 0){
curr.add(1);
map.put(num, curr);
}
else{
int min = last.poll();
curr.add(min+1);
map.put(num, curr);
}
}

for(int last : map.keySet()){
for(int len : map.get(last)){
if(len < 3) return false;
}
}
return true;

}
}
823. Binary Trees With Factors

823. Binary Trees With Factors

Question

Given an array of unique integers, arr, where each integer arr[i] is strictly greater than 1.

We make a binary tree using these integers, and each number may be used for any number of times. Each non-leaf node’s value should be equal to the product of the values of its children.

Return the number of binary trees we can make. The answer may be too large so return the answer modulo 10<sup>9</sup><span> </span>+ 7.

Solution

根据题意,二叉树的根是两个子节点的乘积,因此一个根节点可组成的二叉树总数,相当于其左子节点可组成的二叉树总数,乘以其右子节点可组成的二叉树总数。

因此我们可以先将数组arr排序,用dp[]数组记录以从小到大的数字作为根节点可组成的最大二叉树总数。

由于每个根节点至少可以和自身组成一个节点,因此将dp[]数组初始化为1。

动态规划

从小到大遍历dp[]数组。
在遍历时,从遍历数值i的左侧选取当前位置的子节点。(根据题意子节点一定小于根节点)
当当前位置arr[i]可以整除arr[left]时,则有可能存在另一个子节点。此时根据哈希表中获得对应的另一个子节点right。如果right存在,则dp[i]更新为dp[i]+dp[left]*dp[right]的值。

最后将dp[]数组中记录的所有数字加和并返回。

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
class Solution {
public int numFactoredBinaryTrees(int[] arr) {
final int MOD = (int) Math.pow(10, 9) + 7;
Arrays.sort(arr); //from minium to maxium
HashMap<Integer, Integer> map = new HashMap<>();

for(int i = 0; i < arr.length; i++){
map.put(arr[i], i);
}

long[] dp = new long[arr.length];
Arrays.fill(dp, 1);

for(int i = 0; i < arr.length; i++){
for(int left = 0; left < i; left++){
if(arr[i] % arr[left] == 0){ //make sure divisible
int k = arr[i] / arr[left];
if(map.containsKey(k)){
int right = map.get(k);
dp[i] = (dp[i] + dp[left] * dp[right]) % MOD;
}
}
}
}

int res = 0;

for(long num : dp){
res += num;
res %= MOD;
}

return (int) res;
}
}
729. My Calendar I

729. My Calendar I

Question

You are implementing a program to use as your calendar. We can add a new event if adding the event will not cause a double booking.

A double booking happens when two events have some non-empty intersection (i.e., some moment is common to both events.).

The event can be represented as a pair of integers start and end that represents a booking on the half-open interval [start, end), the range of real numbers x such that start <= x < end.

Implement the MyCalendar class:

  • MyCalendar() Initializes the calendar object.
  • boolean book(int start, int end) Returns true if the event can be added to the calendar successfully without causing a double booking. Otherwise, return false and do not add the event to the calendar.

Solution

有更快速的解法,需要用到TreeMap,有时间再研究。

使用一个队列记录已经预定的范围。
添加无穷大到队列中,方便后期计算。

当预定新的时间段时,遍历队列,如果上一个数组的最大值小于start和end且当前遍历数组大于start和end,则可以预定,将当前的数组插入当前位置,并返回true。
如果没有满足条件的,则返回false。

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
class MyCalendar {
List<int[]> arr;
public MyCalendar() {
arr = new ArrayList<>();
int[] inf = new int[2];
inf[0] = Integer.MAX_VALUE;
inf[1] = Integer.MAX_VALUE;
arr.add(inf);
}

public boolean book(int start, int end) {
int[] last = new int[2];
for(int i = 0; i < arr.size(); i++){
int[] curr = arr.get(i);
if(last[1] < end && last[1] <= start && curr[0] >= end && curr[0] > start){
int[] n = new int[2];
n[0] = start;
n[1] = end;
arr.add(i, n);
return true;
}
last = curr;
}
return false;
}
}

/**
* Your MyCalendar object will be instantiated and called as such:
* MyCalendar obj = new MyCalendar();
* boolean param_1 = obj.book(start,end);
*/
114. Flatten Binary Tree to Linked List

114. Flatten Binary Tree to Linked List

Question

Given the root of a binary tree, flatten the tree into a “linked list”:

  • The “linked list” should use the same TreeNode class where the right child pointer points to the next node in the list and the left child pointer is always null.
  • The “linked list” should be in the same order as a pre-order** traversal** of the binary tree.

Solution

全局变量prev,初始化为null,用来记录上一个访问的节点。

由于最后要达到先序遍历(根节点-左子节点-右子节点)的数据结构顺序,因此在进行访问时需要与先序遍历的操作相反,首先递归右子节点,然后递归左子节点,最后修改当前节点。

先遍历当前节点的右侧节点,再遍历当前节点的左侧节点。
最后处理当前节点,将左子节点设置为null,右子节点设置为prev。
最后将当前节点保存在prev上。

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
/**
* 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 {
TreeNode prev = null;
public void flatten(TreeNode root) {
if(root == null) return;
flatten(root.right); //traverse right nodes first
flatten(root.left); //traverse left nodes
root.left = null; //set current node's left child node to null
root.right = prev; //set current node's right child node to previous node
prev = root; //update previous node
}
}
576. Out of Boundary Paths

576. Out of Boundary Paths

Question

There is an m x n grid with a ball. The ball is initially at the position [startRow, startColumn]. You are allowed to move the ball to one of the four adjacent cells in the grid (possibly out of the grid crossing the grid boundary). You can apply at most maxMove moves to the ball.

Given the five integers m, n, maxMove, startRow, startColumn, return the number of paths to move the ball out of the grid boundary. Since the answer can be very large, return it modulo 109 + 7.

Solution

DFS,记忆化搜索哦剪枝。

记忆化搜索

三维数组memo[][][]用来记录起点为startRow, startColumn,剩余距离为maxMove时的总路径数。
注意需要将memo[][][]所有数值初始化为-1。否则在进行BFS搜索时,无法对maxMove等于0的情况进行剪枝。

DFS

当当前位置越界,则为一个有效路径,返回1。
当剩余步数为0时,无法继续移动,返回0。

如果当前位置与剩余步数的状态已经被搜索并记录过,则直接返回memo[startRow][startColumn][maxMove]。
如果未搜索,则将maxMove减一,并向四周移动一格,对当前位置的四个方向进行递归。
记忆当前位置的四个方向的总路径数之和,模除后返回。

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 {
final int MOD = (int) Math.pow(10, 9) + 7;
long[][][] memo;
public int findPaths(int m, int n, int maxMove, int startRow, int startColumn) {
memo = new long[m][n][maxMove];
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
for(int k = 0; k < maxMove; k++){
memo[i][j][k] = -1;
}
}
}
return (int) dfs(m, n, maxMove, startRow, startColumn);
}

private long dfs(int m, int n, int maxMove, int startRow, int startColumn){
if(startRow < 0 || startRow >= m || startColumn < 0 || startColumn >= n) return 1;
if(maxMove == 0) return 0;

maxMove--;
if(memo[startRow][startColumn][maxMove] != -1) return memo[startRow][startColumn][maxMove];

long left = dfs(m, n, maxMove, startRow-1, startColumn);
long right = dfs(m, n, maxMove, startRow+1, startColumn);
long up = dfs(m, n, maxMove, startRow, startColumn+1);
long down = dfs(m, n, maxMove, startRow, startColumn-1);

memo[startRow][startColumn][maxMove] = (left + right + up + down) % MOD;
return memo[startRow][startColumn][maxMove];
}
}
473. Matchsticks to Square

473. Matchsticks to Square

Question

You are given an integer array matchsticks where matchsticks[i] is the length of the ith matchstick. You want to use all the matchsticks to make one square. You should not break any stick, but you can link them up, and each matchstick must be used exactly one time.

Return true if you can make this square and false otherwise.

Solution

回溯,用int[] edges记录边长。

先计算所有火柴棍的长度之和是否能被4整除,如果不能则返回false。
将所有火柴排序,以减少回溯时的计算量。

Backtracking 回溯

当遍历越界时(传入index为-1),则返回true。

每次将当前火柴matchsticks[index]添加到一个edges[i]中。
当edges[i] <= sum / 4,且向前回溯前一个位置index-1返回结果为真,则返回true。
然后将当前火柴从edges[i]中取出。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
int[] edges;
public boolean makesquare(int[] matchsticks) {
int sum = 0;
for(int i = 0; i < matchsticks.length; i++) sum += matchsticks[i];
if(sum % 4 != 0) return false;

edges = new int[4];
Arrays.sort(matchsticks);
return backtracking(matchsticks, matchsticks.length-1, sum/4);
}

public boolean backtracking(int[] matchsticks, int index, int len){
if(index == -1) return true;

for(int i = 0; i < 4; i++){
edges[i] += matchsticks[index];
if(edges[i] <= len && backtracking(matchsticks, index - 1, len)) return true;
edges[i] -= matchsticks[index];
}
return false;
}
}
128. Longest Consecutive Sequence

128. Longest Consecutive Sequence

Question

Given an unsorted array of integers nums, return the length of the longest consecutive elements sequence.

You must write an algorithm that runs in O(n) time.

Solution 1

哈希表,先将所有元素加入哈希表。
然后遍历哈希表,如果表内没有当前数字-1时(没有更小的连续数字),则将temp初始化为1。
当表内有下一个连续数字时,将temp和curNum增加1。

当没有下一个连续数字时,更新结果到res上。

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
class Solution {
public int longestConsecutive(int[] nums) {
HashSet<Integer> set = new HashSet<>();

for(int num : nums){
set.add(num);
}

int res = 0;

for(int num : set){
if(!set.contains(num - 1)){
int temp = 1;
int curNum = num;
while(set.contains(curNum + 1)){
temp++;
curNum++;
}
res = Math.max(res, temp);
}
}
return res;
}
}

Solution 2

先排序,再遍历。
维护一个最大长度res。
用一个temp参数记录连续长度。
如果当前值是上一个值+1,则temp+1。
如果当前值等于上一个值,则continue。
其他情况则更新最大长度res,并将temp恢复为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
class Solution {
public int longestConsecutive(int[] nums) {
if(nums.length == 0) return 0;
Arrays.sort(nums);
int res = 0, temp = 1;

for(int i = 1; i < nums.length; i++){
if(nums[i] == nums[i-1] + 1){
temp++;
}
else if(nums[i] == nums[i-1]){
continue;
}
else{
res = Math.max(res, temp);
temp = 1;
}
}
res = Math.max(res, temp);

return res;
}
}

2317. Maximum XOR After Operations

Question

You are given a 0-indexed integer array nums. In one operation, select any non-negative integer x and an index i, then update nums[i] to be equal to nums[i] AND (nums[i] XOR x).

Note that AND is the bitwise AND operation and XOR is the bitwise XOR operation.

Return the maximum possible bitwise XOR of all elements of nums after applying the operation any number of times.

Solution

异或运算相当于不进位的加和
和运算相当于掩码操作
或运算相当于保留该位上的1

由于我们要对所有数字进行异或运算,因此只要能改变某一位对应的数字,我们就可以确保这一位在进行异或运算时结果可以为1。(当不改变时改为的异或运算结果为0,则我们只需要改变改为即可得到1)

将所有我们可以操作的位置变为1,就可以得到最大值。

因此,我们只需要确定哪些位是我们可以操作的即可:

  • nums[i]与任意正整数进行异或运算可以得到任意整数。在这个条件下我们可以得到任意的32位整数。
  • 然而和运算相当于掩码操作,如果nums[i]对应二进制的某一位上位0,则我们无法通过和运算将这一位改变为1。

只要该位出现过1,则我们可以控制这个位。因此我们可以通过或运算所有数字,保留所有可控制的位。

Code

1
2
3
4
5
6
7
8
9
class Solution {
public int maximumXOR(int[] nums) {
int sum = 0;
for(int num : nums){
sum |= num;
}
return sum;
}
}

2316. Count Unreachable Pairs of Nodes

Question

You are given an integer n. There is an undirected graph with n nodes, numbered from 0 to n - 1. You are given a 2D integer array edges where edges[i] = [a<sub>i</sub>, b<sub>i</sub>] denotes that there exists an undirected edge connecting nodes a<sub>i</sub> and b<sub>i</sub>.

Return the number of pairs of different nodes that are unreachable from each other.

Solution

并查集,将所有元素union,然后计算每个元素所在集之外的元素和相加即可。
注意由于是无向图,因此计算加和时每两个点之间都计算了两次,因此需要将结果除以2。

路径压缩

在进行find()查找时,将经过的每一个节点设置为根节点。
这样做可以有效的降低树高,减少操作次数。
采用递归的形式实现。

按秩合并

用size[]数组记录当前位置的节点数量。
在合并两个数字时,将秩较小的数字指向秩较大的数字。
并更新根节点的size[]值。

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
class Solution {
public long countPairs(int n, int[][] edges) {
UnionFind uf = new UnionFind(n);
long res = 0;
for(int[] edge : edges){
uf.union(edge[0], edge[1]);
}

for(int i = 0; i < n; i++){
res += ( n - uf.getSize(i) );
}
return res/2; //无向图,因此每两个节点计算了两次
}

class UnionFind {
int[] parent;
int[] size;

public UnionFind(int n){
parent = new int[n];
size = new int[n];
for(int i = 0; i < n; i++){
parent[i] = i;
size[i] = 1;
}
}

private int find(int id){
return parent[id] == id ? id : find(parent[id]); //路径压缩,将路径上的所有节点归结到根节点上
}

private boolean union(int id1, int id2){
int p1 = find(id1);
int p2 = find(id2);
if(p1 == p2) return false;
if(size[p1] > size[p2]){ //按秩进行压缩
parent[p2] = p1;
size[p1] += size[p2];
}
else{
parent[p1] = p2;
size[p2] += size[p1];
}
return true;
}

public int getSize(int num){
return size[find(num)];
}
}
}****
1642. Furthest Building You Can Reach

1642. Furthest Building You Can Reach

Question

You are given an integer array heights representing the heights of buildings, some bricks, and some ladders.

You start your journey from building 0 and move to the next building by possibly using bricks or ladders.

While moving from building i to building i+1 (0-indexed),

  • If the current building’s height is greater than or equal to the next building’s height, you do not need a ladder or bricks.

  • If the current building’s height is less than the next building’s height, you can either use one ladder or (h[i+1] - h[i]) bricks.

Return the furthest building index (0-indexed) you can reach if you use the given ladders and bricks optimally.

Solution

贪心算法+优先级队列。

从第二栋楼开始遍历当前位置,下一栋楼与当前位置的高度差为h。
如果h小于0,则无成本前进。
否则如果剩余砖块,则优先使用砖块,并将使用砖块的个数加入大根堆中。
如果剩余砖块不足,且有梯子剩余时,用梯子替换掉小号最多砖块的位置,增加剩余砖块的数量。
如果剩余砖块和梯子都不足,则返回上一个位置。

如果遍历到最后,则返回最后一个建筑物的位置。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public int furthestBuilding(int[] heights, int bricks, int ladders) {
PriorityQueue<Integer> pq = new PriorityQueue<>(Collections.reverseOrder());
for(int i = 1; i < heights.length; i++){
int h = heights[i] - heights[i-1];
if(h > 0){
pq.add(h);
bricks-=h;
if(bricks < 0){
if(ladders > 0){
ladders--;
bricks += pq.poll();
}
else{
return i-1;
}
}
}
}
return heights.length - 1;
}
}
1268. Search Suggestions System

1268. Search Suggestions System

Question

You are given an array of strings products and a string searchWord.

Design a system that suggests at most three product names from products after each character of searchWord is typed. Suggested products should have common prefix with searchWord. If there are more than three products with a common prefix return the three lexicographically minimums products.

Return a list of lists of the suggested products after each character of searchWord is typed.

Solution

字典树+优先级队列。
在TrieNode节点中保存PriorityQueue,以记录该节点下的三个单词。
注意PriorityQueue中的String需要按倒序排列,以保证维护时可以直接poll掉字符顺序较后的单词。

add()方法

创建并添加TrieNode节点,同时在移动当前节点位置时,需要维护PriorityQueue,当长度大于3时,则挤出字典顺序排列较后的单词。

search()方法

在查找前缀时,将每个TrieNode节点上的PriorityQueue倒序加入List。

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
class Solution {
class TrieNode{
boolean end;
TrieNode[] children = new TrieNode[26];
PriorityQueue<String> q = new PriorityQueue<>((a, b) -> b.compareTo(a));
}

TrieNode root;

public List<List<String>> suggestedProducts(String[] products, String searchWord) {
List<List<String>> ret = new ArrayList<>();
root = new TrieNode();
for(String word : products){
add(word);
}
return search(searchWord);
}

private void add(String word){
TrieNode curr = root;
for(char c : word.toCharArray()){
int i = c - 'a';
if(curr.children[i] == null) curr.children[i] = new TrieNode();
curr = curr.children[i];
curr.q.add(word);
if(curr.q.size() > 3) curr.q.poll();
}
curr.end = true;
}

private List<List<String>> search(String prefix){
List<List<String>> ret = new ArrayList<>();
TrieNode curr = root;
for(char c : prefix.toCharArray()){
int i = c - 'a';
if(curr.children[i] == null) curr.children[i] = new TrieNode();
curr = curr.children[i];
List<String> temp = new ArrayList<>();
while(!curr.q.isEmpty()){
temp.add(curr.q.poll());
}
Collections.reverse(temp);
ret.add(temp);
}
return ret;
}
}

2300. Successful Pairs of Spells and Potions

Question

You are given two positive integer arrays spells and potions, of length n and m respectively, where spells[i] represents the strength of the i<sup>th</sup> spell and potions[j] represents the strength of the j<sup>th</sup> potion.

You are also given an integer success. A spell and potion pair is considered successful if the product of their strengths is at least success.

Return an integer array pairs of length n where pairs[i] is the number of potions that will form a successful pair with the i<sup>th</sup> spell.

Solution 1

排序+二分搜索。同时记录success与spells[i]的比值来减小计算量。

排序

首先对potions[]进行排序,这样可以使用二分搜索查找分界值。
数组scales[]记录success与spells[i]的比值,以此为界大于等于scales[i]的位置都可以计入ret[]数组。

二分搜索

这里有一些tricky。

由于Arrays.binarySearch()方法无法返回重复的数字,因此在搜索时我们将查找值scale减去一个小值,保证在搜索时一定返回负值。(查找值的插入位置的负数-1)
将ret[i]记录为potions[]的总数减去正确的插入位置即可。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
public int[] successfulPairs(int[] spells, int[] potions, long success) {
int[] ret = new int[spells.length];

Arrays.sort(potions);
double[] scales = new double[potions.length];

for(int i = 0; i < potions.length; i++){
scales[i] = (double) potions[i];
}

for(int i = 0; i < spells.length; i++){
double scale = (double) success / spells[i] - 0.000001; //确保浮点数不在scale中出现,binarySearch方法返回的结果必定为上一个插入位置
int index = Arrays.binarySearch(scales, scale);
ret[i] = potions.length + (index + 1);
}
return ret;
}
}

Solution 2

由于Arrays.binarySearch()无法正确的搜索有重复元素的数组,因此采用辅助方法binarySearch()来搜索最左侧的下标。

直接在binarySearch()方法中查找target,比较的对象为spell和potions[i]的乘积。

为了搜寻重复的第一个元素,当遇到target时不直接返回,而是继续修改right的位置,直到left等于right。

如果未搜索到,则返回数组的总长度。

Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Solution {
public int[] successfulPairs(int[] spells, int[] potions, long success) {
int[] ret = new int[spells.length];
Arrays.sort(potions);
for(int i = 0; i < spells.length; i++){
int index = binarySearch(potions, spells[i], success);
ret[i] = potions.length - index;
}
return ret;
}

private int binarySearch(int[] potions, long spell, long target){
int left = 0, right = potions.length - 1;
while(left < right){
int mid = left + (right - left) / 2;
if(potions[mid] * spell < target) left = mid + 1;
else right = mid;
}
return potions[left] * spell < target ? potions.length : left;
}
}