Arrays and Strings Questions (Part 2)

LiveRunGrow
38 min readAug 14, 2023
Took this photo at Beihai Park, Beijing in 2019. I hope this photo can bring me luck 🍀

Continuation of

Just decided to create a new article because the previous one was getting too long.

271. Encode and Decode Strings

Design an algorithm to encode a list of strings to a string. The encoded string is then sent over the network and is decoded back to the original list of strings.

Machine 1 (sender) has the function:

string encode(vector<string> strs) {
// ... your code
return encoded_string;
}

Machine 2 (receiver) has the function:

vector<string> decode(string s) {
//... your code
return strs;
}

So Machine 1 does:

string encoded_string = encode(strs);

and Machine 2 does:

vector<string> strs2 = decode(encoded_string);

strs2 in Machine 2 should be the same as strs in Machine 1.

Implement the encode and decode methods.

You are not allowed to solve the problem using any serialize methods (such as eval).

Example 1:

Input: dummy_input = ["Hello","World"]
Output: ["Hello","World"]
Explanation:
Machine 1:
Codec encoder = new Codec();
String msg = encoder.encode(strs);
Machine 1 ---msg---> Machine 2
Machine 2:
Codec decoder = new Codec();
String[] strs = decoder.decode(msg);

Example 2:

Input: dummy_input = [""]
Output: [""]

Not sure what’s the point of this questions..but here is my solution

public class Codec {


Map<String, List<String>> map = new HashMap<>();

// Encodes a list of strings to a single string.
public String encode(List<String> strs) {
String result = "";
for(String s: strs){
result+=s;
}
map.put(result, strs);
return result;
}

// Decodes a single string to a list of strings.
public List<String> decode(String s) {
return map.getOrDefault(s, new LinkedList<>());
}
}

// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.decode(codec.encode(strs));

443. String Compression

Given an array of characters chars, compress it using the following algorithm:

Begin with an empty string s. For each group of consecutive repeating characters in chars:

  • If the group’s length is 1, append the character to s.
  • Otherwise, append the character followed by the group’s length.

The compressed string s should not be returned separately, but instead, be stored in the input character array chars. Note that group lengths that are 10 or longer will be split into multiple characters in chars.

After you are done modifying the input array, return the new length of the array.

You must write an algorithm that uses only constant extra space.

Example 1:

Input: chars = ["a","a","b","b","c","c","c"]
Output: Return 6, and the first 6 characters of the input array should be: ["a","2","b","2","c","3"]
Explanation: The groups are "aa", "bb", and "ccc". This compresses to "a2b2c3".

Example 2:

Input: chars = ["a"]
Output: Return 1, and the first character of the input array should be: ["a"]
Explanation: The only group is "a", which remains uncompressed since it's a single character.

Example 3:

Input: chars = ["a","b","b","b","b","b","b","b","b","b","b","b","b"]
Output: Return 4, and the first 4 characters of the input array should be: ["a","b","1","2"].
Explanation: The groups are "a" and "bbbbbbbbbbbb". This compresses to "ab12".
class Solution {
public int compress(char[] chars) {
int i=0;
int res=0;
while(i<chars.length){
int groupLen = 1;
while(i+groupLen < chars.length && chars[i+groupLen] == chars[i]){
groupLen++;
}
chars[res++]=chars[i];
if(groupLen>1){
for (char c : Integer.toString(groupLen).toCharArray()) {
chars[res++] = c;
}
}
i+=groupLen;
}

return res;
}
}

2384. Largest Palindromic Number

You are given a string num consisting of digits only.

Return the largest palindromic integer (in the form of a string) that can be formed using digits taken from num. It should not contain leading zeroes.

Notes:

  • You do not need to use all the digits of num, but you must use at least one digit.
  • The digits can be reordered.

Example 1:

Input: num = "444947137"
Output: "7449447"
Explanation:
Use the digits "4449477" from "444947137" to form the palindromic integer "7449447".
It can be shown that "7449447" is the largest palindromic integer that can be formed.

Example 2:

Input: num = "00009"
Output: "9"
Explanation:
It can be shown that "9" is the largest palindromic integer that can be formed.
Note that the integer returned should not contain leading zeroes.

Constraints:

  • 1 <= num.length <= 105
  • num consists of digits.
class Solution {
public String largestPalindromic(String num) {
int[] count = new int[10];
for(int i=0; i<num.length(); i++){
count[num.charAt(i)- '0'] ++;
}

String result = "";
for(int i=9; i>0; i--){
while(count[i] >= 2){
result+=i; // we only add 1 copy since we are creating LHS only
count[i]-=2;
}
}

// avoid leading zeros
while(result.length()>0 && count[0]>=2){
result+="0";
count[0]-=2;
}

// Add single digit
boolean hasSingleDigit = false;
for(int i=9; i>=0; i--){
if(count[i]>0){
hasSingleDigit = true;
result+=i;
break;
}
}
String copyResult = result;
if(!hasSingleDigit) {
result += copyResult.charAt(copyResult.length()-1);
}
// populate RHS based on LHS
for(int i=copyResult.length()-2; i>=0; i--){
result+=copyResult.charAt(i);
}
return result;
}
}

The idea is to first populate LHS of the palindrome string.

527. Word Abbreviation

Given an array of distinct strings words, return the minimal possible abbreviations for every word.

The following are the rules for a string abbreviation:

  1. The initial abbreviation for each word is: the first character, then the number of characters in between, followed by the last character.
  2. If more than one word shares the same abbreviation, then perform the following operation:
  • Increase the prefix (characters in the first part) of each of their abbreviations by 1.
  • For example, say you start with the words ["abcdef","abndef"] both initially abbreviated as "a4f". Then, a sequence of operations would be ["a4f","a4f"] -> ["ab3f","ab3f"] -> ["abc2f","abn2f"].
  • This operation is repeated until every abbreviation is unique.
  1. At the end, if an abbreviation did not make a word shorter, then keep it as the original word.

Example 1:

Input: words = ["like","god","internal","me","internet","interval","intension","face","intrusion"]
Output: ["l2e","god","internal","me","i6t","interval","inte4n","f2e","intr4n"]

Example 2:

Input: words = ["aa","aaa"]
Output: ["aa","aaa"]
class Solution {
public List<String> wordsAbbreviation(List<String> words) {
Map<String, List<IndexedWord>> groups = new HashMap();
String[] ans = new String[words.size()];

int index = 0;
for(String word: words){
String ab = abbrev(word, 0);
if(!groups.containsKey(ab)){
groups.put(ab, new ArrayList());
}
groups.get(ab).add(new IndexedWord(word, index));
index++;
}

for(List<IndexedWord> group: groups.values()){
Collections.sort(group, (a, b) -> a.word.compareTo(b.word));

int[] lcp = new int[group.size()];
for(int i=1; i<group.size(); ++i){
int p = longestCommonPrefix(group.get(i-1).word, group.get(i).word);
lcp[i]=p;
lcp[i-1]=Math.max(lcp[i-1], p);
}

for(int i=0; i<group.size(); ++i){
ans[group.get(i).index] = abbrev(group.get(i).word, lcp[i]);
}
}

return Arrays.asList(ans);
}

public String abbrev(String word, int i) {
int N = word.length();
if (N - i <= 3) return word;
return word.substring(0, i+1) + (N - i - 2) + word.charAt(N-1);
}

public int longestCommonPrefix(String word1, String word2) {
int i = 0;
while (i < word1.length() && i < word2.length()
&& word1.charAt(i) == word2.charAt(i))
i++;
return i;
}
}

class IndexedWord {
String word;
int index;
IndexedWord(String w, int i) {
word = w;
index = i;
}
}

I copied this answer from the editorial…

2817. Minimum Absolute Difference Between Elements With Constraint

You are given a 0-indexed integer array nums and an integer x.

