- August Week 4
// TLE
class Solution {
struct Data {
int copyLen{}, nowLen{};
Data() = default;
Data(int copyLen, int nowLen) : copyLen{copyLen}, nowLen{nowLen} {}
};
public:
int minSteps(int n) {
int answer{-1};
queue<Data> q;
q.emplace(0, 1);
for(int t{};!q.empty();++t) {
for(int s{}, e{static_cast<int>(q.size())}; s<e; ++s) {
auto [copyLen, nowLen] = q.front(); q.pop();
if(nowLen == n) {
answer = t;
break;
}
// copy
if(copyLen != nowLen) q.emplace(nowLen, nowLen);
// paste
if(copyLen != 0) q.emplace(copyLen, nowLen + copyLen);
}
if(answer != -1) break;
}
return answer;
}
};다른 아이디어가 없다..
The problem you're dealing with is a classic dynamic programming (DP) problem. The goal is to find the minimum number of operations needed to get exactly n 'A's on the screen starting with one 'A' using only two operations: "Copy All" and "Paste".
- Initial Setup: You start with one 'A' on the screen.
- Operations:
- Copy All: Copies the entire content of the screen to a buffer.
- Paste: Pastes the content of the buffer to the screen.
The essence of the problem is to decompose n into its prime factors.
If n can be decomposed into smaller numbers, each "Copy All" followed by a series of "Paste" operations would effectively multiply the number of 'A's on the screen.
-
Prime Factorization Insight:
- If you factorize
n, for every factorfofn, it means you can achievef'A's on the screen by first achievingn/f'A's and then applying a "Copy All" followed byf-1"Paste" operations. - The minimum operations needed to get
n'A's is the sum of its prime factors.
- If you factorize
-
Dynamic Programming Approach:
- You can create a DP array where
dp[i]represents the minimum operations needed to geti'A's on the screen. - Start with
dp[1] = 0because you're already starting with one 'A'. - For each
ifrom 2 ton, try all possible factorsj, and updatedp[i]asdp[j] + i/jifi % j == 0.
- You can create a DP array where
Here's the code implementation:
// 5ms, 8.22MB
class Solution {
public:
int minSteps(int n) {
vector<int> dp(n + 1, 0);
for (int i = 2; i <= n; ++i) {
dp[i] = i; // Maximum number of operations needed is i (all Paste operations)
for (int j = i / 2; j > 1; --j) {
if (i % j == 0) {
dp[i] = dp[j] + i / j;
break;
}
}
}
return dp[n];
}
};- dp[i] = i: The worst case is if
iis a prime number, in which you would needioperations (one copy andi-1pastes). - For each
i, we check factorsj: Ifjis a factor ofi, then the operations needed are those to getj'A's plus the number of pastes required to go fromjtoi.
-
Time Complexity:
$O(n \sqrt n)$ because for eachi, you're potentially checking up to$\sqrt i$ factors. -
Space Complexity:
$O(n)$ due to the DP array.
This approach is efficient and leverages the idea of reducing the problem by breaking down the operations into smaller subproblems.
오호..
해설을 보자.
// 4ms, 12.3MB
class Solution {
public:
int stoneGameII(vector<int>& piles) {
vector<vector<int>> memo(piles.size(), vector<int>(piles.size()));
vector<int> suffixSum = piles;
for (int i = suffixSum.size() - 2; i >= 0; --i)
suffixSum[i] += suffixSum[i + 1];
return maxStones(suffixSum, 1, 0, memo);
}
int maxStones(vector<int>& suffixSum, int maxTillNow, int currIndex, vector<vector<int>>& memo) {
if (currIndex + 2 * maxTillNow >= suffixSum.size())
return suffixSum[currIndex]; // all remaining stones can be picked.
if (memo[currIndex][maxTillNow] > 0)
return memo[currIndex][maxTillNow]; // already calculated
int res = INT_MAX;
for (int i = 1; i <= 2 * maxTillNow; ++i) { // 선택할 수 있는 돌의 개수들
res = min(res, maxStones(suffixSum, max(i, maxTillNow), currIndex + i, memo));
}
// 뒤에서 고를 수 있는 최소의 개수를 빼면 최대 개수
memo[currIndex][maxTillNow] = suffixSum[currIndex] - res;
return memo[currIndex][maxTillNow];
}
};// 58ms, 12.4MB
// Solution Code
class Solution {
public:
int stoneGameII(vector<int>& piles) {
int length = piles.size();
vector<vector<int>> dp(length + 1, vector<int>(length + 1, 0));
// dp[i][j]
// i: the starting index of the piles
// j: the maximum number of piles Alice can pick on her turn
// Store suffix sum for all possible suffix
vector<int> suffixSum(length + 1, 0);
for (int i = length - 1; i >= 0; --i) {
suffixSum[i] = suffixSum[i + 1] + piles[i];
}
// Initialize the dp array.
for (int i = 0; i <= length; ++i) {
dp[i][length] = suffixSum[i];
}
// Start from the last index to store the future state first.
for (int index = length - 1; index >= 0; index--) { // 현재 index
for (int maxTillNow = length - 1; maxTillNow >= 1; maxTillNow--) { // 가능한 maxTillNow
for (int X = 1; X <= 2 * maxTillNow && index + X <= length; X++) { // 선택할 수 있는 돌의 개수들
dp[index][maxTillNow] = max(
dp[index][maxTillNow],
suffixSum[index] - dp[index + X][max(maxTillNow, X)]);
}
}
}
// Recursion에 비해 불필요한 연산이 많아서 오래걸리는 듯.
return dp[0][1];
}
};생략
이게 뭔가.. 답지 참고
We have a printer designed to produce a string of lowercase English characters, and we want to return the least number of turns it would take to print a given string. Normally, you would think the number of turns would be the number of characters in the string, but this printer has some bonus features that will let us reduce the number of turns. Instead of counting each keystroke as a turn, we count each time we change the character we are printing as a turn, and we can go back over what we've already typed.
The second bullet point from the problem description is basically saying that we can go back and write over characters we have already printed in previous steps. You could think about it like using an old-school typewriter: it always moves left to right, you have unlimited white-out, and for some reason you want to switch keys as few times as possible.
For example, consider the string s = "aba". We can print it in two ways:
-
Method 1:
- Turn 1: Print
a. - Turn 2: Print
baftera. - Turn 3: Print
aafterb.
- Turn 1: Print
-
Method 2:
- Turn 1: Print
aaa. - Turn 2: Print
bin the middle.
- Turn 1: Print
In this case, we return 2 as the least number of steps required.
Our clue to use dynamic programming is the requirement to find the least number of turns to achieve a goal. This suggests both optimal substructure and overlapping subproblems, where finding the minimum turns for different parts of the string often requires repeated calculations.
Intuition
Instead of analyzing the string from left to right, we want to consider the entire string and identify segments we can print in one turn. If the character at the end of one segment matches the start of the next, you can potentially print them in one turn and then override the middle character(s) in a later turn. This recursive function will break down the string into smaller substrings and determine the minimum number of turns required for each substring.
We will consider different ways of breaking the string and choose the most efficient option. For example:
s = "cabad"
Two possible ways to split this string are:
1. "c" + "aba" + "d"
2. "cab" + "ad"To optimize runtime, we first remove consecutive duplicate characters in the input string. This reduction doesn't change the minimum number of turns needed but can significantly decrease the problem size.
The recursive function, minimumTurns, calculates the minimum number of turns needed to print the substring from index start to end. The recursive relation is as follows:
- Base Case: If
start > end, the substring is empty and requires 0 turns. - Initial Case: Start with the worst-case scenario:
1 + minimumTurns(start + 1, end). - Optimization Case: We try to optimize by checking for matching characters and reducing the number of turns.
Using memoization, we store the results of sub-problems in a cache, preventing redundant computations.
Algorithm
- Call
removeDuplicatesto remove consecutive duplicate characters. - Initialize a 2-D array
memoto store the minimum number of turns. - Call the recursive function
minimumTurnsto compute the result.
Implementation
// solution code - 12ms, 10.60MB
class Solution {
public:
int strangePrinter(string s) {
// Preprocess the string to remove consecutive duplicate characters
s = removeDuplicates(s);
int n = s.length();
// Initialize memoization array
vector<vector<int>> memo(n, vector<int>(n, -1));
// Start the recursive process
return minimumTurns(0, n - 1, s, memo);
}
private:
int minimumTurns(int start, int end, string& s, vector<vector<int>>& memo) {
// Base case: empty string requires 0 turns
if (start > end) {
return 0;
}
// If result is memoized, return it
if (memo[start][end] != -1) {
return memo[start][end];
}
// Initialize with worst case: print each character separately
int minTurns = 1 + minimumTurns(start + 1, end, s, memo);
// Try to optimize by finding matching characters
for (int k = start + 1; k <= end; k++) {
if (s[k] == s[start]) {
// If match found, try splitting the problem
int turnsWithMatch = minimumTurns(start, k - 1, s, memo) +
minimumTurns(k + 1, end, s, memo);
minTurns = min(minTurns, turnsWithMatch);
}
}
// Memoize and return the result
return memo[start][end] = minTurns;
}
string removeDuplicates(string& s) {
string uniqueChars;
int i = 0;
while (i < s.length()) {
char currentChar = s[i];
uniqueChars += currentChar;
// Skip all consecutive occurrences of the current character
while (i < s.length() && s[i] == currentChar) {
i++;
}
}
return uniqueChars;
}
};Complexity Analysis
Let s.
-
Time complexity:
$O(n^3)$ .- The recursive function considers all substrings of the input string. For each substring, it iterates through it to find matching characters, leading to a time complexity of
$O(n^3)$ .
- The recursive function considers all substrings of the input string. For each substring, it iterates through it to find matching characters, leading to a time complexity of
-
Space complexity:
$O(n^2)$ .- The memoization array
memohas dimensions$n \times n$ .
- The memoization array
Intuition
In our previous approach, we used a top-down recursive solution with memoization. To optimize further, we’ll switch to a bottom-up dynamic programming approach to eliminate recursion.
We use a 2-D array minTurns of size minTurns[i][j] represents the minimum number of turns needed to print the substring from index i to j. First, we set up the base case: substrings of length 1 require 1 turn to print. Then we build the solution for substrings of all lengths, using previously computed values.
Algorithm
- Call
removeDuplicatesto remove consecutive duplicate characters. - Initialize a 2-D array
minTurnsof size$n \times n$ . - Use nested loops to iterate over increasing lengths of substrings.
- Compute the minimum turns needed for each substring and store the result in
minTurns.
Implementation
// solution code - 31ms, 10.47MB
class Solution {
public:
int strangePrinter(string s) {
// Preprocess the string to remove consecutive duplicate characters
s = removeDuplicates(s);
int n = s.length();
// dp[i][j] represents the minimum number of turns to print s[i] to s[j]
vector<vector<int>> minTurns(n, vector<int>(n, 0));
// Initialize base case
for (int i = 0; i < n; i++) {
// It takes 1 turn to print a single character
minTurns[i][i] = 1;
}
// Fill the dp table
for (int length = 2; length <= n; length++) {
for (int start = 0; start + length - 1 < n; start++) {
int end = start + length - 1;
// Initialize with worst case: print each character separately
minTurns[start][end] = length;
// Try all possible splits and find the minimum
for (int split = 0; split < length - 1; split++) {
int totalTurns = minTurns[start][start + split] +
minTurns[start + split + 1][end];
// If the characters at the split and end match, we can save
// one turn
if (s[start + split] == s[end]) {
totalTurns--;
}
minTurns[start][end] =
min(minTurns[start][end], totalTurns);
}
}
}
// Return the minimum turns needed to print the entire string
return minTurns[0][n - 1];
}
private:
string removeDuplicates(string& s) {
string uniqueChars;
int i = 0;
while (i < s.length()) {
char currentChar = s[i];
uniqueChars += currentChar;
// Skip all consecutive occurrences of the current character
while (i < s.length() && s[i] == currentChar) {
i++;
}
}
return uniqueChars;
}
};Complexity Analysis
Let s.
-
Time complexity:
$O(n^3)$ .- The main loop iterates over all substrings and computes the minimum number of turns using dynamic programming, which leads to a time complexity of
$O(n^3)$ .
- The main loop iterates over all substrings and computes the minimum number of turns using dynamic programming, which leads to a time complexity of
-
Space complexity:
$O(n^2)$ .- The 2-D array
minTurnshas dimensions$n \times n$ .
- The 2-D array
// 3ms, 7.64MB
class Solution {
public:
int findComplement(int num) {
string tmp;
while(num) {
if(num & 1) tmp.push_back('1');
else tmp.push_back('0');
num >>= 1;
}
int answer{}, d = 1 << (static_cast<int>(tmp.size()) - 1);
for(auto it = rbegin(tmp);it!=rend(tmp);++it) {
if(*it == '0') answer += d;
d >>= 1;
}
return answer;
}
};Certainly! Let's break down the problem and solve it step by step.
Given a positive integer num, you need to find the complement of its binary representation. The complement of a binary number is obtained by flipping all its bits (changing 0 to 1 and 1 to 0).
- Input:
num = 5 - Output:
2 - Explanation:
- The binary representation of
5is101. - Flipping the bits gives
010, which is2in decimal.
- The binary representation of
-
Find the Binary Length: Determine the number of bits in the binary representation of the given number. For instance,
5has a binary length of3(i.e.,101). -
Create a Mask: Create a mask that has the same number of bits, all set to
1. For example, fornum = 5, the mask would be111(which is7in decimal). -
Compute the Complement: The complement can be obtained by XOR-ing the number with the mask:
5 XOR 7gives2.
// 0ms, 7.51MB
class Solution {
public:
int findComplement(int num) {
// Calculate the number of bits required to represent num
int mask = 0;
int temp = num;
while (temp > 0) {
mask = (mask << 1) | 1;
temp >>= 1;
}
// The complement is the XOR of num with the mask
return num ^ mask;
}
};-
Mask Calculation:
- The mask is created by shifting
1left for each bit innumand then OR-ing it with the current mask value. - For
num = 5(which is101), the mask will be111(or7in decimal).
- The mask is created by shifting
-
XOR Operation:
- XOR-ing
numwithmaskflips the bits, giving the complement.
- XOR-ing
-
Time Complexity:
$O(1)$ , since the number of bits in a positive integer is fixed (e.g., 32 bits). -
Space Complexity:
$O(1)$ , as we use a constant amount of extra space.
This approach is efficient and leverages bitwise operations, which are typically fast.
// 4ms, 9.2MB
class Solution {
public:
string fractionAddition(string expression) {
if (expression.front() != '-') {
expression = '+' + expression;
}
vector<int> placeToCut;
for (int s{ 1 }, e{ static_cast<int>(expression.length()) }; s < e; ++s) {
char c = expression[s];
if (c == '-' || c == '+') placeToCut.push_back(s);
}
placeToCut.push_back(static_cast<int>(expression.length()));
int index{};
vector<string> fractions;
for (int pos : placeToCut) {
fractions.push_back(expression.substr(index, pos - index));
index = pos;
}
string answer = fractions.front();
for (int s{ 1 }, e{ static_cast<int>(fractions.size()) }; s < e; ++s) {
string& second = fractions[s];
auto [fn, fd] = StrToNum(answer);
auto [sn, sd] = StrToNum(second);
int gcd = GetGCD(fd, sd);
int denominator = fd * sd / gcd;
fn = fn * denominator / fd;
sn = sn * denominator / sd;
if (answer.front() == '-') fn *= -1;
if (second.front() == '-') sn *= -1;
int nominator = fn + sn;
if (nominator == 0) {
answer = "+0/1";
}
else {
if (nominator > 0) answer = "+";
else {
answer = "-";
nominator *= -1;
}
int rGCD = GetGCD(denominator, nominator);
answer += to_string(nominator / rGCD);
answer.push_back('/');
answer += to_string(denominator / rGCD);
}
}
if (answer.front() == '-') return answer;
return answer.substr(1); // 부호 제거
}
private:
int GetGCD(int a, int b) {
if (b == 0) return a;
return GetGCD(b, a % b);
}
pair<int, int> StrToNum(string& str) {
int pos = str.find('/');
int numerator = stoi(str.substr(1, pos)); // 가장 앞에 부호
int denominator = stoi(str.substr(pos + 1));
return make_pair(numerator, denominator);
}
};Sure! Let's break down the problem and solve it step by step.
You are given a string expression representing a fraction addition and subtraction operation. The fractions are always of the form ±a/b, where a and b are integers. The goal is to return the result of the operation as a reduced fraction.
-
Input:
"1/3-1/2" -
Output:
"-1/6" -
Explanation:
- Convert
"1/3"and"-1/2"to a common denominator:2/6and-3/6. - The result of
2/6 - 3/6is-1/6.
- Convert
-
Parse the String:
- We need to parse the string to extract the fractions.
- Identify and handle both positive and negative fractions.
-
Compute the Least Common Denominator (LCD):
- To add or subtract fractions, they must have a common denominator.
- The LCD of two denominators is found using the formula:
[ \text{LCD}(a, b) = \frac{|a \times b|}{\text{gcd}(a, b)} ] - This allows us to add or subtract fractions with different denominators.
-
Simplify the Result:
- After the arithmetic operations, simplify the resulting fraction by dividing the numerator and denominator by their greatest common divisor (GCD).
class Solution {
public:
std::string fractionAddition(std::string expression) {
int numerator = 0, denominator = 1; // Start with a neutral fraction of 0/1
for (size_t i = 0; i < expression.size();) {
// Extract the sign
int sign = 1;
if (expression[i] == '-' || expression[i] == '+') {
sign = (expression[i] == '-') ? -1 : 1;
++i;
}
// Extract the numerator
int num = 0;
while (std::isdigit(expression[i])) {
num = num * 10 + (expression[i++] - '0');
}
num *= sign; // Apply the sign to the numerator
// Skip the '/'
++i;
// Extract the denominator
int denom = 0;
while (std::isdigit(expression[i])) {
denom = denom * 10 + (expression[i++] - '0');
}
// Perform the fraction addition
numerator = numerator * denom + num * denominator;
denominator *= denom;
// Simplify the fraction
int gcd = std::gcd(numerator, denominator);
numerator /= gcd;
denominator /= gcd;
}
return std::to_string(numerator) + "/" + std::to_string(denominator);
}
};-
Parsing:
- The string is parsed to extract individual fractions. Each fraction is composed of a numerator and a denominator separated by a '/'.
-
Sign Handling:
- The sign before each fraction is taken into account. If the sign is
'-', the numerator becomes negative.
- The sign before each fraction is taken into account. If the sign is
-
Fraction Addition/Subtraction:
- For each fraction, the current numerator and denominator are updated to the common denominator using the formula for adding fractions: $$ \frac{a}{b} \pm \frac{c}{d} = \frac{a \times d \pm c \times b}{b \times d} $$
- The resulting fraction is simplified using the GCD of the numerator and denominator.
-
Output:
- The final fraction is returned as a string in the form
"numerator/denominator".
- The final fraction is returned as a string in the form
-
Time Complexity:
$O(n)$ , wherenis the length of the expression, since each character in the string is processed once. -
Space Complexity:
$O(1)$ , since the space used is constant.
This approach ensures that the fractions are correctly added or subtracted and returned in their simplest form.
해설 참고
Intuition
The problem asks us to find the closest palindrome to a given integer n represented as a string. The string length is at most 18, meaning n can be as large as 999,999,999,999,999,999. The goal is to return the nearest palindrome to n that is not equal to n itself, minimizing the absolute difference.
To solve this, we can think of a palindrome as a number where the first half is mirrored to create the second half. For example, the palindrome for 12321 is formed by reversing the first half (12) and appending it to itself (12 -> 12321). This observation is key to finding the closest palindrome.
If we consider changing the second half of n to match the reverse of the first half, we might obtain a palindrome close to n. However, there are cases where this method might not give us the optimal answer, particularly for odd-length strings or when small adjustments to the first half could yield a closer palindrome.
For instance, consider n = 139. If we mirror the first half (13), we get 131, but a closer palindrome is 141. Therefore, it's important to also check palindromes formed by slightly adjusting the first half of n:
- Same Half: Create a palindrome by mirroring the first half.
- Decremented Half: Create a palindrome by decrementing the first half by 1 and mirroring it.
- Incremented Half: Create a palindrome by incrementing the first half by 1 and mirroring it.
Note
Adding +1 or subtracting -1 to/from the first half ensures that we stay as close as possible to the original number while creating new potential palindromes. If we were to add or subtract a larger value, such as +2 or -2, the resulting palindrome would be farther away from the original number, potentially missing a closer palindrome, and it's given that we need to find the closest palindrome.
In addition to these cases, we must handle edge cases where n is close to numbers like 1000, 10000, etc., or very small numbers like 11 or 9. These can produce palindromes like 99, 999, or 101, 1001, which might be closer to n.
To summarize, we need to check the following five candidates:
- Palindrome formed from the first half of
n. - Palindrome formed from the first half decremented by 1.
- Palindrome formed from the first half incremented by 1.
- Nearest palindrome of the form
99,999, etc. - Nearest palindrome of the form
101,1001, etc.
After generating these candidates, we compare them to n and choose the one with the smallest absolute difference.
Algorithm
Main Function - nearestPalindromic(n)
- Calculate the length of
nand determine the midpoint. - Extract the first half of the number.
- Generate possible palindromic candidates and append them to
possibilitieslist:- Mirror the first half and append it to the string.
- Mirror the first half incremented by 1 and append it to the string.
- Mirror the first half decremented by 1 and append it to the string.
- Add the form 999....
- Add the form 100...001.
- Find the nearest palindromic number by comparing absolute differences.
- Return the closest palindrome.
Helper Function - halfToPalindrome(left, even)
- Initialize
reswithleft. - If the length is odd, divide
leftby 10. - Mirror the digits of
leftto form a palindrome. - Return the palindrome
res.
Implementation
// 0ms, 8.2MB
class Solution {
public:
string nearestPalindromic(string n) {
int len = n.size();
int i = len % 2 == 0 ? len / 2 - 1 : len / 2;
long firstHalf = stol(n.substr(0, i + 1));
/*
Generate possible palindromic candidates:
1. Create a palindrome by mirroring the first half.
2. Create a palindrome by mirroring the first half incremented by 1.
3. Create a palindrome by mirroring the first half decremented by 1.
4. Handle edge cases by considering palindromes of the form 999...
and 100...001 (smallest and largest n-digit palindromes).
*/
vector<long> possibilities;
possibilities.push_back(halfToPalindrome(firstHalf, len % 2 == 0));
possibilities.push_back(halfToPalindrome(firstHalf + 1, len % 2 == 0));
possibilities.push_back(halfToPalindrome(firstHalf - 1, len % 2 == 0));
possibilities.push_back((long)pow(10, len - 1) - 1);
possibilities.push_back((long)pow(10, len) + 1);
long diff = LONG_MAX, res = 0, nl = stol(n);
for (auto cand : possibilities) {
if (cand == nl) continue;
if (abs(cand - nl) < diff) {
diff = abs(cand - nl);
res = cand;
} else if (abs(cand - nl) == diff) {
res = min(res, cand);
}
}
return to_string(res);
}
private:
long halfToPalindrome(long left, bool even) {
long res = left;
if (!even) left = left / 10;
while (left > 0) {
res = res * 10 + left % 10;
left /= 10;
}
return res;
}
};Complexity Analysis
Let n be the number of digits in the input number.
-
Time complexity:
$O(n)$ - We perform operations on exactly 5 strings. The palindrome construction for each string takes
$O(n)$ time. Therefore, total time complexity is given by$O(n)$ .
- We perform operations on exactly 5 strings. The palindrome construction for each string takes
-
Space complexity:
$O(n)$ - We store the 5 possible candidates in the
possibilitiesarray. Apart from this, the built-in functions used to make thefirstHalfcan potentially lead to$O(n)$ space complexity, as they copy the characters into a new String. Therefore, the total space complexity is$O(n)$ .
- We store the 5 possible candidates in the
Intuition
Another way to solve the problem is by using binary search. The task is to find the smallest palindrome greater than n and the largest palindrome smaller than n, then return the one with the smallest absolute difference. Since this is a minimization/maximization, we can try to use binary search to solve this problem. But, our search space should be sorted to apply binary search. Observe that when you construct the palindromes using the first half for two integers, then the greater integer would always have its constructed palindrome greater. Therefore, our search space is sorted in a non-decreasing order.
Given that palindromes are symmetric numbers, we can search within a specific range by leveraging binary search. The key is to first determine potential palindromes by constructing them based on the first half of n.
Finding the Next Palindrome:
- Start with the left boundary as
n + 1and the right boundary as an infinitely large value. - Perform binary search within this range. For each midpoint value, construct the palindrome by mirroring its first half.
- If the constructed palindrome is greater than
n, shift the search to the left (smaller values). Otherwise, move to the right.
Finding the Previous Palindrome:
- Start with the left boundary as
0and the right boundary asn - 1. - Perform binary search, constructing palindromes as above.
- If the constructed palindrome is smaller than
n, shift the search to the right (larger values). Otherwise, move to the left.
Binary search efficiently narrows down the range of possible palindromes, finding the closest one that is greater and the closest one that is smaller. Once we have these two candidates, we simply compare their differences with n to determine the closest palindrome.
Algorithm
convert(num)
- Convert the number
numto a strings. - Identify the midpoint indices
l (left)andr (right). - Mirror the left half of the string
sonto the right half to create a palindrome. - Return the palindrome as a long integer.
nextPalindrome(num)
- Initialize
leftto0andrighttonum. - Use binary search to find the next palindrome greater than
num:- Calculate
midas the midpoint betweenleftandright. - Convert
midto a palindrome usingconvert(mid). - If the palindrome is less than
num, updateansto the palindrome and setlefttomid + 1. - Otherwise, set
righttomid - 1.
- Calculate
- Return the result
ans.
previousPalindrome(num)
- Initialize
lefttonumandrightto a large value(1e18). - Use binary search to find the previous palindrome smaller than
num:- Calculate
midas the midpoint betweenleftandright. - Convert
midto a palindrome usingconvert(mid). - If the palindrome is greater than
num, updateansto the palindrome and setrighttomid - 1. - Otherwise, set
lefttomid + 1.
- Calculate
- Return the result
ans.
Main Function - nearestPalindromic(n)
- Convert the input string
nto a long integernum. - Call
nextPalindrome(num)to find the next palindrome greater thannum. - Call
previousPalindrome(num)to find the previous palindrome smaller thannum. - Compare the differences between
numand the two palindromes found:- If the difference with the next palindrome is less than or equal to the difference with the previous palindrome, return the next palindrome. Otherwise, return the previous palindrome as a string.
Implementation
// 4ms, 11.4MB
class Solution {
public:
// Convert to palindrome keeping first half constant.
long long convert(long long& num) {
string s = to_string(num);
int n = s.length();
int l = (n - 1) / 2, r = n / 2;
while (l >= 0) s[r++] = s[l--];
return stoll(s);
}
// Find the previous palindrome, just smaller than n.
long long previousPalindrome(long long num) {
long long left = 0, right = num;
long long ans = INT_MIN;
while (left <= right) {
long long mid = (right - left) / 2 + left;
long long palin = convert(mid);
if (palin < num) {
ans = palin;
left = mid + 1;
} else {
right = mid - 1;
}
}
return ans;
}
// Find the next palindrome, just greater than n.
long long nextPalindrome(long long num) {
long long left = num, right = 1e18;
long long ans = INT_MIN;
while (left <= right) {
long long mid = (right - left) / 2 + left;
long long palin = convert(mid);
if (palin > num) {
ans = palin;
right = mid - 1;
} else {
left = mid + 1;
}
}
return ans;
}
string nearestPalindromic(string n) {
int len = n.size();
long long num = stoll(n);
long long a = previousPalindrome(num);
long long b = nextPalindrome(num);
if (abs(a - num) <= abs(b - num)) return to_string(a);
return to_string(b);
}
};Complexity Analysis
Let m be the input number and n be the number of digits in it.
-
Time complexity:
$O(n ⋅ log(m))$ - We perform two binary search operations on a search space of size
m, and in each operation iterate through all the digits. Therefore, the total time complexity is given byO(n ⋅ log(m)).
- We perform two binary search operations on a search space of size
-
Space complexity:
$O(n)$ - The space complexity is primarily determined by the storage needed for the string representation of the number and the intermediate list or character array used for manipulation. Since these data structures are proportional to the number of digits in
$O(n)$ , the total space complexity is$O(n)$ .
- The space complexity is primarily determined by the storage needed for the string representation of the number and the intermediate list or character array used for manipulation. Since these data structures are proportional to the number of digits in
- For C++:
to_string(num)- Converts the number to a string, which requires space proportional to the number of digits in$O(n)$ . - For Java:
Long.toString(num)- Converts the number to a string, requiring$O(n)$ space. - For Python:
''.join(s_list)- Creates a new string from the list, requiring$O(n)$ space.
// 4ms, 10.3MB
/**
* 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:
vector<int> postorderTraversal(TreeNode* root) {
if(root != nullptr) makePostOrder(root);
return answer;
}
private:
vector<int> answer;
void makePostOrder(TreeNode* node) {
// postorder: left - right - root
if(node->left) makePostOrder(node->left);
if(node->right) makePostOrder(node->right);
answer.push_back(node->val);
}
};The problem you've shared is a LeetCode problem that involves performing a postorder traversal on a binary tree. Let's break down the solution.
You need to perform a postorder traversal on a binary tree. In postorder traversal, you visit nodes in the following order:
- Traverse the left subtree.
- Traverse the right subtree.
- Visit the root node.
Given the root of a binary tree, you need to return the postorder traversal of its nodes' values.
There are several ways to implement the postorder traversal of a binary tree: recursively, iteratively using two stacks, or iteratively using one stack and a visited node indicator.
The recursive approach is straightforward because it directly follows the postorder definition.
// 0ms, 10.27MB
/**
* 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:
void postorder(TreeNode* node, vector<int>& result) {
if (node == nullptr) return;
postorder(node->left, result); // Traverse left subtree
postorder(node->right, result); // Traverse right subtree
result.push_back(node->val); // Visit root node
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
postorder(root, result);
return result;
}
};An iterative approach using one stack and a visited node indicator can be a bit more complex but is more memory efficient.
// 4ms, 10.15MB
/**
* 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:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
if (!root) return result;
stack<TreeNode*> stack;
TreeNode* lastVisited = nullptr;
TreeNode* current = root;
while (!stack.empty() || current != nullptr) {
if (current != nullptr) {
stack.push(current);
current = current->left;
} else {
TreeNode* node = stack.top();
if (node->right != nullptr && lastVisited != node->right) {
current = node->right;
} else {
result.push_back(node->val);
lastVisited = node;
stack.pop();
}
}
}
return result;
}
};-
Recursive Approach:
- The
postorderfunction is called recursively on the left and right children, and then the current node's value is added to the result list. - This is the simplest and most intuitive approach, but it can lead to stack overflow for very deep trees.
- The
-
Iterative Approach:
- This approach uses a stack to simulate the call stack used in the recursive approach.
- It keeps track of the last visited node to ensure that nodes are added to the result list only after their children have been fully processed.
Both approaches have a time complexity of n is the number of nodes in the tree, since each node is visited exactly once.
-
Recursive Approach:
$O(h)$ , wherehis the height of the tree, due to the recursive call stack. -
Iterative Approach:
$O(h)$ , wherehis the height of the tree, due to the stack used for the traversal.
Choose the recursive approach for its simplicity unless you are working with very deep trees, in which case the iterative approach might be safer to avoid stack overflow.
You can run either of the above solutions in your LeetCode environment, and they should work correctly for the problem provided.
