- September Week 1
- 24.09.01 - 2022. Convert 1D Array Into 2D Array
- 24.09.02 - 1894. Find the Student that Will Replace the Chalk
- 24.09.03 - 1945. Sum of Digits of String After Convert
- 24.09.04 - 874. Walking Robot Simulation
- 24.09.05 - 2028. Find Missing Observations
- 24.09.06 - 3217. Delete Nodes From Linked List Present in Array
- 24.09.07 - 1367. Linked List in Binary Tree
- 24.09.08 - 725. Split Linked List in Parts
// 81ms, 89.91MB
class Solution {
public:
vector<vector<int>> construct2DArray(vector<int>& original, int m, int n) {
int SIZE = original.size();
vector<vector<int>> answer;
if(m * n != SIZE) return answer;
for(int i{};i<m;++i) {
answer.push_back(vector<int>(n, 0));
for(int j{};j<n;++j) {
answer[i][j] = original[n * i + j];
}
}
return answer;
}
};The "Convert 1D Array Into 2D Array" problem requires transforming a given 1D array into a 2D array with specified row and column dimensions. If the total number of elements in the 1D array doesn't match rows * cols, return an empty array.
- Check if the conversion is possible by verifying the array size.
- Traverse the 1D array and map each element to the corresponding position in the 2D array.
Here's the C++ code to solve it:
// 78ms, 88.03MB
class Solution {
public:
vector<vector<int>> construct2DArray(vector<int>& original, int m, int n) {
if (original.size() != m * n)
return {};
vector<vector<int>> result(m, vector<int>(n));
for (int i = 0; i < original.size(); ++i) {
result[i / n][i % n] = original[i];
}
return result;
}
};- Check Validity: If the size of the 1D array isn't
m * n, return an empty array. - Filling the 2D Array: Use integer division and modulus to map each element from the 1D array into the appropriate 2D position.
-
$O(m \times n)$ for filling the 2D array.
// 79ms, 77.10MB
class Solution {
public:
int chalkReplacer(vector<int>& chalk, int k) {
int answer{};
int sum{}; // k <= 10^9 : no need to long long
for(int s{}, e{static_cast<int>(chalk.size())}; s<e; ++s) {
sum += chalk[s];
if(sum > k) {
answer = s; // 조기 종료
break;
}
}
if(answer) return answer;
k %= sum;
sum = 0;
for(int s{}, e{static_cast<int>(chalk.size())}; s<e; ++s) {
sum += chalk[s];
if(sum > k) {
answer = s; // 조기 종료
break;
}
}
return answer;
}
};In this problem, we have an array chalk of n elements representing the number of chalks used by each student, and an integer k indicating the total number of chalks available. The brute force approach would involve repeatedly subtracting the number of chalks from k until it reaches zero, cycling through the array if necessary. Given that k can be as large as 1,000,000,000, this approach is impractical.
To optimize, observe that the total number of chalks used in one complete cycle through the array is given by sum, the sum of all elements in chalk. If k is less than sum, we will reach zero within the first cycle. If k is greater than sum, after the first cycle, k will be reduced to k - sum, and after subsequent cycles, it will be reduced further. This process continues until k becomes less than sum, which is equivalent to computing k \% sum.
This is because
kreduced by multiples ofsumwill eventually be less thansum, and this final value is equivalent tok \% sum.
We then need to find the first index in the chalk array where the remaining k \% sum becomes negative. We do this by maintaining a running prefix sum of chalk elements and iterating through the array to find the index where the prefix sum exceeds k \% sum.
- Initialize an integer variable
sumto 0. - Iterate over the chalk array from 0 to
chalk.size() - 1:- Add the value at the current index
itosum. - If at any point
sumexceedsk, exit the loop.
- Add the value at the current index
- Calculate
kask \% sum, representing the remaining chalk after full rounds. - Iterate over the chalk array again from
0tochalk.size() - 1:- If
kis less than the value at the current indexi, returnias the index of the student who will run out of chalk. - Otherwise, subtract the value at
chalk[i]fromk.
- If
- If no student is found within the loop, return
0(though this should not be reached given the problem constraints).
// 96ms, 77.05MB
class Solution {
public:
int chalkReplacer(vector<int>& chalk, int k) {
// Find the sum of all elements.
int sum = 0;
for (int i = 0; i < chalk.size(); i++) {
sum += chalk[i];
if (sum > k) {
break;
}
}
// Find modulo of k with sum.
k = k % sum;
for (int i = 0; i < chalk.size(); i++) {
if (k < chalk[i]) {
return i;
}
k = k - chalk[i];
}
return 0;
}
};Let n be the size of the chalk array.
-
Time complexity:
$O(n)$
We iterate through thechalkarray exactly twice. Apart from this, all operations are performed in constant time. Therefore, the total time complexity is given by$O(n)$ . -
Space complexity:
$O(1)$
No additional space is used proportional to the array sizen. Therefore, the space complexity is given by$O(1)$ .
Instead of iterating through the array to find the first index, we can use binary search. Binary search is ideal here because it quickly narrows down the search space in a sorted array.
We start by defining a predicate function that checks if the prefix sum at a given index is greater than the k modulo sum. This function returns true for indices where the prefix sum exceeds the target and false otherwise. Since the array is sorted based on the prefix sums, true indicates indices with no chalk left, while false indicates indices with some chalk remaining.
Using binary search, we locate the smallest index where the predicate returns true.
- If the predicate returns
true, it means there might be smaller indices withtruevalues, so we adjust the upper bound of the search space to the current index. - If the predicate returns
false, it means alltruevalues are beyond the current index, so we adjust the lower bound of the search space to the current index.
Main Function - chalkReplacer(chalk, k):
- Create an array
prefixSumof lengthnto store prefix sums. - Initialize
prefixSum[0]withchalk[0]. - Iterate through the chalk array from index
1ton-1and updateprefixSum[i]as the sum ofprefixSum[i-1]andchalk[i]. - Calculate
sumasprefixSum[n-1], representing the total chalk needed for one full round. - Calculate
remainingChalkask \% sum. - Call the helper function
binarySearch(prefixSum, remainingChalk)to find the student who will run out of chalk and return the result ofbinarySearch.
Helper Function - binarySearch(arr, remainingChalk):
- Set
lowto 0 andhightoarr.length - 1. - While
lowis less thanhigh:- Calculate
midas the average oflowandhigh. - If
arr[mid]is less than or equal toremainingChalk, updatelow to mid + 1. - Otherwise, update
hightomid.
- Calculate
- Return
highas the index of the student who will run out of chalk.
// 89ms, 83.93MB
class Solution {
public:
int chalkReplacer(vector<int>& chalk, int k) {
int n = chalk.size();
vector<long> prefixSum(n);
prefixSum[0] = chalk[0];
for (int i = 1; i < n; i++) {
prefixSum[i] = prefixSum[i - 1] + chalk[i];
}
long sum = prefixSum[n - 1];
long remainingChalk = (k % sum);
return binarySearch(prefixSum, remainingChalk);
}
private:
int binarySearch(vector<long>& arr, long tar) {
int low = 0, high = arr.size() - 1;
while (low < high) {
int mid = low + (high - low) / 2;
if (arr[mid] <= tar) {
low = mid + 1;
} else {
high = mid;
}
}
return high;
}
};Let chalk array.
-
Time complexity:
$O(n)$
We iterate through thechalkarray once. Apart from this, the binary search operation takes$O(\log n)$ time. Therefore, the total time complexity is given by$O(n)$ . -
Space complexity:
$O(n)$
We initialize an arrayprefixSumof sizento store the prefix sums of thechalkarray. Apart from this, no additional space is used. Therefore, the space complexity is given by$O(n)$ .
// 5ms, 10.12MB
class Solution {
public:
int getLucky(string s, int k) {
string convertedS;
for(char c : s) {
convertedS += to_string(c - 'a' + 1);
}
int num{};
for(int i{};i<k;++i) {
num = 0;
for(char c : convertedS) {
num += c - '0';
}
convertedS = to_string(num);
}
return num;
}
};The "Sum of Digits of String After Convert" problem asks you to convert a string into its numeric form based on the position of each letter in the alphabet (a = 1, b = 2, ..., z = 26). After that, you perform a transformation k times where you sum the digits of the resulting number.
- Convert the string to a numeric string based on the alphabet's position.
- Sum the digits of this number for
kiterations.
// 6ms, 10.19MB
class Solution {
public:
int getLucky(string s, int k) {
// Convert string to a numerical string based on alphabetic position
string numStr = "";
for (char c : s) {
numStr += to_string(c - 'a' + 1);
}
// Perform the transformation k times
int sum = 0;
for (int i = 0; i < k; ++i) {
sum = 0;
for (char c : numStr) {
sum += c - '0'; // Sum digits
}
numStr = to_string(sum); // Update the number as string
}
return sum;
}
};- Step 1: Conversion: Convert each character in the string to its position in the alphabet. For example, "zbax" becomes "26213124".
- Step 2: Transformation: Sum the digits of the resulting number and repeat this process
ktimes.
-
$O(n + k \times d)$ , where$n$ is the length of the string and$d$ is the number of digits in the number after conversion.
simulation은 생각했는데, 장애물 처리 아이디어가 없다.
- 또 궁금했던 건 장애물이 (0, 0)에 있을 때 그냥 못 가는 건지, 처음에는 갈 수 있는 건지 도 애매했는 것 같다.
- 장애물 처리 아이디어 해설 참고!
We have a robot facing north at the origin (0, 0) of an infinite 2D grid. The robot receives a series of instructions from a given list of commands, where instruction can be of three types:
-2: Turn left 90 degrees while staying at the current coordinate.-1: Turn right 90 degrees while staying at the current coordinate.- Any positive integer
kfrom 1 to 9: Advancekunits in the current direction.
Additionally, we are given a list of obstacles containing the coordinates of various obstacles on the grid. If the robot encounters an obstacle while moving forward, it stops its motion at the coordinate just before the obstacle and proceeds to the next command.
Our goal is to find the farthest squared distance from the origin that the robot reaches during its journey. In other words, we need to find the maximum value of (x, y) visited by the robot.
Keep in mind when planning your approach that the farthest traveled distance during the robot's journey is not the same as its distance from the origin at the end of its journey.
Note: An obstacle may exist at the origin (0, 0). In this case, the robot can move away from the starting point but will be unable to return to (0, 0).
The robot's state is defined by two factors:
- The coordinates of the robot's position: we can use a simple integer array
[x, y]. - The direction the robot is facing: we can use an integer value (0, 1, 2, 3) representing North, East, South, and West respectively. Consequently, we need a
directionsarray representing the direction of motion of the robot, where each index corresponds to [North, East, South, West].
The presence of obstacles prevents us from being able to simply loop over each command and simulate the robot's motion on the grid. A naive approach would be to loop through the obstacle array to check if the next attempted move is blocked by an obstacle. However, this results in quadratic complexity, which is inefficient given our constraints.
Checking whether a given coordinate is an obstacle using hash sets allows for constant-time lookups. If you're unfamiliar with hash sets, this LeetCode Explore Card provides an in-depth explanation.
Our challenge becomes how to look up coordinates in a hash set.
We solve this by hashing the coordinates of each obstacle to a unique integer value and storing these values in the hash set. To check if a coordinate contains an obstacle, we hash the coordinates using the same function and check if the value is present in the hash set.
There are various methods to create hashing functions. For this problem, we'll create a simple one that generates a unique integer value for all coordinates within the given problem constraints.
hash(x, y) = x + HASH_MULTIPLIER * yWhere HASH_MULTIPLIER is a constant slightly larger than twice the maximum possible coordinate value. In this case, we choose 60013.
We choose
60013because it is the smallest prime number greater than 60000 (twice the maximum possible coordinate). This helps reduce the number of potential collisions in our hash function.
slideshow 생략
- Create a constant
HASH_MULTIPLIERto use in the hashing function.
robotSim Function:
- Convert the list of obstacles into a set of hashed coordinates for quick lookup during the simulation.
- Define the four possible movement directions corresponding to North, East, South, and West.
- Initialize the robot's starting position at the origin
(0, 0)and set the initial maximum distance squared to zero. - Initialize the current direction of the robot facing North.
- Iterate through the list of commands:
- If the command is
-1, turn the robot 90 degrees to the right by adjusting the current direction index. - If the command is
-2, turn the robot 90 degrees to the left by adjusting the current direction index. - Otherwise, for a positive command, move the robot forward step by step:
- Calculate the next potential position by adding the current direction vector to the robot's position.
- If the next position is an obstacle, stop moving forward.
- Otherwise, update the robot's position to the new coordinates.
- Update the maximum distance squared if the current position is farther from the origin than before.
- If the command is
- Return the maximum distance squared as the result of the simulation.
hashCoordinates Function:
- Combine the
xandycoordinates into a unique hash value by multiplying theycoordinate by a constant multiplier and adding thexcoordinate. - Return the computed hash value to be used for obstacle lookup.
// 76ms, 38.18MB
class Solution {
private:
static const long long HASH_MULTIPLIER =
60013; // Slightly larger than 2 * max coordinate value
// Hash function to convert (x, y) coordinates to a unique integer value
long long hashCoordinates(long long x, long long y) {
return x + HASH_MULTIPLIER * y;
}
public:
int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
// Store obstacles in an unordered_set for efficient lookup
unordered_set<long long> obstacleSet;
for (auto& obstacle : obstacles) {
obstacleSet.insert(hashCoordinates(obstacle[0], obstacle[1]));
}
// Define direction vectors: North, East, South, West
vector<vector<int>> directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
vector<int> currentPosition = {0, 0};
int maxDistanceSquared = 0;
int currentDirection = 0; // 0: North, 1: East, 2: South, 3: West
for (int command : commands) {
if (command == -1) {
// Turn right
currentDirection = (currentDirection + 1) % 4;
continue;
}
if (command == -2) {
// Turn left
currentDirection = (currentDirection + 3) % 4;
continue;
}
// Move forward
vector<int> direction = directions[currentDirection];
for (int step = 0; step < command; step++) {
int nextX = currentPosition[0] + direction[0];
int nextY = currentPosition[1] + direction[1];
if (obstacleSet.contains(hashCoordinates(nextX, nextY))) {
break;
}
currentPosition[0] = nextX;
currentPosition[1] = nextY;
}
maxDistanceSquared =
max(maxDistanceSquared,
currentPosition[0] * currentPosition[0] +
currentPosition[1] * currentPosition[1]);
}
return maxDistanceSquared;
}
};Let commands and obstacles, respectively.
-
Time complexity:
$O(m + n)$
The algorithm initially iterates over theobstaclesarray and hashes each obstacle’s coordinates, taking$O(n)$ time.The algorithm then loops over the
commandsarray. In the worst case, each command is a positive integerk. Since the maximum value ofkis limited to 9, this step has a time complexity of$O(9 \cdot m) = O(m)$ .Thus, the overall time complexity of the algorithm is
$O(n) + O(m) = O(m + n)$ . -
Space complexity:
$O(n)$
The only additional space used by the algorithm is theobstacleSet, which stores up to$n$ hashed obstacle positions. ThedirectionsandcurrentPositionarrays and all other primitive variables use constant space.Thus, the space complexity of the algorithm is
$O(n)$ .
In the "Walking Robot Simulation" problem, you are given a robot starting at the origin of a 2D plane, facing north. The robot receives a series of commands (positive for movement and negative for turns) and obstacles scattered on the plane. The goal is to determine the farthest distance (squared) the robot can be from the origin after executing all commands while avoiding obstacles.
- Movement Logic: Track robot's position and direction (north, east, south, west).
- Obstacle Handling: Skip commands that would move the robot into obstacles.
// 124ms, 39.64MB
class Solution {
public:
int robotSim(vector<int>& commands, vector<vector<int>>& obstacles) {
// Directions: north, east, south, west
vector<vector<int>> directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
unordered_set<string> obsSet;
// Add obstacles to a hash set for O(1) lookup
for (const auto& obs : obstacles) {
obsSet.insert(to_string(obs[0]) + "," + to_string(obs[1]));
}
int x = 0, y = 0; // Robot's position
int dir = 0; // Start facing north
int maxDist = 0;
for (int cmd : commands) {
if (cmd == -1) {
// Turn right (clockwise)
dir = (dir + 1) % 4;
} else if (cmd == -2) {
// Turn left (counterclockwise)
dir = (dir + 3) % 4;
} else {
// Move forward by 'cmd' units
for (int step = 0; step < cmd; ++step) {
int newX = x + directions[dir][0];
int newY = y + directions[dir][1];
if (obsSet.count(to_string(newX) + "," + to_string(newY))) {
break; // Hit an obstacle, stop moving
}
x = newX;
y = newY;
maxDist = max(maxDist, x * x + y * y); // Update max distance
}
}
}
return maxDist;
}
};- Direction Control: Use an array to manage directional changes (north, east, south, west).
- Obstacle Checking: Store obstacles in a hash set for quick lookup and stop movement when hitting one.
- Distance Calculation: Track the maximum squared distance from the origin.
-
$O(n + m)$ , where$n$ is the number of commands and$m$ is the number of obstacles.
// 98ms, 126.21MB
class Solution {
public:
vector<int> missingRolls(vector<int>& rolls, int mean, int n) {
int sum{};
for(int roll : rolls) sum += roll;
int m = rolls.size();
int rest = mean * (n + m) - sum; // missing: n
vector<int> answer;
if(n <= rest && rest <= 6 * n) { // 답 만들 수 있음.
// n개의 수로 others 만들기.
for(;rest != n;--n) {
int r = rest - (n - 1);
if(1 <= r && r <= 6) {
answer.push_back(r);
rest -= r;
}
else {
answer.push_back(6);
rest -= 6;
}
}
for(int i{};i<n;++i) answer.push_back(1);
}
return answer;
}
};To solve the LeetCode problem "Find Missing Observations", you need to find n missing dice rolls given m observed rolls and the mean of all n + m rolls.
The task is to determine what values those missing rolls should have to maintain the overall average.
-
Calculate the total sum:
- The sum of all
n + mrolls must bemean * (n + m). - Calculate the sum of the observed rolls (
rolls[]) and subtract it from this total to get the sum of the missing rolls.
- The sum of all
-
Check for feasibility:
- The sum of the missing
nrolls should be betweenn(all 1s) and6 * n(all 6s) because each dice roll is between 1 and 6. - If the sum is outside this range, it's impossible to generate valid rolls, and you return an empty array.
- The sum of the missing
-
Distribute the sum:
- Distribute the sum of the missing rolls evenly. Start by assigning each missing roll the floor value of the average (
total_sum_of_missing // n). - Then, distribute the remainder (
total_sum_of_missing % n) by adding1to the first few rolls to balance the total.
- Distribute the sum of the missing rolls evenly. Start by assigning each missing roll the floor value of the average (
// 107ms, 116.37MB
class Solution {
public:
vector<int> missingRolls(vector<int>& rolls, int mean, int n) {
int m = rolls.size();
int total_sum = mean * (n + m);
int observed_sum = 0;
for (int roll : rolls) {
observed_sum += roll;
}
int missing_sum = total_sum - observed_sum;
// Check if it's possible to generate valid missing rolls
if (missing_sum < n || missing_sum > 6 * n) {
return {};
}
// Distribute the missing_sum
vector<int> result(n, missing_sum / n);
int remainder = missing_sum % n;
for (int i = 0; i < remainder; ++i) {
result[i]++;
}
return result;
}
};- We first compute the total sum of all
n + mrolls. - Then, subtract the observed sum to get the sum of the missing rolls.
- If this sum is not within the valid range (
n <= sum <= 6 * n), return an empty vector because it's impossible. - Otherwise, we distribute the sum evenly among the missing rolls, ensuring that the total remains consistent.
- The time complexity is
$O(m + n)$ , wheremis the size ofrollsandnis the number of missing rolls.
- The space complexity is
$O(n)$ for the result array.
// 387ms, 319.5MB
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* modifiedList(vector<int>& nums, ListNode* head) {
const int SIZE = 100'010;
vector<int> dat(SIZE, 0);
for(int num : nums) dat[num] = 1;
// 시작점 찾기
while(head -> next) {
if(dat[head->val]) {
head = head->next;
}
else break;
}
ListNode* now = head; // 첫 시작점은 무조건 존재하는 head
while(now -> next) {
ListNode* next = now -> next;
if(dat[next->val]) {
now -> next = next -> next;
}
else {
if(!(now->next)) break;
now = now -> next;
}
}
return head;
}
};To solve the problem "Delete Nodes From Linked List Present in Array" on LeetCode, the goal is to remove all nodes from the linked list whose values are present in the given array nums. Here's an efficient approach using a hash set for constant-time lookups.
-
Create a Set: Store all elements from the array
numsin an unordered set for$O(1)$ lookup time. - Use a Dummy Node: Create a dummy node pointing to the head of the linked list. This simplifies edge cases like removing the head node.
-
Traverse the List: Iterate through the linked list and check if the value of the current node's
nextis in the set:- If it is, bypass that node by adjusting the pointers.
- Otherwise, move to the next node.
-
Return the Modified List: Finally, return the list starting from
dummy.next(to skip the dummy node itself).
// 405ms, 258.36MB
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* modifiedList(vector<int>& nums, ListNode* head) {
unordered_set<int> values(nums.begin(), nums.end());
ListNode* dummy = new ListNode(0, head);
ListNode* current = dummy;
while (current->next != nullptr) {
if (values.count(current->next->val)) {
current->next = current->next->next; // Remove the node
} else {
current = current->next;
}
}
return dummy->next; // Return the modified list
}
};-
Time Complexity:
$O(n + m)$ , wherenis the size of the arraynumsandmis the number of nodes in the linked list. -
Space Complexity:
$O(n)$ , for storing the elements ofnumsin a set.
This approach efficiently handles large inputs due to its linear complexity.
TLE가 나서 해설 참고..
// TLE
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool answer{};
ListNode* listStart;
void traverseSubPath(ListNode* head, TreeNode* root) {
if(answer) return;
if(head == nullptr) { // reach the end.
answer = true;
return;
}
if(root == nullptr) return; // leaf
if(head->val == root->val) { // 같다면 유지
traverseSubPath(head->next, root->left);
traverseSubPath(head->next, root->right);
}
// 아닌 경우 새로 시작
traverseSubPath(listStart, root->left);
traverseSubPath(listStart, root->right);
}
bool isSubPath(ListNode* head, TreeNode* root) {
listStart = head;
traverseSubPath(head, root);
return answer;
}
};We are given a binary tree and a linked list. Our task is to determine if the linked list is represented by any downward path in the binary tree. A downward path in the binary tree is defined as a path that starts at any node and extends to its subsequent child nodes, going downward.
A direct approach is to explore every possible path in the tree using Depth-First Search (DFS). This method allows us to examine each path fully before moving to the next.
We begin at the root of the tree and compare its value to the head of the linked list. If they match, we continue by checking the left and right children of the tree node against the next node in the linked list. If the tree node's value does not match the linked list node, we stop exploring that path since it can't lead to a match. We then backtrack and try the next possible path.
- If
rootis null, returnfalse(base case). - Call
checkPath(root, head)to start checking for the linked list path in the tree. checkPathfunction:- If
nodeis null, returnfalse(base case). - Call
dfs(node, head)to check if a matching path starts fromnode.- If
dfsreturnstrue, returntrue(a matching path is found).
- If
- Recursively call
checkPathon both left and right subtrees with the samehead.
- If
dfsfunction:- If
headis null, returntrue(all nodes in the list have been matched). - If
nodeis null, returnfalse(reached end of the tree without matching all nodes). - If the value of
nodedoes not matchhead, returnfalse(value mismatch). - Recursively call
dfson both left and right children ofnodewithhead->next.
- If
- Return
trueifcheckPathordfsfinds a matching path; otherwise, continue checking.
// 25ms, 30.86MB
class Solution {
public:
bool isSubPath(ListNode* head, TreeNode* root) {
if (!root) return false;
return checkPath(root, head);
}
private:
bool checkPath(TreeNode* node, ListNode* head) {
if (!node) return false;
if (dfs(node, head)) return true; // If a matching path is found
// Recursively check left and right subtrees
return checkPath(node->left, head) || checkPath(node->right, head);
}
bool dfs(TreeNode* node, ListNode* head) {
if (!head) return true; // All nodes in the list matched
if (!node)
return false; // Reached end of tree without matching all nodes
if (node->val != head->val) return false; // Value mismatch
return dfs(node->left, head->next) || dfs(node->right, head->next);
}
};Let
-
Time complexity:
$O(n \times m)$
In the worst case, we might need to check every node in the tree as a potential starting point for the linked list. For each node, we might need to traverse up to$m$ nodes in the linked list. -
Space complexity:
$O(n + m)$
The space complexity remains the same as Approach 1 due to the recursive nature of the solution.
A common rule of thumb is that all approaches solvable via recursion can also be solved using a stack to mimic the call stack's nature. Unlike recursion, where each function call adds a new frame to the call stack, using a stack avoids the risk of stack overflow errors in cases where the depth of recursion is too large (e.g., in a very deep tree).
We start by putting the root of the tree onto the stack. This stack helps us explore the tree without recursion. We repeatedly take the top node from the stack and check if there is a path from this node that matches the linked list. If there is, we return true. If not, we add the node's left and right children to the stack for further checking.
To match the path, we use another stack to keep track of pairs of tree nodes and linked list nodes. We compare each pair, and if they match, we continue with the next node in the linked list and the children of the current tree node. If we find that the entire linked list matches a path in the tree, we return true.
If we finish checking all possible paths without finding a match, we return false.
Fun fact: Iterative approaches often provide more control over traversal, allowing you to access every path and create patterns that do not follow traditional recursion rules.
- Check if
rootis null:- If
rootis null, returnfalse(base case).
- If
- Initialize a stack
nodesand pushrootonto the stack. - While the stack
nodesis not empty:- Pop the top
nodefrom the stack. - Call
isMatch(node, head)to check if the linked listheadmatches a path starting fromnode.- If
isMatchreturnstrue, returntrue(a matching path is found).
- If
- If
nodehas a left child, push it onto the stack. - If
nodehas a right child, push it onto the stack.
- Pop the top
- If no matching path is found after checking all nodes, return
false. isMatchfunction:- Initialize a stack
sand push a pair{node, lst}onto it. - While the stack
sis not empty:- Pop the top pair
{currentNode, currentList}from the stack. - While both
currentNodeandcurrentListare not null:- If
currentNode->valdoes not matchcurrentList->val, break (no match). - Move to the next node in the linked list (
currentList = currentList->next). - If
currentListis not null:- If
currentNodehas a left child, push{currentNode->left, currentList}onto the stack. - If
currentNodehas a right child, push{currentNode->right, currentList}onto the stack. - Break to continue with the next pair in the stack.
- If
- If
- If
currentListbecomes null, returntrue(all nodes in the list matched).
- Pop the top pair
- Initialize a stack
- Return
falseif no matching path is found after exploring all possibilities.
// 47ms, 50.24MB
class Solution {
public:
bool isSubPath(ListNode* head, TreeNode* root) {
if (!root) return false;
// Initialize a stack for DFS on the tree
stack<TreeNode*> nodes;
nodes.push(root);
// Perform DFS
while (!nodes.empty()) {
TreeNode* node = nodes.top();
nodes.pop();
// Check if the current node's path matches the linked list
if (isMatch(node, head)) {
return true;
}
// Push left and right children onto the stack
if (node->left) {
nodes.push(node->left);
}
if (node->right) {
nodes.push(node->right);
}
}
return false;
}
private:
bool isMatch(TreeNode* node, ListNode* lst) {
stack<pair<TreeNode*, ListNode*>> s;
s.push({node, lst});
while (!s.empty()) {
auto [currentNode, currentList] = s.top();
s.pop();
while (currentNode && currentList) {
if (currentNode->val != currentList->val) {
break;
}
currentList = currentList->next;
if (currentList) {
if (currentNode->left) {
s.push({currentNode->left, currentList});
}
if (currentNode->right) {
s.push({currentNode->right, currentList});
}
break;
}
}
if (!currentList) {
return true;
}
}
return false;
}
};Let
-
Time complexity:
$O(n \times m)$
We potentially visit each node in the tree once. For each node, we might need to check up to$m$ nodes in the linked list. -
Space complexity:
$O(n)$
The space is used by the stack, which in the worst case might contain all nodes of the tree. We don't need extra space for the linked list traversal as it's done iteratively.
Approach 3 is more advanced and requires an understanding of the Knuth-Morris-Pratt (KMP) string-matching algorithm. We suggest reviewing 28. Find the Index of the First Occurrence in a String - Easy Tagged and solving it using the KMP algorithm before diving into this approach.
The previous approaches all involve searching the tree from the root and checking each path independently, which can be repetitive. By adjusting the idea behind the KMP algorithm, we can reduce this repetition and optimize the approach.
The KMP algorithm efficiently finds occurrences of a pattern (in this case, the linked list) within a text by using a prefix table, or failure function, to skip unnecessary comparisons.
The key to KMP is the prefix table, also known as the failure function. This table helps us understand how to skip certain comparisons based on what we’ve already matched.
We first build a table that indicates the longest proper prefix of the pattern that is also a suffix. This table tells us where to resume the search in the pattern after a mismatch. For example, consider the pattern ABABCABAB. The prefix table for this pattern helps us understand that if a mismatch occurs after AB, we don’t need to start from the beginning of the pattern but can skip to the next best position that aligns with what we’ve already matched.
As we search for the pattern in the text, if we encounter a mismatch, the prefix table tells us how far back we should go in the pattern to continue the search efficiently. Instead of starting the comparison from the beginning of the pattern again, we use the prefix table to skip over parts of the pattern that have already been matched. This reduces unnecessary comparisons.
Similarly, we construct the prefix table for the linked list by following the same principle of finding the longest prefix that is also a suffix. This helps in efficiently finding where to resume the search if a mismatch occurs while traversing paths in the tree.
We perform a DFS on the tree, treating each node's value as part of the text where we want to match our pattern (the linked list). As we traverse the tree, if a mismatch occurs, the prefix table tells us how much of the pattern we can skip, based on what we’ve already matched.
Note: Running through a dry run of this approach will help you get a better grip on how it works. It’s a great way to see the logic in action with a few concrete examples and spot any issues.
- Build the pattern and prefix table from the linked list:
- Initialize
patternwith the value of the head node of the linked list. - Initialize
prefixTablewith0to store prefix lengths. - Iterate through the linked list to construct
patternandprefixTable:- For each value, update the
patternIndexto find matching prefixes using theprefixTable. - Add the current value to
patternand updateprefixTableaccordingly. - Move to the next node in the linked list.
- For each value, update the
- Initialize
- Perform DFS to search for the pattern in the tree:
- Call
searchInTreewith the root of the tree, starting pattern index0, and thepatternandprefixTable.
- Call
searchInTreefunction:- If
nodeis null, returnfalse(base case). - Update
patternIndexto find the matching prefix:- If the current node value does not match the pattern at
patternIndex, use theprefixTableto backtrack to the correct index. - Increment
patternIndexif there is a match.
- If the current node value does not match the pattern at
- Check if the entire
patternhas been matched (patternIndex == pattern.size()):- If matched, return
true.
- If matched, return
- Recursively search in both left and right subtrees of the current
node:- Return
trueif either subtree contains a matching path; otherwise, continue searching.
- Return
- If
// 34ms, 30.95MB
class Solution {
public:
bool isSubPath(ListNode* head, TreeNode* root) {
// Build the pattern and prefix table from the linked list
vector<int> pattern = {head->val}, prefixTable = {0};
int patternIndex = 0;
head = head->next;
while (head) {
while (patternIndex && head->val != pattern[patternIndex])
patternIndex = prefixTable[patternIndex - 1];
patternIndex += head->val == pattern[patternIndex];
pattern.push_back(head->val);
prefixTable.push_back(patternIndex);
head = head->next;
}
// Perform DFS to search for the pattern in the tree
return searchInTree(root, 0, pattern, prefixTable);
}
private:
bool searchInTree(TreeNode* node, int patternIndex,
const vector<int>& pattern,
const vector<int>& prefixTable) {
if (!node) return false;
while (patternIndex && node->val != pattern[patternIndex])
patternIndex = prefixTable[patternIndex - 1];
patternIndex += node->val == pattern[patternIndex];
// Check if the pattern is fully matched
if (patternIndex == pattern.size()) return true;
// Recursively search left and right subtrees
return searchInTree(node->left, patternIndex, pattern, prefixTable) ||
searchInTree(node->right, patternIndex, pattern, prefixTable);
}
};Let
-
Time complexity:
$O(n + m)$
Building the pattern and prefix table takes$O(m)$ , and searching the tree is$O(n)$ . -
Space complexity:
$O(n + m)$
We need$O(m)$ space for the pattern and prefix table. The recursive call stack in the worst case (skewed tree) can take up to$O(n)$ space.
// 0ms, 13.9MB
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
vector<ListNode*> splitListToParts(ListNode* head, int k) {
int cnt{};
/*
// 이 부분이 틀렸네..
for(ListNode* now = head;now->next;now = now->next, ++cnt)
if(head) ++cnt; // 초기 원소 누락
*/
ListNode* now = head;
while (now) {
++cnt;
now = now->next;
}
int quotient = cnt / k, remainder = cnt % k;
vector<int> sizes(k, quotient); // 크기 k, 원소 값: quotient
for(int i{};i<remainder;++i) sizes[i]++;
vector<ListNode*> answer;
now = head;
for(int i{}; i<k; ++i) {
answer.push_back(now); // if nullptr => push_back nullptr.
for(int j{};j<sizes[i] && now;++j) {
if(j == sizes[i] - 1) {
ListNode* tmp = now->next; // 마지막 원소 연결 끊기
now->next = nullptr;
now = tmp;
}
else now = now->next;
}
}
return answer;
}
};개수 카운팅할 때
for(ListNode* now = head;now->next;now = now->next, ++cnt)
if(head) ++cnt; // 초기 원소 누락위의 코드에서 틀렸는데, 반례가 없을까..
수정한 코드
for(ListNode* now = head;now;now = now->next) { // 조건 수정
++cnt;
}OR
ListNode* now = head;
while (now) {
++cnt;
now = now->next;
}To solve the problem "Split Linked List in Parts", the idea is to divide the linked list into k parts where each part has an approximately equal number of nodes. The first few parts may have an extra node if the number of nodes is not perfectly divisible by k.
-
Calculate the Length of the Linked List: Traverse the entire list to determine its total length
N. -
Determine the Size of Each Part:
- Each part should have a base size of
N // k. - There will be
N % kparts that need one extra node. These parts should be handled first.
- Each part should have a base size of
-
Split the List: Traverse the list again to split it into parts. For each part:
- Determine its size (either
N // korN // k + 1). - Detach it from the main list by updating the
nextpointer.
- Determine its size (either
-
Handle Remaining Parts: If
kis larger thanN, the remaining parts will be empty (null).
// 5ms, 13.88MB
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
vector<ListNode*> splitListToParts(ListNode* head, int k) {
vector<ListNode*> result(k, nullptr);
int length = 0;
ListNode* temp = head;
// Step 1: Calculate the total length of the linked list
while (temp != nullptr) {
length++;
temp = temp->next;
}
int base_size = length / k;
int extra = length % k;
temp = head;
for (int i = 0; i < k; ++i) {
result[i] = temp;
int part_size = base_size + (i < extra ? 1 : 0);
// Split the list and prepare the next part
for (int j = 0; j < part_size - 1; ++j) {
if (temp != nullptr) {
temp = temp->next;
}
}
if (temp != nullptr) {
ListNode* next_part = temp->next;
temp->next = nullptr;
temp = next_part;
}
}
return result;
}
};-
Time Complexity:
$O(N)$ , whereNis the number of nodes in the list (two passes). -
Space Complexity:
$O(k)$ for storing the result.