Find the minimum absolute difference between two elements in the array that are at least x indices apart.

In other words, find two indices i and j such that abs(i - j) >= x and abs(nums[i] - nums[j]) is minimized.

Return an integer denoting the minimum absolute difference between two elements that are at least x indices apart.

Example 1:

Input: nums = [4,3,2,4], x = 2
Output: 0
Explanation: We can select nums[0] = 4 and nums[3] = 4.
They are at least 2 indices apart, and their absolute difference is the minimum, 0.
It can be shown that 0 is the optimal answer.

Example 2:

Input: nums = [5,3,2,10,15], x = 1
Output: 1
Explanation: We can select nums[1] = 3 and nums[2] = 2.
They are at least 1 index apart, and their absolute difference is the minimum, 1.
It can be shown that 1 is the optimal answer.

Example 3:

Input: nums = [1,2,3,4], x = 3
Output: 3
Explanation: We can select nums[0] = 1 and nums[3] = 4.
They are at least 3 indices apart, and their absolute difference is the minimum, 3.
It can be shown that 3 is the optimal answer.

Leet Code Solution posted by another user

Intuition

The problem essentially asks us to find two numbers in the array that:

  1. Have a minimum difference in their values.
  2. Their positions are at least x apart.

Simple Case: Ignoring the Position Condition
Without the second condition about the indices being x apart, the problem is simpler: you'd want to find two numbers in the array with the smallest difference between them. A straightforward way to achieve this would be to sort the numbers and then compare adjacent numbers to find the smallest difference. But for the current problem, sorting is not ideal because we lose the original indices of the elements.

However, to utilize this idea, we can use a TreeSet. It helps us maintain an ordered set of numbers and lets us search for the closest numbers efficiently.

Incorporating the Index Distance Constraint
Now, we need to incorporate the condition that the two numbers we pick should be at least x indices apart.

A neat approach is to iterate through the list of numbers backwards. As we move back, for every element, we check the difference with numbers that are x indices ahead and add them to our TreeSet. This way, our TreeSet always contains elements that are at least x indices ahead of our current element.

For every current element, we look for the nearest larger and smaller numbers present in the TreeSet (using the higher and lower methods). These will give us the smallest possible differences satisfying the conditions.

Complexity

  • Time complexity:
    O( N log N ) : The iteration over the numbers is
    O(N), and the TreeSet operations (add, higher, lower) are O(log N), O(logN) and O(logN) respectively.
  • Space complexity:
    O ( N ) — We’re using a TreeSet to store up to N numbers
class Solution {

public int minAbsoluteDifference(List<Integer> nums, int x) {
int n = nums.size();
TreeSet<Integer> set = new TreeSet<>();
int result = Integer.MAX_VALUE;
for(int i=n-1-x; i>=0; i--){
int indexAhead = i+x;
set.add(nums.get(indexAhead));

int element = nums.get(i);
Integer min = set.lower(element+1);
Integer max = set.higher(element-1);

if(min != null){
result = Math.min(result, Math.abs(min-element));
}
if(max!=null){
result = Math.min(result, Math.abs(max-element));
}
}
return result;
}
}

311. Sparse Matrix Multiplication

Given two sparse matrices mat1 of size m x k and mat2 of size k x n, return the result of mat1 x mat2. You may assume that multiplication is always possible.

Example 1:

Input: mat1 = [[1,0,0],[-1,0,3]], mat2 = [[7,0,0],[0,0,0],[0,0,1]]
Output: [[7,0,0],[-7,0,3]]

Example 2:

Input: mat1 = [[0]], mat2 = [[0]]
Output: [[0]]
class Solution {
public int[][] multiply(int[][] mat1, int[][] mat2) {
int[][] result = new int[mat1.length][mat2[0].length];
for(int i=0; i<result.length; i++){
for(int j=0; j<result[0].length; j++){
int[] row = mat1[i];
int[] colInRowForm = getCol(mat2, j);
int val = 0;
for(int w=0; w<row.length; w++){
val+=row[w]*colInRowForm[w];
}
result[i][j]=val;
}
}
return result;
}

public int[] getCol(int[][] mat2, int j){
int[] result = new int[mat2.length];
int r = 0;
for(int i=0; i<mat2.length; i++){
result[r++]=mat2[i][j];
}
return result;
}
}

Key is to think of a formula for matrix multiplication.

2131. Longest Palindrome by Concatenating Two Letter Words

You are given an array of strings words. Each element of words consists of two lowercase English letters.

Create the longest possible palindrome by selecting some elements from words and concatenating them in any order. Each element can be selected at most once.

Return the length of the longest palindrome that you can create. If it is impossible to create any palindrome, return 0.

A palindrome is a string that reads the same forward and backward.

Example 1:

Input: words = ["lc","cl","gg"]
Output: 6
Explanation: One longest palindrome is "lc" + "gg" + "cl" = "lcggcl", of length 6.
Note that "clgglc" is another longest palindrome that can be created.

Example 2:

Input: words = ["ab","ty","yt","lc","cl","ab"]
Output: 8
Explanation: One longest palindrome is "ty" + "lc" + "cl" + "yt" = "tylcclyt", of length 8.
Note that "lcyttycl" is another longest palindrome that can be created.

Example 3:

Input: words = ["cc","ll","xx"]
Output: 2
Explanation: One longest palindrome is "cc", of length 2.
Note that "ll" is another longest palindrome that can be created, and so is "xx".
class Solution {
public int longestPalindrome(String[] words) {
int result = 0;
Map<String, Integer> stringToCount = new HashMap<>();
HashSet<String> doubles = new HashSet<>(); // store elements like "aa", "bb"
for(int i=0; i<words.length; i++){
String currReversed = reverseString(words[i]);
if(stringToCount.containsKey(currReversed) && stringToCount.get(currReversed) >0){
result += 4; // found a pair
stringToCount.put(currReversed, stringToCount.get(currReversed)-1);
} else {
stringToCount.putIfAbsent(words[i], 0);
stringToCount.put(words[i], stringToCount.get(words[i])+1);
}

if(currReversed.equals(words[i])){
doubles.add(currReversed);
}
}

// we only attempt to add doubles to the result at the end
// This is so that if words contains two of a double elment, meaning, it has
// 2 of "aa", then we want to make sure they are added above first.
// At the end here, we will just choose one of the doubles element and
// set that to be the central element of the final result...hence just add 2.
for(String d: doubles){
int count = stringToCount.get(d);
if(count == 1){
result+=2;
break;
}
}
return result;
}
public String reverseString(String str){
String result = "";
for(int i=str.length()-1; i>=0; i--){
result+=str.charAt(i);
}
return result;
}
}

68. Text Justification

Given an array of strings words and a width maxWidth, format the text such that each line has exactly maxWidth characters and is fully (left and right) justified.

You should pack your words in a greedy approach; that is, pack as many words as you can in each line. Pad extra spaces ' ' when necessary so that each line has exactly maxWidth characters.

Extra spaces between words should be distributed as evenly as possible. If the number of spaces on a line does not divide evenly between words, the empty slots on the left will be assigned more spaces than the slots on the right.

For the last line of text, it should be left-justified, and no extra space is inserted between words.

Note:

  • A word is defined as a character sequence consisting of non-space characters only.
  • Each word’s length is guaranteed to be greater than 0 and not exceed maxWidth.
  • The input array words contains at least one word.

Example 1:

Input: words = ["This", "is", "an", "example", "of", "text", "justification."], maxWidth = 16
Output:
[
"This is an",
"example of text",
"justification. "
]

Example 2:

Input: words = ["What","must","be","acknowledgment","shall","be"], maxWidth = 16
Output:
[
"What must be",
"acknowledgment ",
"shall be "
]
Explanation: Note that the last line is "shall be " instead of "shall be", because the last line must be left-justified instead of fully-justified.
Note that the second line is also left-justified because it contains only one word.

Example 3:

Input: words = ["Science","is","what","we","understand","well","enough","to","explain","to","a","computer.","Art","is","everything","else","we","do"], maxWidth = 20
Output:
[
"Science is what we",
"understand well",
"enough to explain to",
"a computer. Art is",
"everything else we",
"do "
]
class Solution {
public List<String> fullJustify(String[] words, int maxWidth) {
List<String> ans = new ArrayList<>();
int i = 0;

while (i < words.length) {
List<String> currentLine = getWords(i, words, maxWidth);
i += currentLine.size();
ans.add(createLine(currentLine, i, words, maxWidth));
}

return ans;
}

private List<String> getWords(int i, String[] words, int maxWidth) {
List<String> currentLine = new ArrayList<>();
int currLength = 0;

while (i < words.length && currLength + words[i].length() <= maxWidth) {
currentLine.add(words[i]);
currLength += words[i].length() + 1;
i++;
}

return currentLine;
}

private String createLine(List<String> line, int i, String[] words, int maxWidth) {
int baseLength = -1;
for (String word: line) {
baseLength += word.length() + 1;
}

int extraSpaces = maxWidth - baseLength;

if (line.size() == 1 || i == words.length) {
return String.join(" ", line) + " ".repeat(extraSpaces);
}

int wordCount = line.size() - 1;
int spacesPerWord = extraSpaces / wordCount;
int needsExtraSpace = extraSpaces % wordCount;

for (int j = 0; j < needsExtraSpace; j++) {
line.set(j, line.get(j) + " ");
}
for (int j = 0; j < wordCount; j++) {
line.set(j, line.get(j) + " ".repeat(spacesPerWord));
}

// Add the default 1 space between words in a single line
return String.join(" ", line);
}
}

896. Monotonic Array

An array is monotonic if it is either monotone increasing or monotone decreasing.

An array nums is monotone increasing if for all i <= j, nums[i] <= nums[j]. An array nums is monotone decreasing if for all i <= j, nums[i] >= nums[j].

Given an integer array nums, return true if the given array is monotonic, or false otherwise.

Example 1:

Input: nums = [1,2,2,3]
Output: true

Example 2:

Input: nums = [6,5,4,4]
Output: true

Example 3:

Input: nums = [1,3,2]
Output: false
class Solution {
public boolean isMonotonic(int[] A) {
boolean increasing = true;
boolean decreasing = true;
for (int i = 0; i < A.length - 1; ++i) {
if (A[i] > A[i+1])
increasing = false;
if (A[i] < A[i+1])
decreasing = false;
}

return increasing || decreasing;
}
}

My initial solution was overly complex. Here is one copied from the Editorial.

1235. Maximum Profit in Job Scheduling

We have n jobs, where every job is scheduled to be done from startTime[i] to endTime[i], obtaining a profit of profit[i].

You’re given the startTime, endTime and profit arrays, return the maximum profit you can take such that there are no two jobs in the subset with overlapping time range.

If you choose a job that ends at time X you will be able to start another job that starts at time X.

Example 1:

Input: startTime = [1,2,3,3], endTime = [3,4,5,6], profit = [50,10,40,70]
Output: 120
Explanation: The subset chosen is the first and fourth job.
Time range [1-3]+[3-6] , we get profit of 120 = 50 + 70.

Example 2:

Input: startTime = [1,2,3,4,6], endTime = [3,5,10,6,9], profit = [20,20,100,70,60]
Output: 150
Explanation: The subset chosen is the first, fourth and fifth job.
Profit obtained 150 = 20 + 70 + 60.

Example 3:

Input: startTime = [1,1,1], endTime = [2,3,4], profit = [5,6,4]
Output: 6

Constraints:

  • 1 <= startTime.length == endTime.length == profit.length <= 5 * 104
  • 1 <= startTime[i] < endTime[i] <= 109
  • 1 <= profit[i] <= 104

Editorial solution

Notice that for every new job, we iterate over all of the previous job chains to find the most profitable chain that ends at or before the current job starts. This results in O(N2)O(N²)O(N2) time complexity. Can we do better?

Let’s take a moment and think about how we can optimize this approach.

There are two key observations to make:

  1. For each job we want to find all chains that end before the current job’s start time.
  2. Since the jobs are sorted according to start time if a chain does not conflict with the current job, then it will also not conflict with any future job.

The first observation tells us that we want to store the existing chains so that those with the earliest end time can be accessed efficiently. The second observation tells us that we do not need to remember chains that have ended, we only need to remember the maximum profit from any chain that has ended. These observations (accessing chains that have earlier end times and removing them from the data structure) hint that a heap is an efficient data structure to store the chains.

Algorithm

We will iterate over the jobs from left to right and for each job, we will check if this job can be appended to any previous chain of jobs by popping out the chains from the heap. Among all chains that do not conflict with the current job, we will append the current job to the chain with the highest profit. When we append the current job to the highest profit chain, we form a new chain. This job chain will then be pushed into the heap as a pair of the end time and the total profit (the current job’s profit plus maximum profit from non-conflicting chains).

However, there is a tricky part here. When we pop a job chain from the heap we don’t push it back into the heap, although this job chain can still be combined with other jobs in the future. To handle this case we introduce a variable maxProfit whenever we pop a job chain from heap we compare its profit with the maxProfit and update it accordingly. The reason is that the jobs are sorted and hence if we pop out a chain from the heap for the ith job then it implies that this chain can be combined with any other job that comes after index i. Therefore we only need to store the chain's profit. Furthermore, since it is always optimal to use the most profitable chain, we only need to keep track of the maximum profit seen so far. Formally at the time of ith iteration, the value of maxProfit will be the maximum profit of a set of job chains to which the ith job can be appended.

  1. Store the startTime, endTime, and profit of each job in jobs.
  2. Sort the jobs according to their starting time.
  3. Iterate over jobs from left to right, where i is the index of the current job. For each job
  • While the job chain at the top of the priority queue does not conflict with the current job, pop from the priority queue.
  • For each popped job chain compare its total profit with the maxProfit and update maxProfit accordingly.

4. Push the pair of ending time and the combined profit (maxProfit + profit of this job) into the heap. This item represents the chain created by adding the current job to the most profitable job chain.

5. After iterating over all the jobs, compare the profit of the remaining chains in the priority queue with the maxProfit. Return the value of maxProfit.

class Solution {
public int jobScheduling(int[] startTime, int[] endTime, int[] profit) {
List<Unit> jobs = new ArrayList<>();
int length = profit.length;
for(int i=0; i<length; i++){
jobs.add(new Unit(startTime[i], endTime[i], profit[i]));
}
jobs.sort(Comparator.comparingInt(a -> a.start));
return findMaxProfits(jobs);
}
public int findMaxProfits(List<Unit> jobs){
int n = jobs.size();
int maxProfit = 0;
PriorityQueue<ArrayList<Integer>> pq = new PriorityQueue<>((a, b) -> a.get(0)-b.get(0));

for(int i=0; i<n; i++){
int start = jobs.get(i).start;
int end = jobs.get(i).end;
int profit = jobs.get(i).profit;

// at each iteration we discard all chains that end before the current job starts.
// we discard all the chains that are compatible with the current job. Since all future jobs have a greater start-time, all the chains we discarded will be compatible with those future jobs too
while(!pq.isEmpty() && start >= pq.peek().get(0)){
maxProfit = Math.max(maxProfit, pq.peek().get(1));
pq.remove();
}

ArrayList<Integer> combinedJob = new ArrayList<>();
combinedJob.add(end);
combinedJob.add(profit + maxProfit);
pq.add(combinedJob);
}

while(!pq.isEmpty()){
maxProfit = Math.max(maxProfit, pq.peek().get(1));
pq.remove();
}
return maxProfit;
}
class Unit{
int start;
int end;
int profit;
public Unit(int start, int end, int profit){
this.start = start;
this.end = end;
this.profit = profit;
}
}
}

1861. Rotating the Box

You are given an m x n matrix of characters box representing a side-view of a box. Each cell of the box is one of the following:

  • A stone '#'
  • A stationary obstacle '*'
  • Empty '.'

The box is rotated 90 degrees clockwise, causing some of the stones to fall due to gravity. Each stone falls down until it lands on an obstacle, another stone, or the bottom of the box. Gravity does not affect the obstacles’ positions, and the inertia from the box’s rotation does not affect the stones’ horizontal positions.

It is guaranteed that each stone in box rests on an obstacle, another stone, or the bottom of the box.

Return an n x m matrix representing the box after the rotation described above.

Example 1:

Input: box = [["#",".","#"]]
Output: [["."],
["#"],
["#"]]

Example 2:

Input: box = [["#",".","*","."],
["#","#","*","."]]
Output: [["#","."],
["#","#"],
["*","*"],
[".","."]]

Example 3:

Input: box = [["#","#","*",".","*","."],
["#","#","#","*",".","."],
["#","#","#",".","#","."]]
Output: [[".","#","#"],
[".","#","#"],
["#","#","*"],
["#","*","."],
["#",".","*"],
["#",".","."]]
 class Solution {
public char[][] rotateTheBox(char[][] box) {
int row = box.length, col = box[0].length;
char[][] res = new char[col][row];
// rotate first, then drop
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
res[j][i] = box[row-1-i][j];
}
}

for (int i = col - 1; i >= 0; i--) {
for (int j = 0; j < row; j++) { // j can beincreasing or decreasing
if (res[i][j] == '#') {
int curRow = i;
while (curRow+1 < col && res[curRow+1][j] == '.') {
curRow++; // drop as long as is empty space
}
if (curRow != i) {
res[curRow][j] = '#';
res[i][j] = '.';
}
}
}
}
return res;
}
}

First rotate the box….

Then, drop each stone….starting from the bottom. The column does not matter.

658. Find K Closest Elements

Given a sorted integer array arr, two integers k and x, return the k closest integers to x in the array. The result should also be sorted in ascending order.

An integer a is closer to x than an integer b if:

  • |a - x| < |b - x|, or
  • |a - x| == |b - x| and a < b

Example 1:

Input: arr = [1,2,3,4,5], k = 4, x = 3
Output: [1,2,3,4]

Example 2:

Input: arr = [1,2,3,4,5], k = 4, x = -1
Output: [1,2,3,4]
class Solution {
public List<Integer> findClosestElements(int[] arr, int k, int x) {
List<Integer> result = new LinkedList<>();
for(int i=0; i<arr.length; i++){
if(result.size()<k){
result.add(arr[i]);
} else{
int currentDiff = Math.abs(arr[i]-x);
int firstDiff = Math.abs(result.get(0)-x);
if(currentDiff<firstDiff){
result.remove(0);
result.add(arr[i]);
}

}
}
return result;
}
}

665. Non-decreasing Array

Given an array nums with n integers, your task is to check if it could become non-decreasing by modifying at most one element.

We define an array is non-decreasing if nums[i] <= nums[i + 1] holds for every i (0-based) such that (0 <= i <= n - 2).

Example 1:

Input: nums = [4,2,3]
Output: true
Explanation: You could modify the first 4 to 1 to get a non-decreasing array.

Example 2:

Input: nums = [4,2,1]
Output: false
Explanation: You cannot get a non-decreasing array by modifying at most one element.
class Solution {
public boolean checkPossibility(int[] nums) {
Boolean flag = false;
for(int i=1; i<nums.length; i++){
if(nums[i-1]>nums[i]){
if(flag) return false;
if(i<2 || nums[i-2] <= nums[i]){
nums[i-1]=nums[i];
}else{
nums[i]=nums[i-1];
}
flag=true;
}
}
return true;
}
}

This is quite tough.

The idea is that for each pair of numbers you are looking at, you choose to modify one of them, depending on some conditions.

725. Split Linked List in Parts

Given the head of a singly linked list and an integer k, split the linked list into k consecutive linked list parts.

The length of each part should be as equal as possible: no two parts should have a size differing by more than one. This may lead to some parts being null.

The parts should be in the order of occurrence in the input list, and parts occurring earlier should always have a size greater than or equal to parts occurring later.

Return an array of the k parts.

Example 1:

Input: head = [1,2,3], k = 5
Output: [[1],[2],[3],[],[]]
Explanation:
The first element output[0] has output[0].val = 1, output[0].next = null.
The last element output[4] is null, but its string representation as a ListNode is [].

Example 2:

Input: head = [1,2,3,4,5,6,7,8,9,10], k = 3
Output: [[1,2,3,4],[5,6,7],[8,9,10]]
Explanation:
The input has been split into consecutive parts with size difference at most 1, and earlier parts are a larger size than the later parts.
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode[] splitListToParts(ListNode head, int k) {
ListNode[] result = new ListNode[k];

int count = 0;
ListNode tempHead = head;
while(tempHead != null){
tempHead = tempHead.next;
count++;
}

int groupSize = count/k;
int remainder = count%k;
System.out.println(groupSize + " " + k);
int j=0;
while(head != null){
ListNode currHead = head;
ListNode currTail = head;
// -1 because need to count number of steps
for(int i=0; i<groupSize + (j < remainder ? 1 : 0)-1; i++){
if(currTail != null) currTail = currTail.next;
}
if(currTail != null){
head = currTail.next;
currTail.next = null;
}

result[j]=currHead;
j++;
}


return result;
}
}

843. Guess the Word

You are given an array of unique strings words where words[i] is six letters long. One word of words was chosen as a secret word.

You are also given the helper object Master. You may call Master.guess(word) where word is a six-letter-long string, and it must be from words. Master.guess(word) returns:

  • -1 if word is not from words, or
  • an integer representing the number of exact matches (value and position) of your guess to the secret word.

There is a parameter allowedGuesses for each test case where allowedGuesses is the maximum number of times you can call Master.guess(word).

For each test case, you should call Master.guess with the secret word without exceeding the maximum number of allowed guesses. You will get:

  • "Either you took too many guesses, or you did not find the secret word." if you called Master.guess more than allowedGuesses times or if you did not call Master.guess with the secret word, or
  • "You guessed the secret word correctly." if you called Master.guess with the secret word with the number of calls to Master.guess less than or equal to allowedGuesses.

The test cases are generated such that you can guess the secret word with a reasonable strategy (other than using the bruteforce method).

Example 1:

Input: secret = "acckzz", words = ["acckzz","ccbazz","eiowzz","abcczz"], allowedGuesses = 10
Output: You guessed the secret word correctly.
Explanation:
master.guess("aaaaaa") returns -1, because "aaaaaa" is not in wordlist.
master.guess("acckzz") returns 6, because "acckzz" is secret and has all 6 matches.
master.guess("ccbazz") returns 3, because "ccbazz" has 3 matches.
master.guess("eiowzz") returns 2, because "eiowzz" has 2 matches.
master.guess("abcczz") returns 4, because "abcczz" has 4 matches.
We made 5 calls to master.guess, and one of them was the secret, so we pass the test case.

Example 2:

Input: secret = "hamada", words = ["hamada","khaled"], allowedGuesses = 10
Output: You guessed the secret word correctly.
Explanation: Since there are two words, you can guess both.

Constraints:

  • 1 <= words.length <= 100
  • words[i].length == 6
  • words[i] consist of lowercase English letters.
  • All the strings of wordlist are unique.
  • secret exists in words.
  • 10 <= allowedGuesses <= 30
// Time limit exceeded - failed last test case, but the logic is good enough

class Solution {
public void findSecretWord(String[] words, Master master) {

for(int i=0, matches = 0; i<10 && matches != 6; i++){
String guess = words[words.length-1];
matches = master.guess(guess); // count how many matching chars
List<String> candidates = new ArrayList<>();
for(String word: words){
if(matches == getMatches(guess, word)){
candidates.add(word);// add words with the same num of matches
}
}

words = candidates.toArray(new String[0]);
}
}

private int getMatches(String word1, String word2){
int matches = 0;
for(int i=0; i<word1.length(); i++){
if(word1.charAt(i) == word2.charAt(i)){
matches++;
}
}
return matches;
}


}

Hangman guesser

You are to build a program to guess a list of words in hangman.

In hangman you are given a word of X letters in the form of blanks. Ex: 5 letter word, _ _ _ _ _.
You are also given a number of guesses (ex: 26). At each guess, you can pick a letter, if the letter is in the word it will be filled but if it is not then you will have lost a turn.
If you have used up all of the guesses then you will move onto the next word without guessing the word. We will tally the total number of guesses and correctly guessed words. We want to guess as many correct words in as few guesses

Your hangman program will be given 1000 words to guess. The words will be from length 4 to length 13.
Your hangman will also be given a dictionary of all the possible words to guess.
All words and guesses will be in lower case.

import java.io.*;
import java.util.*;


// Implement this!!
class WordGuesser {
ArrayList<String> originalWordDictionary;
ArrayList<String> wordDictionary;
Set<Character> guessedLetters;

public WordGuesser(ArrayList<String> wordDictionary) {
this.originalWordDictionary = wordDictionary;
this.wordDictionary = wordDictionary;
this.guessedLetters = new HashSet<>();
}

public char guessLetter(char[] wordState) {

ArrayList<String> trimmedWordDictionary = new ArrayList<>();

// TRIM DICT
this.wordDictionary.forEach((w) -> {
if(w.length() != wordState.length) return;
for(int i=0; i<wordState.length; i++){
if(wordState[i]!='_' && w.charAt(i) != wordState[i]){
return;
}
}
trimmedWordDictionary.add(w);
});

// GUESS A RANDOM CHAR from trimmedword dict
// and one that is not guessed before.
// To maximise the chance that we guess a char correctly, we track
// the count that each char appears in the dictionary word
// and return the char with the most count
Map<Character, Integer> map = new HashMap<>();
Character maxC = '_';
int maxCount = 0;
for(String dict: trimmedWordDictionary){
for(int i=0; i<wordState.length; i++){
if(wordState[i] == '_' && !guessedLetters.contains(dict.charAt(i))){
Character c = dict.charAt(i);
map.putIfAbsent(c, 0);
int count = map.get(c)+1;
map.put(c, count);
if(count>maxCount){
maxCount = count;
maxC = c;
}
}
}
}
guessedLetters.add(maxC);
this.wordDictionary = trimmedWordDictionary;
return maxC;
}

public void reset(){
guessedLetters.clear();
this.wordDictionary = this.originalWordDictionary;
}
}


class GameResult {
boolean guessedCorrectly;
int numGuesses;
int numDistictChars;


public GameResult(boolean guessedCorrectly, int numGuesses, int numDistictChars) {
this.guessedCorrectly = guessedCorrectly;
this.numGuesses = numGuesses;
this.numDistictChars = numDistictChars;
}
}


class ToyGame {
private String wordToGuess;
private ArrayList<String> wordList;

public ToyGame(ArrayList<String> wordList) {
this.wordList = wordList;
}

public void setWord(String word) {
this.wordToGuess = word;
}

public int getWordLength() {
return this.wordToGuess.length();
}


public ArrayList<Integer> submitLetter(char letter) {
ArrayList<Integer> positions = new ArrayList<Integer>();
for (int i = 0; i < this.getWordLength(); i++) {
if (this.wordToGuess.charAt(i) == letter) {
positions.add(i);
}
}
return positions;
}


private boolean isWordGuessed(char[] wordState) {
for (int i = 0; i < wordState.length; i++) {
if (wordState[i] == '_') {
return false;
}
}
return true;
}


public GameResult run(WordGuesser guesser, boolean showOutput) {
if (showOutput) {
System.out.println("Running game");
}
Random r = new Random();
this.setWord(this.wordList.get(r.nextInt(this.wordList.size())));
char[] wordState = new char[this.wordToGuess.length()];
for (int i = 0; i < wordState.length; i++) {
wordState[i] = '_';
}
for (int i = 0; i < 26; i++) {
char guess = guesser.guessLetter(wordState);
ArrayList<Integer> result = this.submitLetter(guess);
for (int j = 0; j < result.size(); j++) {
wordState[result.get(j)] = guess;
}
if (showOutput) {
System.out.printf("You guessed letter: %s\n", guess);
System.out.printf("Current word state: %s\n", new String(wordState));
}
if (this.isWordGuessed(wordState)) {
return new GameResult(true, i, (int) this.wordToGuess.chars().distinct().count());
}
}

if (showOutput) {
System.out.printf("Failed to guess word: %s\n", this.wordToGuess);
}
return new GameResult(false, 26, (int) this.wordToGuess.chars().distinct().count());
}
}




class Main {
private static ArrayList<String> WORD_DICT = new ArrayList<String>(Arrays.asList("abdominous", "alveolitis", "amimide", "amorism", "amygdala", "anacardium",
"angeline", "angiosperm", "antioxidizer", "apterium", "apyretic", "aquativeness", "araneoidea", "argyrosomus",
"arilloid", "bacillicidal", "bandeau", "belletrist", "benda", "bigarade", "biradiated", "blubberingly", "boghole",
"bookbinding", "bosthoon", "bulimia", "carburometer", "carmot", "celtish", "chenopodiales", "chiococca",
"ciceroni", "cockroach", "constabulary", "contraband", "coracomorphae", "costrel", "crinose", "curl",
"cystoscopic", "damningness", "darkskin", "deathday", "deflowerer", "delirious", "demulsify", "depolymerize",
"detruncation", "dioscorine", "disecondary", "drinker", "earnful", "electroscope", "erigenia", "evilspeaking",
"exquisitism", "finick", "fosite", "galvanism", "gamboised", "geckoid", "gorsechat", "guardianless", "gulose",
"hewel", "hogshead", "homoveratrole", "horsejockey", "hydrangeaceae", "hydrotropic", "incognizance", "indone",
"ingrained", "inmate", "invertedly", "irrelevance", "karyoschisis", "kerwham", "kobi", "laryngograph",
"lineation", "lunn", "mastigure", "mellitic", "mende", "mercurialism", "micawberism", "microgyria", "millet",
"millinering", "mischance", "misgive", "modeling", "mohel", "montepulciano", "monzonitic", "mosquitoproof",
"mugget", "myelomere", "napa", "nonlicet", "nonsequacious", "nowroze", "oclock", "odontatrophy", "overrake",
"pantostomata", "paroch", "paroptic", "particulate", "peccability", "peculiarism", "pervulgation", "phrenesis",
"pinacone", "planetlike", "planographic", "pogonion", "polymolybdate", "poppyhead", "preaccordance",
"precipitable", "precontent", "prefragrance", "prerefuse", "prismatic", "prunably", "pseudocarpous", "psychology",
"pteropodan", "puddening", "pulpal", "quadrumanous", "quickset", "realizably", "rebeget", "revere", "rewayle",
"rheostatics", "ridgepoled", "riempie", "rinneite", "rocouyenne", "roundel", "sacrospinal", "salse",
"saltatorial", "sayability", "scenarioist", "sciomantic", "scyllarus", "scythic", "semisacred", "serge",
"serpivolant", "shehitah", "sillandar", "silverfin", "slad", "smither", "snakepiece", "solecist", "sotik",
"spiculae", "stepstone", "stricker", "stroboscopic", "stubbleward", "styracin", "subnivean", "subparallel",
"subserrate", "subsist", "subversionary", "supertragical", "thuja", "tourette", "treddle", "trimestrial",
"typhosepsis", "umbrellaless", "unclassible", "uncreatedness", "unextendedly", "unfavored", "unflurried",
"ungentled", "uniocular", "unmonetary", "unpatrician", "unrefitted", "unrioted", "untainted", "valedictorily",
"veratrine", "verticomental", "vesical", "viseman", "visit", "whistlewing", "winsome", "wistlessness",
"woolstock", "zingel", "zooecia"));


public static void main(String[] args) {
WordGuesser guesser = new WordGuesser(WORD_DICT);
ToyGame toyGame = new ToyGame(WORD_DICT);
int numGames = 1000;
boolean shouldPrint = numGames == 1 ? true : false;
int totalWins = 0;
int totalGuesses = 0;
int bestPossibleScore = 0;
for (int i = 0; i < numGames; i ++) {
GameResult result = toyGame.run(guesser, shouldPrint);
if (result.guessedCorrectly) {
totalWins += 1;
}
totalGuesses += result.numGuesses;
bestPossibleScore += result.numDistictChars;
guesser.reset();
}
System.out.printf("In %s games you guessed %s words in %s guesses\n", numGames, totalWins, totalGuesses);
System.out.printf("Best possible score was: %s\n", bestPossibleScore);
}
}

1101. The Earliest Moment When Everyone Become Friends

There are n people in a social group labeled from 0 to n - 1. You are given an array logs where logs[i] = [timestampi, xi, yi] indicates that xi and yi will be friends at the time timestampi.

Friendship is symmetric. That means if a is friends with b, then b is friends with a. Also, person a is acquainted with a person b if a is friends with b, or a is a friend of someone acquainted with b.

Return the earliest time for which every person became acquainted with every other person. If there is no such earliest time, return -1.

Example 1:

Input: logs = [[20190101,0,1],[20190104,3,4],[20190107,2,3],[20190211,1,5],[20190224,2,4],[20190301,0,3],[20190312,1,2],[20190322,4,5]], n = 6
Output: 20190301
Explanation:
The first event occurs at timestamp = 20190101, and after 0 and 1 become friends, we have the following friendship groups [0,1], [2], [3], [4], [5].
The second event occurs at timestamp = 20190104, and after 3 and 4 become friends, we have the following friendship groups [0,1], [2], [3,4], [5].
The third event occurs at timestamp = 20190107, and after 2 and 3 become friends, we have the following friendship groups [0,1], [2,3,4], [5].
The fourth event occurs at timestamp = 20190211, and after 1 and 5 become friends, we have the following friendship groups [0,1,5], [2,3,4].
The fifth event occurs at timestamp = 20190224, and as 2 and 4 are already friends, nothing happens.
The sixth event occurs at timestamp = 20190301, and after 0 and 3 become friends, we all become friends.

Example 2:

Input: logs = [[0,2,0],[1,0,1],[3,0,3],[4,1,2],[7,3,1]], n = 4
Output: 3
Explanation: At timestamp = 3, all the persons (i.e., 0, 1, 2, and 3) become friends.
class Solution {
public int earliestAcq(int[][] logs, int count) {

Arrays.sort(logs, (a, b) -> a[0] - b[0]);
HashMap<Integer, Integer> individualToGroup = new HashMap<>();
HashMap<Integer, Set<Integer>> groupToIndividuals = new HashMap<>();

int groupCount = count;
for(int i=0; i<logs.length; i++){
int time = logs[i][0];
int person1 = logs[i][1];
int person2 = logs[i][2];

if(individualToGroup.containsKey(person1) && individualToGroup.containsKey(person2)){
if(individualToGroup.get(person1) < individualToGroup.get(person2)){
int person2Group = individualToGroup.get(person2);
individualToGroup.put(person2, individualToGroup.get(person1));
Set<Integer> neigh = groupToIndividuals.get(person2Group);
for(int n: neigh){
individualToGroup.put(n, individualToGroup.get(person1));
groupToIndividuals.get(individualToGroup.get(person1)).add(n);
}
groupToIndividuals.remove(person2Group);
} else if(individualToGroup.get(person1) > individualToGroup.get(person2)) {
int person1Group = individualToGroup.get(person1);
individualToGroup.put(person1, individualToGroup.get(person2));
Set<Integer> neigh = groupToIndividuals.get(person1Group);
for(int n: neigh){
individualToGroup.put(n, individualToGroup.get(person2));
groupToIndividuals.get(individualToGroup.get(person2)).add(n);
}
groupToIndividuals.remove(person1Group);
}
} else if(individualToGroup.containsKey(person1)){
int person1Group = individualToGroup.get(person1);
individualToGroup.put(person2, person1Group);
groupToIndividuals.get(person1Group).add(person2);
} else if (individualToGroup.containsKey(person2)){
int person2Group = individualToGroup.get(person2);
individualToGroup.put(person1, person2Group);
groupToIndividuals.get(person2Group).add(person1);
} else {
individualToGroup.put(person1, groupCount);
individualToGroup.put(person2, groupCount);
HashSet<Integer> friends = new HashSet<>();
friends.add(person1);
friends.add(person2);
groupToIndividuals.put(groupCount, friends);
groupCount++;
}

if(groupToIndividuals.size() == 1 && individualToGroup.size()==count) return time;
}

return -1;

}
}

My general algorithm is to assign the current pair to a group (that is not used), if both of them do not have neighbours already assigned to groups.

Otherwise, just assign the smaller group number of the two items in the pair to the item with a bigger group number. Eg, if item1 has bigger group number, we assign it to the group number of item2. Then, find the neighbours of item1 and assign them to the smaller group number as well.

Keep doing this and we expect the groups to decrease until 1.

Use HashSet and HashMap to track the information.

Leet code solution

Approach: Union-Find (Disjoint-Set)

Intuition

This problem deals with the relationship or membership of entities.
For those of you who are familiar with a data structure called Union-Find, this problem might ring a bell.
In fact, it is a perfect example that demonstrates the advantages of Union-Find data structure (also known as Disjoint-Set).

Union-Find (a.k.a Disjoint-Set) is a data structure that keeps track of the connectivities among interconnected individuals efficiently. With Union-Find, one can quickly determine which group a specific individual belongs to. In addition, one can quickly merge two individuals together along with the two groups that they belong to.

As suggested by its name, a typical Union-Find data structure usually provides two interfaces as follows:

  • find(a): this function returns the group that the individual a belongs to.
  • union(a, b): this function merges the two groups that the individuals a and b belong to respectively, if the groups are not of the same group already.

To make the union(a, b) function more useful, one can return a boolean value in the function to indicate whether the merging actually happens or not.
For example, union(a, b) would return true when a and b (and their respective groups) are merged together, and false when a and b are already in the same group and thus do not need to be merged together.

Now, imagine that we already have the above Union-Find data structure available, we can go over the problem again and try to come up with a solution using the data structure.

class Solution {
public int earliestAcq(int[][] logs, int n) {
// In order to ensure that we find the _earliest_ moment,
// first of all we need to sort the events in chronological order.
Arrays.sort(logs, new Comparator<int[]>() {
@Override
public int compare(int[] log1, int[] log2) {
Integer tsp1 = new Integer(log1[0]);
Integer tsp2 = new Integer(log2[0]);
return tsp1.compareTo(tsp2);
}
});

// Initially, we treat each individual as a separate group.
int groupCount = n;
UnionFind uf = new UnionFind(n);

for (int[] log : logs) {
int timestamp = log[0];
int friendA = log[1];
int friendB = log[2];
// We merge the groups along the way.
if (uf.union(friendA, friendB)) {
groupCount -= 1;
}

// The moment when all individuals are connected to each other.
if (groupCount == 1) {
return timestamp;
}
}

// There are still more than one groups left,
// i.e. not everyone is connected.
return -1;
}
}

Yes, talk is cheap. But still, here are a few more words to help you better understand the above code.

  • In order to discover the earliest moment, we must first ensure that we read through the logs in chronological order.
    Since there is no mentioning whether the logs are ordered or not in the problem description, we need to sort them first.
  • Once the logs are sorted by time, we then iterate through them, while applying the Union-Find data structure.
  • For each log, we connect the two individuals that were involved in the log, by applying the union(a, b) function.
  • Each log adds more connections among the individuals.
    A connection is useful if the two individuals are separated (disjoint), or redundant if the two individuals are connected already via other individuals.
  • Initially, we treat each individual as a separate group. The number of groups decreases along with the useful merging operations.
    The moment when the number of groups is reduced to one is the earliest moment when everyone becomes connected (friends).

Implementation

In the above solutions, we assume that the Union-Find data structure has been implemented.
In this section, we provide a complete solution with an optimized implementation of the Union-Find data structure.
By optimized, we apply the path compression optimization in the find(a) interface and union by rank in the union(a, b) interface.
For those of you who are not familiar with the data structure, we have an Explore Card that dives into more details, including the optimization techniques mentioned here.

class Solution {
public int earliestAcq(int[][] logs, int n) {

// First, we need to sort the events in chronological order.
Arrays.sort(logs, new Comparator<int[]>() {
@Override
public int compare(int[] log1, int[] log2) {
Integer tsp1 = new Integer(log1[0]);
Integer tsp2 = new Integer(log2[0]);
return tsp1.compareTo(tsp2);
}
});

// Initially, we treat each individual as a separate group.
int groupCount = n;
UnionFind uf = new UnionFind(n);

for (int[] log : logs) {
int timestamp = log[0], friendA = log[1], friendB = log[2];

// We merge the groups along the way.
if (uf.union(friendA, friendB)) {
groupCount -= 1;
}

// The moment when all individuals are connected to each other.
if (groupCount == 1) {
return timestamp;
}
}

// There are still more than one groups left,
// i.e. not everyone is connected.
return -1;
}
}

class UnionFind {
private int[] group;
private int[] rank;

public UnionFind(int size) {
this.group = new int[size];
this.rank = new int[size];
for (int person = 0; person < size; ++person) {
this.group[person] = person;
this.rank[person] = 0;
}
}

/** Return the id of group that the person belongs to. */
public int find(int person) {
if (this.group[person] != person)
this.group[person] = this.find(this.group[person]);
return this.group[person];
}

/**
* If it is necessary to merge the two groups that x, y belong to.
* @return true: if the groups are merged.
*/
public boolean union(int a, int b) {
int groupA = this.find(a);
int groupB = this.find(b);
boolean isMerged = false;

// The two people share the same group.
if (groupA == groupB)
return isMerged;

// Otherwise, merge the two groups.
isMerged = true;
// Merge the lower-rank group into the higher-rank group.
if (this.rank[groupA] > this.rank[groupB]) {
this.group[groupB] = groupA;
} else if (this.rank[groupA] < this.rank[groupB]) {
this.group[groupA] = groupB;
} else {
this.group[groupA] = groupB;
this.rank[groupB] += 1;
}

return isMerged;
}
}

252. Meeting Rooms

Given an array of meeting time intervals where intervals[i] = [starti, endi], determine if a person could attend all meetings.

Example 1:

Input: intervals = [[0,30],[5,10],[15,20]]
Output: false

Example 2:

Input: intervals = [[7,10],[2,4]]
Output: true

Constraints:

  • 0 <= intervals.length <= 104
  • intervals[i].length == 2
  • 0 <= starti < endi <= 106
class Solution {
public boolean canAttendMeetings(int[][] intervals) {
Arrays.sort(intervals, (a, b) -> a[0]-b[0]);

int prevEnd=0;
for(int[] i: intervals){
if(i[0] < prevEnd){
return false;
}
prevEnd = i[1];
}
return true;
}
}

253. Meeting Rooms II

Given an array of meeting time intervals intervals where intervals[i] = [starti, endi], return the minimum number of conference rooms required.

Example 1:

Input: intervals = [[0,30],[5,10],[15,20]]
Output: 2

Example 2:

Input: intervals = [[7,10],[2,4]]
Output: 1

Constraints:

  • 1 <= intervals.length <= 104
  • 0 <= starti < endi <= 106
class Solution {
public int minMeetingRooms(int[][] intervals) {

// Check for the base case. If there are no intervals, return 0
if (intervals.length == 0) {
return 0;
}

// Min heap
PriorityQueue<Integer> allocator =
new PriorityQueue<Integer>(
intervals.length,
new Comparator<Integer>() {
public int compare(Integer a, Integer b) {
return a - b;
}
});

// Sort the intervals by start time
Arrays.sort(intervals, (a, b) -> {
if(a[0] != b[0]) return a[0]-b[0];
return a[1]-b[1];
});

// Add the first meeting
allocator.add(intervals[0][1]);

// Iterate over remaining intervals
for (int i = 1; i < intervals.length; i++) {

// If the room due to free up the earliest is free, assign that room to this meeting.
if (intervals[i][0] >= allocator.peek()) {
allocator.poll();
}

// If a new room is to be assigned, then also we add to the heap,
// If an old room is allocated, then also we have to add to the heap with updated end time.
allocator.add(intervals[i][1]);
}

// The size of the heap tells us the minimum rooms required for all the meetings.
return allocator.size();
}
}

1229. Meeting Scheduler

Given the availability time slots arrays slots1 and slots2 of two people and a meeting duration duration, return the earliest time slot that works for both of them and is of duration duration.

If there is no common time slot that satisfies the requirements, return an empty array.

The format of a time slot is an array of two elements [start, end] representing an inclusive time range from start to end.

It is guaranteed that no two availability slots of the same person intersect with each other. That is, for any two time slots [start1, end1] and [start2, end2] of the same person, either start1 > end2 or start2 > end1.

Example 1:

Input: slots1 = [[10,50],[60,120],[140,210]], slots2 = [[0,15],[60,70]], duration = 8
Output: [60,68]

Example 2:

Input: slots1 = [[10,50],[60,120],[140,210]], slots2 = [[0,15],[60,70]], duration = 12
Output: []

Constraints:

  • 1 <= slots1.length, slots2.length <= 104
  • slots1[i].length, slots2[i].length == 2
  • slots1[i][0] < slots1[i][1]
  • slots2[i][0] < slots2[i][1]
  • 0 <= slots1[i][j], slots2[i][j] <= 109
  • 1 <= duration <= 106
class Solution {
public List<Integer> minAvailableDuration(int[][] slots1, int[][] slots2, int duration) {
// TODO sort
Arrays.sort(slots1, (a, b)->a[0]-b[0]);
Arrays.sort(slots2, (a, b) -> a[0]-b[0]);

/*
Overlapping scenarios
---
---

------
---

---
---
*/

int i=0;
int j=0;
while(i<slots1.length && j<slots2.length){
if(slots1[i][1]>slots2[j][0] || slots1[i][0]<slots2[j][1]){
// has overlap
List<Integer> overlap = new LinkedList<>();
overlap.add(Math.max(slots1[i][0], slots2[j][0]));
overlap.add(Math.min(slots1[i][1], slots2[j][1]));

if(overlap.get(1)-overlap.get(0) >= duration){
List<Integer> res = new LinkedList<>();
res.add(overlap.get(0));
res.add(overlap.get(0)+duration); // don't allocate more than you need
return res;
}
}

if(slots1[i][1] < slots2[j][1]){
i++; // move pointer for the slot with earlier end time
} else {
j++;
}
}

return new LinkedList<>();
}
}

2210. Count Hills and Valleys in an Array

You are given a 0-indexed integer array nums. An index i is part of a hill in nums if the closest non-equal neighbors of i are smaller than nums[i]. Similarly, an index i is part of a valley in nums if the closest non-equal neighbors of i are larger than nums[i]. Adjacent indices i and j are part of the same hill or valley if nums[i] == nums[j].

Note that for an index to be part of a hill or valley, it must have a non-equal neighbor on both the left and right of the index.

Return the number of hills and valleys in nums.

Example 1:

Input: nums = [2,4,1,1,6,5]
Output: 3
Explanation:
At index 0: There is no non-equal neighbor of 2 on the left, so index 0 is neither a hill nor a valley.
At index 1: The closest non-equal neighbors of 4 are 2 and 1. Since 4 > 2 and 4 > 1, index 1 is a hill.
At index 2: The closest non-equal neighbors of 1 are 4 and 6. Since 1 < 4 and 1 < 6, index 2 is a valley.
At index 3: The closest non-equal neighbors of 1 are 4 and 6. Since 1 < 4 and 1 < 6, index 3 is a valley, but note that it is part of the same valley as index 2.
At index 4: The closest non-equal neighbors of 6 are 1 and 5. Since 6 > 1 and 6 > 5, index 4 is a hill.
At index 5: There is no non-equal neighbor of 5 on the right, so index 5 is neither a hill nor a valley.
There are 3 hills and valleys so we return 3.

Example 2:

Input: nums = [6,6,5,5,4,1]
Output: 0
Explanation:
At index 0: There is no non-equal neighbor of 6 on the left, so index 0 is neither a hill nor a valley.
At index 1: There is no non-equal neighbor of 6 on the left, so index 1 is neither a hill nor a valley.
At index 2: The closest non-equal neighbors of 5 are 6 and 4. Since 5 < 6 and 5 > 4, index 2 is neither a hill nor a valley.
At index 3: The closest non-equal neighbors of 5 are 6 and 4. Since 5 < 6 and 5 > 4, index 3 is neither a hill nor a valley.
At index 4: The closest non-equal neighbors of 4 are 5 and 1. Since 4 < 5 and 4 > 1, index 4 is neither a hill nor a valley.
At index 5: There is no non-equal neighbor of 1 on the right, so index 5 is neither a hill nor a valley.
There are 0 hills and valleys so we return 0.
class Solution {
public int countHillValley(int[] nums) {
int count = 0;
for(int i=1; i<nums.length-1; i++){
if(nums[i]>nums[i-1] && nums[i]>nums[i+1]){
count++;
}
if(nums[i]<nums[i-1] && nums[i]<nums[i+1]){
count++;
}
if(nums[i] == nums[i+1]){
nums[i] = nums[i-1];
}
}
return count;
}
}

953. Verifying an Alien Dictionary

In an alien language, surprisingly, they also use English lowercase letters, but possibly in a different order. The order of the alphabet is some permutation of lowercase letters.

Given a sequence of words written in the alien language, and the order of the alphabet, return true if and only if the given words are sorted lexicographically in this alien language.

Example 1:

Input: words = ["hello","leetcode"], order = "hlabcdefgijkmnopqrstuvwxyz"
Output: true
Explanation: As 'h' comes before 'l' in this language, then the sequence is sorted.

Example 2:

Input: words = ["word","world","row"], order = "worldabcefghijkmnpqstuvxyz"
Output: false
Explanation: As 'd' comes after 'l' in this language, then words[0] > words[1], hence the sequence is unsorted.

Example 3:

Input: words = ["apple","app"], order = "abcdefghijklmnopqrstuvwxyz"
Output: false
Explanation: The first three characters "app" match, and the second string is shorter (in size.) According to lexicographical rules "apple" > "app", because 'l' > '∅', where '∅' is defined as the blank character which is less than any other character (More info).
class Solution {
public boolean isAlienSorted(String[] words, String order) {
// build order hashmap
HashMap<Character, Integer> orderMap = new HashMap<>();
for(int i=0; i<order.length(); i++){
orderMap.put(order.charAt(i), i);
}

// check
for(int i=0; i<words.length-1; i++){
for(int j=0; j<words[i].length(); j++){
if(j >= words[i+1].length()) return false;

if(words[i].charAt(j) != words[i+1].charAt(j)){
int curr = orderMap.get(words[i].charAt(j));
int next = orderMap.get(words[i+1].charAt(j));
if(curr>next) return false;
else break;
}
}
}
return true;
}
}

269. Alien Dictionary

There is a new alien language that uses the English alphabet. However, the order of the letters is unknown to you.

You are given a list of strings words from the alien language's dictionary. Now it is claimed that the strings in words are

sorted lexicographically

by the rules of this new language.

If this claim is incorrect, and the given arrangement of string in words cannot correspond to any order of letters, return "".

Otherwise, return a string of the unique letters in the new alien language sorted in lexicographically increasing order by the new language’s rules. If there are multiple solutions, return any of them.

Example 1:

Input: words = ["wrt","wrf","er","ett","rftt"]
Output: "wertf"

Example 2:

Input: words = ["z","x"]
Output: "zx"

Example 3:

Input: words = ["z","x","z"]
Output: ""
Explanation: The order is invalid, so return "".
class Solution {

private Map<Character, List<Character>> reverseAdjList = new HashMap<>();
private Map<Character, Boolean> seen = new HashMap<>();
private StringBuilder output = new StringBuilder();

public String alienOrder(String[] words) {

// Step 0: Put all unique letters into reverseAdjList as keys.
for (String word : words) {
for (char c : word.toCharArray()) {
reverseAdjList.putIfAbsent(c, new ArrayList<>());
}
}

// Step 1: Find all edges and add reverse edges to reverseAdjList.
for (int i = 0; i < words.length - 1; i++) {
String word1 = words[i];
String word2 = words[i + 1];
// Check that word2 is not a prefix of word1.
if (word1.length() > word2.length() && word1.startsWith(word2)) {
return "";
}
// Find the first non match and insert the corresponding relation.
for (int j = 0; j < Math.min(word1.length(), word2.length()); j++) {
if (word1.charAt(j) != word2.charAt(j)) {
reverseAdjList.get(word2.charAt(j)).add(word1.charAt(j));
break;
}
}
}

System.out.println(reverseAdjList);

// Step 2: DFS to build up the output list.
for (Character c : reverseAdjList.keySet()) {
boolean result = dfs(c);
if (!result) return "";
}

return output.toString();
}

// Return true iff no cycles detected.
private boolean dfs(Character c) {
if (seen.containsKey(c)) {
return seen.get(c); // If this node was grey (false), a cycle was detected.
}
seen.put(c, false);
for (Character next : reverseAdjList.get(c)) {
boolean result = dfs(next);
if (!result) return false;
}
seen.put(c, true);
output.append(c);
return true;
}
}

If we made a reverse adjacency list instead of a forward one, the output order would be correct (without needing to be reversed). Remember that when we reverse the edges of a directed graph, the nodes with no incoming edges became the ones with no outgoing edges. This means that the ones at the start of the alphabet will now be the ones returned first.

One issue we need to be careful of is cycles. In directed graphs, we often detect cycles by using graph coloring. All nodes start as white, and then once they’re first visited they become grey, and then once all their outgoing nodes have been fully explored, they become black. We know there is a cycle if we enter a node that is currently grey (it works because all nodes that are currently on the stack are grey. Nodes are changed to black when they are removed from the stack).

--

--

LiveRunGrow

𓆉︎ 𝙳𝚛𝚎𝚊𝚖𝚎𝚛 🪴𝙲𝚛𝚎𝚊𝚝𝚘𝚛 👩‍💻𝚂𝚘𝚏𝚝𝚠𝚊𝚛𝚎 𝚎𝚗𝚐𝚒𝚗𝚎𝚎𝚛 ☻ I write & reflect weekly about software engineering, my life and books. Ŧ๏ɭɭ๏ฬ ๓є!