MindMap Gallery Code compilation of key ideas for the most powerful deduction algorithm questions
I have compiled the main ideas and codes of the algorithm questions above. This mind map will continue to be updated. Friends who purchased it can add me as friends through the blog in my personal introduction. I will continue to provide updates, and you can also discuss with me. Algorithmic problem.
Edited at 2021-06-29 20:35:23One Hundred Years of Solitude is the masterpiece of Gabriel Garcia Marquez. Reading this book begins with making sense of the characters' relationships, which are centered on the Buendía family and tells the story of the family's prosperity and decline, internal relationships and political struggles, self-mixing and rebirth over the course of a hundred years.
One Hundred Years of Solitude is the masterpiece of Gabriel Garcia Marquez. Reading this book begins with making sense of the characters' relationships, which are centered on the Buendía family and tells the story of the family's prosperity and decline, internal relationships and political struggles, self-mixing and rebirth over the course of a hundred years.
Project management is the process of applying specialized knowledge, skills, tools, and methods to project activities so that the project can achieve or exceed the set needs and expectations within the constraints of limited resources. This diagram provides a comprehensive overview of the 8 components of the project management process and can be used as a generic template for direct application.
One Hundred Years of Solitude is the masterpiece of Gabriel Garcia Marquez. Reading this book begins with making sense of the characters' relationships, which are centered on the Buendía family and tells the story of the family's prosperity and decline, internal relationships and political struggles, self-mixing and rebirth over the course of a hundred years.
One Hundred Years of Solitude is the masterpiece of Gabriel Garcia Marquez. Reading this book begins with making sense of the characters' relationships, which are centered on the Buendía family and tells the story of the family's prosperity and decline, internal relationships and political struggles, self-mixing and rebirth over the course of a hundred years.
Project management is the process of applying specialized knowledge, skills, tools, and methods to project activities so that the project can achieve or exceed the set needs and expectations within the constraints of limited resources. This diagram provides a comprehensive overview of the 8 components of the project management process and can be used as a generic template for direct application.
Table of contents
Likou algorithm
0. Reversal thinking
6.Zigzag transformation
1. Sort by row
0.Title
Arrange a given string in a Z-shape from top to bottom and from left to right according to the given number of lines. After that, your output needs to be read line by line from left to right to generate a new string.
1. Ideas
Iterate over ss from left to right, adding each character to the appropriate line. The appropriate row can be tracked using the current row and current direction variables. The current direction will only change when we move up to the top row or down to the bottom row.
2.Code
class Solution { public String convert(String s, int numRows) { if (numRows == 1) return s; List<StringBuilder> rows = new ArrayList<>(); //Storage the characters of each row after transformation for (int i = 0; i < Math.min(numRows, s.length()); i ) rows.add(new StringBuilder()); int curRow = 0; boolean goingDown = false; for (char c : s.toCharArray()) { //Traverse s directly, convert it into a character array, and assign it to c one by one rows.get(curRow).append(c); if (curRow == 0 || curRow == numRows - 1) goingDown = !goingDown; curRow = goingDown ? 1 : -1; } StringBuilder ret = new StringBuilder(); for (StringBuilder row : rows) ret.append(row); return ret.toString(); } }
3. Complexity
Time O(n), where n=len(s), space O(n)
1. Pure Algorithm
7. Integer reversal
1. Question
Given a 32-bit signed integer, you need to reverse each bit of the integer. Assume that our environment can only store 32-bit signed integers. If the integer overflows after inversion, 0 will be returned.
2. Ideas
To "pop" and "push" numbers without the help of a auxiliary stack/array, we can use math, take out the last digit first, then divide by 10 to get rid of the last digit, invert the number and keep multiplying itself After 10, add the last digit taken out, and first determine whether it will overflow.
3.Code
class Solution { public int reverse(int x) { int rev = 0; while (x != 0) { int pop = x % 10; //Take out the last digit of x x /= 10; //Remove the last digit of x //When int occupies 32 bits, the value range is -2147483648~2147483647, so pop>7 or pop<-8 if (rev > Integer.MAX_VALUE/10 || (rev == Integer.MAX_VALUE / 10 && pop > Integer.MAX_VALUE % 10)) return 0; if (rev < Integer.MIN_VALUE/10 || (rev == Integer.MIN_VALUE / 10 && pop < Integer.MIN_VALUE % 10)) return 0; rev = rev * 10 pop; //After the whole moves forward one digit, add the last digit } return rev; } }
4. Complexity
Time: O(log(x)), space O(1)
2.Array
3. Linked list
4. String
5.Binary search
6.Tree
1. Binary tree
7.Breadth first
8. Depth first
9.Double pointers
10. Sorting
11.Backtracking method
12. Hash table
13.Stack
14. Dynamic programming
subtopic
Classic interview questions
15,
Algorithm basics
1. Time complexity
1.Definition
Time complexity is a function that qualitatively describes the running time of the algorithm
2. What is Big O?
Big O is used to represent the upper bound. When it is used as the upper bound of the worst-case running time of the algorithm, it is the upper bound of the running time for any data input. In the interview, it refers to the time complexity of the algorithm. generally
3. Differences in different data sizes
Because Big O is the time complexity shown when the data magnitude breaks through a point and the data magnitude is very large. This amount of data is the amount of data where the constant coefficient no longer plays a decisive role, so we call time complexity The constant term coefficients are omitted because the default data size is generally large enough. Based on this fact, a ranking of the time complexity of the algorithm given is as follows:
O(1) constant order < O(logn) logarithmic order < O(n) linear order < O(n^2) square order < O(n^3) (cubic order) < O(2^n) (exponential order)
4.What is the base of log in O(logn)?
But we collectively say logn, that is, ignoring the description of the base, the logarithm of n with base 2 = the logarithm of 10 with base 2 * the logarithm of n with base 10, and the logarithm of 10 with base 2 is a constant that can be ignored
Question summary
0.Interesting
One pass of coding is as fierce as a tiger, defeating 500% in submission
A string of ideas bloomed with laughter, and the submission beat 80%
1. Don’t understand
1.Tree
145. Postorder traversal Morris traversal
105. Why is it not possible to use index mapping in the concise method?
2. Interview frequency
1.Binary search
4,50,33,167,287,315,349,29,153,240,222,327,69,378,410,162,1111,35,34,300,363,350,209,354,278,374,981,174
2.Breadth first
200,279,301,199,101,127,102,407,133,107,103,126,773,994,207,111,847,417,529,130,542,690,,,743,210,913,512
3. Hash table
1,771,3,136,535,138,85,202,149,49,463,739,76,37,347,336,219,18,217,36,349,560,242,187,204,500,811,609
4. Backtracking method
22,17,46,10,39,37,79,78,51,93,89,357,131,140,77,306,1240,401,126,47,212,60,216,980,44,52,784,526
5. Linked list
2,21,206,23,237,148,138,141,24,234,445,147,143,92,25,160,328,142,203,19,86,109,83,61,82,430,817,
6. Sort
148,56,147,315,349,179,253,164,242,220,75,280,327,973,324,767,350,296,969,57,1329,274,252,1122,493,1057,1152,1086
7. Depth first
200,104,1192,108,301,394,100,105,695,959,124,99,979,199,110,101,114,109,834,116,679,339,133,,,257,546,364,
8.Tree
104,226,96,617,173,1130,108,297,100,105,95,124,654,669,99,979,199,110,236,101,235,114,94,222,102,938,437
9.Array
1,4,11,42,53,15,121,238,561,85,169,66,88,283,16,56,122,48,31,289,41,128,152,54,26,442,39
10.Double pointers
11,344,3,42,15,141,88,283,16,234,26,76,27,167,18,287,349,28,142,763,19,30,75,86,345,125,457,350
11.Stack
42,20,85,155,739,173,1130,316,394,341,150,224,94,84,770,232,71,496,103,144,636,856,907,682,975,503,225,145
12 strings
5,20,937,3,273,22,1249,68,49,415,76,10,17,91,6,609,93,227,680,767,12,8,67,126,13,336,
subtopic
Similar questions
1.The sum of two numbers
1,15,
Code related
1. Code style
1. Core Principles
1.Indentation
It is preferred to use 4 spaces. At present, almost all IDEs convert tabs to 4 spaces by default, so there is no big problem.
2. Maximum length of line
79 characters, it would look better to use backslashes for line breaks
3.Import
1.Format
The import is at the top of the file, after the file comment. The import is usually a single line import or from ... import
2. Sequence
1. Standard library import 2. Relevant third-party imports 3. Specific local application/library imports Put a blank line between each import group. Absolute imports are recommended because they are more readable; explicit relative imports can be used instead of absolute imports when dealing with complex package layouts, which are too verbose
3. Attention
4. Comments
5. Document strings docstrings
6. Naming convention
Functions, variables, and properties are spelled in lowercase letters, with underscores between words, for example, lowercase_underscore. Classes and exceptions should be named with the first letter of each word capitalized (high camel case), such as CapitalizedWord. Protected instance attributes should begin with a single underscore. Private instance properties should start with two underscores. Module-level constants should be spelled in all capital letters, with underscores between words. The first parameter of the instance method in the class should be named self, which represents the object itself. The first parameter of the class method (cls method) should be named cls, indicating the class itself
2. Pay attention to details
0.Tools
1. Code wrap
Newlines should precede binary operators
2. Blank line
There are two blank lines between the top-level function and the class definition. There is a blank line between function definitions inside the class
3. String quotes
Double quotes and single quotes are the same, but it is best to follow a style. I am used to using single quotes because: No need to hold down the Shift key when writing code to improve efficiency Some language strings must use double quotes, and Python does not need to add backslash escaping when processing them.
4. Space
Use space for indentation instead of the tab key. Use four spaces for each level of syntax-related indentation. For long expressions that span multiple lines, all but the first line should be indented 4 spaces above the usual level. When using subscripts to retrieve list elements, call functions, or assign values to keyword arguments, do not add spaces around them. When assigning a value to a variable, a space should be written on the left and right sides of the assignment symbol, and only one
Immediately inside parentheses, brackets, or braces Immediately before a comma, semicolon or colon, there must be a space after it. Immediately before the opening parenthesis of a function parameter list Immediately before the opening parenthesis of an index or slicing operation Always place 1 space on either side of the following binary operators: assignment (=), incremental assignment (=, -=, etc.), comparison (==, <, >, !=, <>, <=, > =, in, not, in, is, is not), Boolean (and, or, not) Put spaces around math operators When used to specify keyword arguments or default argument values, do not use spaces around = return magic(r=real, i=imag)
Add necessary spaces, but avoid unnecessary spaces. Always avoid trailing whitespace, including any invisible characters. Therefore, if your IDE supports displaying all invisible characters, turn it on! At the same time, if the IDE supports deleting blank content at the end of the line, then please enable it too! yapf can help us solve this part. We only need to format the code after writing it.
5. Compound statement
It is not recommended to include multiple statements in one line
6. Trailing comma
When list elements, parameters, and import items may continue to increase in the future, leaving a trailing comma is a good choice. The usual usage is that each element is on its own line, with a comma at the end, and a closing tag is written on the next line after the last element. If all elements are on the same line, there is no need to do this
7. Fix problems detected by linter
Use flake8 to check Python code and modify all checked Errors and Warnings unless there are good reasons.
2. Commonly used codes
1. Dictionary
1. Count the number of occurrences
counts[word] = counts.get(word,0) 1
2. List
1. List specified keyword sorting
items.sort(key=lambda x:x[1], reverse=True)# Sort the second element in reverse order
2. Convert each element in the string list into a number list
list(map(eval,list))
3. Reverse all elements in the list
res[::-1]
4. Exchange two numbers in the list
res[i],res[j] = res[j],res[i] #No intermediate variables required
5. Allocate a fixed-length list
G = [0]*(n 1) #List of length n 1
6. Find the largest element in the interval [i,j] in the arr list
max(arr[i:j 1])
7. Use this element to split the in-order list
left_l = inorder[:idx] right_l = inorder[idx 1:]
3. Loop/judgment
1. When you only need to determine the number of loops and do not need to obtain the value
for _ in range(len(queue)):
2.Abbreviation of if...else
node.left = recur_func(left_l) if left_l else None
3. Reverse cycle
for i in range(len(postorder)-2, -1,-1)
4. Get elements and subscripts at the same time
for i, v in enumerate(nums):
5. Traverse multiple lists at the same time and return multiple values
for net, opt, l_his in zip(nets, optimizers, losses_ his):
Without zip, only one value will be output each time
6. Traverse multiple lists and return a value
for label in ax.get_xticklabels() ax.get_yticklabels():
4. Mapping
1. Quickly convert traversable structures into mappings corresponding to subscripts
index = {element: i for i, element in enumerate(inorder)}
It can also be achieved using list.index(x), but the list must be traversed every time, and the time is O(n). The above is O(1)
3. Commonly used methods
1. Comes with the system
0.Classification
1.Type conversion
1.int(x)
1. When the floating point type is converted to an integer type, the decimal part is directly discarded.
2.float(x)
3.str(x)
1.sorted(num)
1. Sort the specified elements
2.map(func,list)
1. Apply the function of the first parameter to each element of the second parameter
map(eval,list)
3.len(i)
1. Get the length, which can be of any type
4.enumerate()
1. Combine a traversable data object (such as a list, tuple or string) into an index sequence, and list the data subscript and data at the same time. Generally used in for loops: for i, element in enumerate(seq) :
2.enumerate(sequence, [start=0]), the subscript starts from 0, returns the enumerate (enumeration) object
5.type(x)
1. Determine the type of variable x, applicable to any data type
2. If you need to use the variable type as a condition in conditional judgment, you can use the type() function for direct comparison.
if type(n) == type(123):
2.List[]
1. When stack is used
1. Push into the stack
stack.append()
2. Pop out of the stack
stack.pop()
Pop the last element
3. Return the top element of the stack
There is no peek method in the list. You can only pop first and then append.
2. When used in a queue
1. Join the team
queue.append()
2.Leave the team
queue.pop(0)
Dequeue the first element
3. Get element subscript
m_i = nums.index(max_v)
3. Queue deque
1. Double-ended queue is used as a queue
1. Join the team
queue.append()
2.Leave the team
queue.popleft()
4. Common mistakes/differences
1. Question format
1.IndentationError:expected an indented block
There is a problem with indentation. The most common mistake is to mix the Tab and Space keys to achieve code indentation.
Another common reason for the above error is that there is no first line indentation. For example, when writing an if statement, add a colon after it. If you directly change the line, many code editors will automatically indent the first line. However, some code editors may not have this function. In this case, you need to manually indent. It is best to develop a habit. Please don't hit the space bar several times in a row. It is recommended to just press the Tab key.
1.pycharm problem
1. How to get rid of the prompt that the server certificate is not trusted in pycharm
Click File > Settings > Tools > Server Certificates > Accept non-trusted certificates automatically
1.pycharm skills
1. Import Python files into the current project
Directly copy the corresponding file in the file manager to the directory corresponding to the current project, and it will appear directly in pycharm. If the version problem does not occur, collapse the current project directory yourself and then expand it again.
2. Operation error
0. To look at the error type, be sure to look at the error on the line above the last dividing line.
Other errors
1."no module named XX"
As everyone's development level improves and the complexity of the program increases, more and more modules and third-party libraries will be used in the program. The reason for this error is that the library "XX" is not installed, pip install ww
1.error
1.Error: Command errored out with exit status 1
Download the corresponding third-party installation package and enter the download directory to install the full name of the downloaded file.
2.error: Microsoft Visual C 14.0 is required
Install Microsoft Visual C 14.0, the blog has the download address visualcppbuildtools_full
3.error: invalid command 'bdist_wheel'
pip3 install wheel
2.AttributeError Property error
0. General solution
If it prompts which module file has an error, find this module, delete it, and replace it with the same file from a friend that can run.
1.AttributeError: module 'xxx' has no attribute 'xxx'
1. The file name conflicts with python’s own keywords and the like.
2. Two py files import each other, causing one of them to be deleted.
2.AttributeError: module 'six' has no attribute 'main'
pip version problem 10.0 does not have main(), Need to downgrade the version: python -m pip install –upgrade pip==9.0.1
Some APIs have changed after Pip v10, resulting in incompatibility between the old and new versions, which affects our installation and update of packages. Just update the IDE and you’re done! When updating, the IDE also gave us friendly prompts. PyCharm -> Help -> Check for Updates
Do not update the cracked version, otherwise the activation information will become invalid.
3.AttributeError: module 'async io' has no attribute 'run'
You named an asyncio py file If the check is not the first one, you need to check your python version because python3.7 and later only support the run method. 1 Upgrade python version 2 run is rewritten as follows loop = asyncio.get_event_loop() result = loop.run_until_complete(coro)
4.AttributeError: module 'asyncio.constants' has no attribute '_SendfileMode'
Replace constants files in asyncio
3.TypeError
1. "TypeError: 'tuple' object cannot be interpreted as an integer"
t=('a','b','c') for i in range(t):
Typical type error problem. In the above code, the range() function expects the incoming parameter to be an integer (integer), but the incoming parameter is a tuple (tuple). The solution is to change the input parameter tuple t to The number of tuples can be of integer type len(t). For example, change range(t) in the above code to range(len(t))
2. "TypeError: 'str' object does not support item assignment"
Caused by trying to modify the value of a string, which is an immutable data type
s[3] = 'a' becomes s = s[:3] 'a' s[4:]
3. "TypeError: Can't convert 'int' object to str implicitly"
Caused by trying to concatenate a non-string value with a string, just cast the non-string to a string with str()
4. "TypeError: unhashable type: 'list'
When using the set function to construct a set, the elements in the given parameter list cannot contain lists as parameters.
5.TypeError: cannot use a string pattern on a bytes-like object
When switching between python2 and python3, you will inevitably encounter some problems. In python3, Unicode strings are in the default format (str type), and ASCII-encoded strings (bytes type). The bytes type contains byte values and is not actually a character. String, python3 and bytearray byte array type) must be preceded by operator b or B; in python2, it is the opposite, ASCII encoded string is the default, and Unicode string must be preceded by operator u or U
import chardet #Need to import this module and detect the encoding format encode_type = chardet.detect(html) html = html.decode(encode_type['encoding']) #Decode accordingly and assign it to the original identifier (variable)
4.IOError
1. "IOError: File not open for writing"
>>> f=open ("hello.py") >>> f.write ("test")
The cause of the error is that the read-write mode parameter mode is not added to the incoming parameters of open("hello.py"), which means that the default way to open the file is read-only.
The solution is to change the mode mode to write mode permission w, f = open("hello. py", "w ")
5.SyntaxError Grammatical errors
1.SyntaxError:invalid syntax”
Caused by forgetting to add colons at the end of statements such as if, elif, else, for, while, class and def
Incorrectly using "=" instead of "==". In Python programs, "=" is an assignment operator, and "==" is an equal comparison operation.
6.UnicodeDecodeError Encoding interpretation error
1.'gbk' codec can't decode byte
Most of the time this is because the file is not UTF8 encoded (for example, it may be GBK encoded) and the system uses UTF8 decoding by default. The solution is to change to the corresponding decoding method: with open('acl-metadata.txt','rb') as data:
open(path, ‘-mode-‘, encoding=’UTF-8’) i.e. open(path file name, read-write mode, encoding)
Commonly used reading and writing modes
7.ValueError
1.too many values to unpack
When calling a function, there are not enough variables to accept the return value.
8.OSError
1.WinError 1455] The page file is too small and the operation cannot be completed.
1. Restart pycharm (basically useless)
2. Set num_works to 0 (maybe useless)
3. Increase the size of the page file (completely solve the problem)
9.ImportError
1.DLL load failed: The page file is too small and the operation cannot be completed.
1. Not only is one project running, but the python program of another project is also running, just turn it off.
2. The windows operating system does not support python’s multi-process operation. The neural network uses multiple processes in data set loading, so set the parameter num_workers in DataLoader to 0.
1.DLL load failed: The operating system cannot run %1
0. Recently when I was running a scrapy project, the scrapy framework that was installed suddenly reported an error and I was caught off guard.
1. Because it is not difficult to install scrapy in Anaconda, it is not as simple and efficient as reinstalling to find a solution.
2. I can't completely uninstall it by just using the conda remove scrapy command. The reason may be that I installed scrapy twice using pip and conda respectively. Readers can try both pip and conda uninstall commands.
pip uninstall scrapy conda remove scrapy
Reinstall pip install scrapy
class error
1. Property issues of multiple inheritance of classes
We only modified A.x, why was C.x also modified? In Python programs, class variables are treated internally as dictionaries, which follow the commonly cited method resolution order (MRO). So in the above code, since the x attribute in class C is not found, it will look up its base class (although Python supports multiple inheritance, there is only A in the above example). In other words, class C does not have its own x attribute, which is independent of A. Therefore, C.x is actually a reference to A.x
scope error
1. Global variables and local variables
1. Local variable x has no initial value, and external variable X cannot be introduced internally.
2. Different operations on lists
Fool did not assign a value to lst, but fool2 did. You know, lst = [5] is the abbreviation of lst = lst [5], and we are trying to assign a value to lst (Python treats it as a local variable). In addition, our assignment to lst is based on lst itself (which is once again treated as a local variable by Python), but it has not been defined yet, so an error occurs! So here we need to distinguish between the use of local variables and external variables.
2.1 Python2 upgrade Python3 error
1.print becomes print()
1. In the Python 2 version, print is used as a statement. In the Python 3 version, print appears as a function. In the Python 3 version, all print content must be enclosed in parentheses.
2.raw_Input becomes input
In the Python 2 version, input functionality is implemented through raw_input. In the Python 3 version, it is implemented through input
3. Problems with integers and division
1. "TypeError: 'float* object cannot be interpreted as an integer"
2. In Python 3, int and long are unified into the int type. Int represents an integer of any precision. The long type has disappeared in Python 3, and the suffix L has also been deprecated. When using int exceeds the local integer size, it will not Then cause OverflowError exception
3. In previous Python 2 versions, if the parameter is int or long, the rounded down (floor) of the division result will be returned, and if the parameter is float or complex, then the equivalent result will be returned. a good approximation of the result after division
4. "/" in Python 3 always returns a floating point number, which always means downward division. Just change "/" to "//" to get the result of integer division
4. Major upgrade of exception handling
1. In Python 2 programs, the format of catching exceptions is as follows: except Exception, identifier
except ValueError, e: # Python 2 handles single exceptions except (ValueError, TypeError), e: # Python 2 handles multiple exceptions
2. In Python 3 programs, the format of catching exceptions is as follows: except Exception as identifier
except ValueError as e: # Python3 handles a single exception except (ValueError, TypeError) as e: # Python3 handles multiple exceptions
3. In Python 2 programs, the format of throwing exceptions is as follows: raise Exception, args
4. In Python 3 programs, the format of throwing exceptions is as follows: raise Exception(args)
raise ValueError, e # Python 2.x method raise ValueError(e) # Python 3.x method
5.xrange() becomes range()
"NameError: name 'xrange' is not definedw"
6. Reload cannot be used directly
"name 'reload' is not defined and AttributeError: module 'sys' has no att"
import importlib importlib.reload(sys)
7. No more Unicode types
"python unicode is not defined"
In Python 3, the Unicode type no longer exists and is replaced by the new str type. The original str type in Python 2 was replaced by bytes in Python 3
8.has_key has been abandoned
"AttributeError: 'diet' object has no attribute 'has_key' "
has_key has been abandoned in Python 3. The modification method is to use in instead of has_key.
9.urllib2 has been replaced by urllib.request
"lmportError: No module named urllib2"
The solution is to modify urllib2 to urllib.request
10. Encoding issues
TypeError: cannot use a string pattern on a bytes-like object
When switching between python2 and python3, you will inevitably encounter some problems. In python3, Unicode strings are in the default format (str type), and ASCII-encoded strings (bytes type). The bytes type contains byte values and is not actually a character. String, python3 and bytearray byte array type) must be preceded by operator b or B; in python2, it is the opposite, ASCII encoded string is the default, and Unicode string must be preceded by operator u or U
import chardet #Need to import this module and detect the encoding format encode_type = chardet.detect(html) html = html.decode(encode_type['encoding']) #Decode accordingly and assign it to the original identifier (variable)
2.1 Command prompt line command
1. Operation directory
1.cd changes the current subdirectory, you can directly copy the path and enter it in one go
2. The CD command cannot change the current disk. CD.. returns to the previous directory. CD\ means returning to the directory of the current disk. When CD has no parameters, the current directory name is displayed.
3.d: Change the disk location
2.Python related
1.pip
0.Get help
pip help
0. Change pip source
1. Temporary use
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple package name
2. Set as default
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
After setting it as default, the future installation libraries will be downloaded from Tsinghua source, and there is no need to add the mirror source URL.
3. Mainstream mirror source address
1. Install the specified library
pip install package name
2. Install the specified version of the specified library
pip install package_name==1.1.2
3. Check which libraries are installed
pip list
4. View the specific information of the library
pip show -f package name
5.Where is pip installed?
pip -V
6. Batch installation library
pip install -r d:\\requirements.txt Directly write package name == version number in the file
7. Install the library using the wheel file
1. Find the .whl file of the corresponding library on the website below, search with Ctrl F, and pay attention to the corresponding version. https://www.lfd.uci.edu/~gohlke/pythonlibs/
2. In the folder where .whl is located, press the Shift key and right-click the mouse to open the CMD window or PowerShell (Or enter this folder through the command line)
3. Enter the command: pip install matplotlib‑3.4.1‑cp39‑cp39‑win_amd64.whl
8. Uninstall the library
pip uninstall package_name
9.Upgrade library
pip install --upgrade package_name
10. Save the library list to the specified file
pip freeze > filename.txt
11. Check the libraries that need to be upgraded
pip list -o
12. Check for compatibility issues
Verify whether the installed library has compatible dependencies pip check package-name
13. Download the library to local
Download the library to a local specified file and save it in whl format pip download package_name -d "File path to save"
2. Package the program into an executable file
pyinstaller -F filename.py
3. Code writing
1. No format
1. Without i, it can only be written as i =1
2. Different formats
1. Class
1. Parameters
0. When defining a class, the first parameter must be self, and the same applies to all methods. When calling members of this class, self. members must be used.
1. For parameters in the class, first write the variable name, add a colon, and then write the type name, separated by commas
2. After the class definition is completed, you can add -> type name at the end to indicate the type of return value.
2. Call
1. To call itself for recursion in a class, you must use self.self function (you do not need to add self in the parameters)
2. When using a class, there is no need to create a new class, just use it directly root = TreeNode(max_num)
3.Method
1._Start with an underscore to define the protection method
2.__Start with two underscores to define private methods
subtopic
2. Numbers
1. Expression of plus and minus infinity
float("inf"), float("-inf")
3. Symbols
1. In Python / means normal division with remainder, // means integer division with no remainder.
2. Non! in Python is represented by not
3.The square in Python is **
4. Statement
1. A colon must be added after all statements related to keywords: except return
2. There is no need to add () to the conditions in loops, judgments and other similar statements, and there are no {} in statement blocks. Use indentation to strictly represent the format.
5. Comments
1. Single line comment #
2. Multi-line comments ''' or """
4. Different codes
1. List
1. When you need to use list subscripts explicitly, you must use G = [0]*(n 1) # to create a list with a length of n 1, otherwise the subscript will be out of bounds.
2. When creating a fixed-length two-dimensional list, if *n fails, try using a loop
dp = [[float('inf') for _ in range(n)] for _ in range(n)]
3. If the function return value is a list, but only one variable is received, add a comma
line, = ax.plot(x, np.sin(x))
5.Python programming
1. File problem
1. When importing other projects that require a file, use the absolute path of the file.
2. Command line execution file
Python filename.py
3. Third-party package mirror resources
When using pycharm to download third-party packages, add it in Manage Repositories http://mirrors.aliyun.com/pypi/simple/, delete the original URL
6.Pycharm shortcut keys
1. Comment (add/delete)
Ctrl/
Single line comments#
2.Code right shift
Tab
3. Code left shift
Shift Tab
4. Automatic indentation
Ctrl alt I
5. Run
Ctrl shift F10
6.PEP8 standard formatting
Ctrl alt L
It is also the QQ lock shortcut key. Cancel the setting in QQ.
6.1 Quick Fix
alt enter and press enter
7. Copy one line/multiple lines/selected part
Ctrl D
8. Delete one/multiple lines
Ctrl Y
9.Find
Ctrl F
9.1 Global search
Ctrl shift F
10.Replacement
Ctrl R
10.1 Global replacement
Ctrl shift R
11.Move the cursor to the next line
shift Enter
12.Multi-line cursor click
alt left mouse button click
13. Jump to the next breakpoint
alt F9
14.Cancellation
Ctrl Z
14.1 Anti-cancellation
Ctrl shift Z
15. Copy the parent class code
Ctrl o
16. Select word/code block
Ctrl W
17. Quickly view documents (code information)
Ctrl Q
18. Insert a line downwards at any position
shift enter
19. Insert a row upwards at any position
Ctrl alt enter
20. View project view
alt 1
21. View the structure view
alt 7
22. Get into code quickly
Ctrl left click
23. Quickly view history
alt left (return)/right key (forward)
24. Quickly view different methods
alt up/down
25. Switch views
Ctrl Tab
26. View resource files
shift twice
27. See where the method is called
Ctrl alt H Double click to determine position
28.View parent class
Ctrl U
29. View inheritance relationship
Ctrl H
7.pycharm page
0.Menu bar
1.View window
1.Navigation Bar navigation bar
2.Refactor Refactoring
3.Tools
4.VCS version control
1. Debug the code
1. Click in front of the code to insert a breakpoint, click on the crawler to debug, and click on the box next to the crawler to end debugging.
2. Click the ↘ icon to jump to the next breakpoint, and you can continuously observe the value of the variable.
2.settings settings
1.appearance & Behavior interface and behavior
1.appearance overall style
1.Theme theme
1.Darcula black theme
2.High contrast high contrast theme
3.Intellij bright theme
2.custom font custom font
2.system settingssystem settings
1.update
Automatically detecting updates can be turned off
2.keymap shortcut keys
1. According to the system view, you can search directly (comment note)
3.editor only edits the area
1.Font code font (adjustable size)
2.Color Scheme code area color scheme
3.Code Style code style
0.Python can make changes to every detail
1.Python
1.Indent number
2.Space space setting
3.Wrapping and Braces
1.Hard wrap at the maximum number of codes in one line
4.Blank lines blank lines
4.Inspections
1.PEP 8 is a code standard, not a grammatical error. Try to keep it as standard as possible.
2. You can set what content is checked, and you can also set the strictness of the check in Severity.
5.File and Code Templates File and Code Templates
1. Add information in Python.Script that will be displayed every time a new file is created, Find out what information you can add online
6.File Encodings file encoding
1.Default UTF-8
7.Live Templates dynamic templates (easy to use and master)
0. You can click the plus sign to add the template yourself, and be sure to set the usage location.
1.for loop
Write iter to select a loop and keep pressing Enter to write code
2. Use a for loop to write a list
Just write compl
4.Plugins
5.Project
1.Project Interpret project interpreter
1. You can manage and add third-party libraries (click the plus sign and search)
2. Different interpreters can be set for the current project
3.Project project management
1. Right-click the file and select Show in Explorer to directly open the file location.
2.New
1.File
It can be various other files, not necessarily Python files.
2.New Scratch File
Create temporary files, equivalent to scratch paper, used to test part of the code
3.Directory directory
Create a new lower-level folder
4.Python File
Compared with ordinary folders, there are more empty initialization Python files.
5. When creating a new HTML file, you can right-click it and open it in a browser to see the display effect.
4. Code results page
1.Terminal terminal
1. It is the same as the system CMD. You can install the package directly here and use the dos command.
2.Python Console console
You can directly write Python code, run it interactively, and edit it line by line.
3.TODO
It is equivalent to a memo. Write TODO ('') at the code point, so you can quickly find it and continue working. It can be used for mutual cooperation.
4. The avatar on the far right
The strictness of code checking can be adjusted. The power saving mode is equivalent to placing the arrow to the far left. All grammatical errors will not be checked.
5.Virtual environment
1.
2. The virtual environment is created because in actual development, different versions of python interpreters and different versions of the same library need to be used at the same time. Therefore, it is necessary to create a virtual environment to isolate the project environment from other environments (system environment, other virtual environments)
3. There are three ways to create a virtual environment in PyCharm, virtualen, conda and pipen
4. Virtualen can be imagined as creating an isolated copy of the current system environment. The interpreter used is the same one you installed (copy)
5. Conda selects a specific python version based on your needs, then downloads the relevant version from the Internet, and creates a new environment that is different from the system environment. The interpreter used is also different from the one you installed.
6. Pipen is similar to virtualen. It also creates a copy based on the existing system environment, but pipen uses Pipfile instead of virtualen’s requirements.txt for dependency management, which is more convenient.
6.Interrelationship
7.SVN
1. When downloading from the official website, select the English version 1.10, which is different from the Chinese version.
2. When installing TortoiseSVN.msi, be sure to check command line tools
3. A series of svn.exe files will appear in the installed bin directory.
4. Configure in pycharm and find the svn.exe file in the bin directory in setting/version control/subversion
5. Right-click the modified file in the folder to view the modifications
6. Right-click the project and a new subversion shortcut will appear. You can submit commits and undo all revert changes.
8. Easy-to-use plug-ins
5. Common code snippets
1.Input
1. Obtain user input of variable length
def getNum(): #Get user input of variable length nums = [] iNumStr = input("Please enter a number (press enter to exit): ") while iNumStr != "": nums.append(eval(iNumStr)) iNumStr = input("Please enter a number (press enter to exit): ") return nums
2.Text
1. English text denoising and normalization
def getText(): txt = open("hamlet.txt", "r").read() txt = txt.lower() #Convert all to lowercase letters for ch in '!"#$%&()* ,-./:;<=>?@[\\]^_‘{|}~': txt = txt.replace(ch, " ") #Replace special characters in text with spaces return txt
2. Count the frequency of words in English text
hamletTxt = getText() words = hamletTxt.split() #Separate text with spaces and convert to a list counts = {} #Create a new dictionary for word in words: #Count the frequency of each word, the default value is 0 counts[word] = counts.get(word,0) 1 items = list(counts.items()) #Convert dictionary to list items.sort(key=lambda x:x[1], reverse=True)# Sort the second element in reverse order for i in range(20): word, count = items[i] print ("{0:<10}{1:>5}".format(word, count))
3. Statistics of Chinese text word frequency
import jieba txt = open("threekingdoms.txt", "r", encoding='utf-8').read() words = jieba.lcut(txt) counts = {} for word in words: if len(word) == 1: continue else: counts[word] = counts.get(word,0) 1 items = list(counts.items()) #Convert to list to sort items.sort(key=lambda x:x[1], reverse=True) for i in range(15): word, count = items[i] print ("{0:<10}{1:>5}".format(word, count))
3.Array
1. Find the maximum value in the array
1. Elements are repeated
max_v, m_i = float(-inf), 0 #Combine a traversable data object (such as a list, tuple or string) into an index sequence, List both data subscripts and data for i, v in enumerate(nums): if v > max_v: max_v = v m_i = i
2. No repeated elements
max_v = max(nums) m_i = nums.index(max_v)
2. Dichotomy
class Solution: def searchInsert(self, nums: List[int], target: int) -> int: left, right = 0, len(nums) #Use the left closed and right open interval [left, right) while left < right: # Open to the right, so there cannot be =, the interval does not exist mid = left (right - left)//2 # Prevent overflow, //Indicates integer division if nums[mid] < target: # The midpoint is smaller than the target value. On the right side, equal positions can be obtained left = mid 1 # Left closed, so 1 else: right = mid # Open right, the real right endpoint is mid-1 return left # When this algorithm ends, it is guaranteed that left = right, and the return will be the same for everyone.
4.Matplotlib
1. Move the coordinates to (0,0)
ax = plt.gca() ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') ax.xaxis.set_ticks_position('bottom') ax.spines['bottom'].set_position(('data', 0)) ax.yaxis.set_ticks_position('left') ax.spines['left'].set_position(('data', 0))
math
1. Calculate the average
def mean(numbers): #Calculate the average s = 0.0 for num in numbers: s = s num return s/len(numbers)
2. Calculate the variance
def dev(numbers, mean): #Calculate variance sdev = 0.0 for num in numbers: sdev = sdev (num - mean)**2 return pow(sdev / (len(numbers)-1), 0.5)
3. Calculate the median
def median(numbers): #Calculate the median sorted(numbers) size = len(numbers) if size % 2 == 0: med = (numbers[size//2-1] numbers[size//2])/2 else: med = numbers[size//2] return med
6. Code runs locally
In fact, just define a main function, construct an input use case, then define a solution variable, and call the minCostClimbingStairs function.
0. Code writing skills
1.Format
1. Want to write several lines in one line
Add a semicolon at the end of each line;
2. A line is too long and I want to wrap it in a new line.
Just add a right slash\ at the end of this line
Algorithm related
classic thought
0.Frequently encountered problems
1. Prevent overflow
1. When calculating the product of many numbers, in order to prevent overflow, you can take the logarithm of the product and change it into the form of addition.
1.Data structure
1.Array
0.Basic
1. As long as you see that the array given in the interview question is an ordered array, you can think about whether you can use the dichotomy method
2. The elements of the array are continuous in the memory address. An element in the array cannot be deleted individually, but can only be overwritten.
1. The same element in the array cannot be traversed twice.
for (int i = 0; i < nums.length; i ) { for (int j = i 1; j < nums.length; j ) {
2. Circular array problem
When there is a constraint that the head and tail cannot be at the same time, decompose the circular array into several ordinary array problems and find the maximum value
3. Dichotomy interval problem
1. Left closed and right closed [left, right]
int middle = left ((right - left) / 2);// prevent overflow, equivalent to (left right)/2
while (left <= right) { // When left==right, the interval [left, right] is still valid
if (nums[middle] > target) { right = middle - 1; // target is in the left range, so [left, middle - 1]
} else if (nums[middle] < target) { left = middle 1; // target is in the right range, so [middle 1, right]
2. Close left and open right [left, right)
while (left < right) { // Because when left == right, [left, right) is an invalid space
if (nums[middle] > target) { right = middle; // target is in the left interval, in [left, middle)
} else if (nums[middle] < target) { left = middle 1; // target is in the right interval, in [middle 1, right)
2. Hash table
0. As long as it involves counting the number of occurrences of a certain number/value, use a hash table
1. The hash table contains the corresponding number of a certain number, and is not itself
for (int i = 0; i < nums.length; i ) { int complement = target - nums[i]; if (map.containsKey(complement) && map.get(complement) != i)
3. Linked list
1. Add the numbers in two linked lists
1. When reversing the order: the possible carry issue of the last addition must be considered separately.
2. In forward sequence: reverse the linked list/use the data structure of the stack to achieve reversal
2. Find the median of an ordered singly linked list (left closed and right open)
Assume that the left endpoint of the current linked list is left, the right endpoint is right, and the inclusion relationship is "left closed, right open". The given linked list is a one-way linked list. It is very easy to access subsequent elements, but it cannot directly access the predecessor elements. Therefore, after finding the median node mid of the linked list, if you set the relationship of "left closed, right open", you can directly use (left, mid) and (mid.next, right) to represent the list corresponding to the left and right subtrees No need for mid.pre, and the initial list can also be conveniently represented by (head, null)
4.Characters
1. Record whether each character appears
Hash set: Set<Character> occ = new HashSet<Character>(); occ.contains(a)
5. Numbers
1. Carry acquisition for adding two single-digit numbers
int sum = carry x y; int carry = sum/10;
2. Integer reversal
To "pop" and "push" numbers without the help of a auxiliary stack/array, we can use math, take out the last digit first, then divide by 10 to remove the last digit, invert the number and keep multiplying itself After 10, add the last digit taken out, and first determine whether it will overflow.
6.Tree
1. Find precursor nodes
Take one step to the left, then keep walking to the right until you can't go any further
predecessor = root.left; while (predecessor.right != null && predecessor.right != root) { predecessor = predecessor.right; }
7. Collection
1. Remove duplicate elements from the list
s = set(ls); lt = list(s)
8. Tuple
1. If you do not want the data to be changed by the program, convert it to a tuple type
lt = tuple(ls)
2. Classic algorithm
1.Double pointer
0. Commonly used in arrays and linked lists
1. When you need to enumerate two elements in an array, if you find that as the first element increases, the second element decreases, then you can use the double pointer method to move the second pointer from the end of the array Start traversing while ensuring that the second pointer is greater than the first pointer, reducing the time complexity of the enumeration from O(N^2) to O(N)
2. When the search result is a certain range, the double pointers are used to continuously change, similar to the sliding window mechanism.
2. Fast and slow pointer method
Initially, both the fast pointer fast and the slow pointer slow point to the left endpoint left of the linked list. While we move the fast pointer fast to the right twice, we move the slow pointer to the right once until the fast pointer reaches the boundary (that is, the fast pointer reaches the right endpoint or the next node of the fast pointer is the right endpoint). At this time, the element corresponding to the slow pointer is the median
3. Dynamic programming
1. The required number can be obtained through some operation through the previous required number, and the dynamic transfer equation can be found
2.Conditions
If a problem has many overlapping sub-problems, dynamic programming is most effective.
3. Five Steps
1. Determine the meaning of dp array (dp table) and subscripts 2. Determine the recursion formula 3.How to initialize the dp array 4. Determine the traversal order 5. Derivation of dp array with examples
Why determine the recursion formula first and then consider initialization? Because in some cases the recursive formula determines how to initialize the dp array
4.How to debug
1. Print out the dp array and see if it is deduced according to your own ideas.
2. Before writing code, be sure to simulate the specific situation of the state transfer on the dp array, and be sure that the final result is the desired result.
5.Scroll array
When the recursive equation is only related to a few adjacent numbers, a rolling array can be used to optimize the space complexity to O(1)
4. Recursion
0. Recursive Trilogy
Recursion termination condition, what this recursion does, and what it returns
3. Commonly used techniques
1. Clever use of array subscripts
1.Application
The subscript of an array is an implicitly useful array, especially when counting some numbers (treating the corresponding array value as the subscript temp[arr[i]] of the new array), or determining whether some integers appear. time passed
2.Examples
1. When we give you a string of letters and ask you to determine the number of times these letters appear, we can use these letters as subscripts. When traversing, if the letter a is traversed, then arr[a] can be increased by 1. That is, arr[a]. Through this clever use of subscripts, we don’t need to judge letter by letter.
2. You are given n unordered int integer arrays arr, and the value range of these integers is between 0-20. You are required to sort these n numbers from small to large in O(n) time complexity. Print it out in large order, and use the corresponding value as the array subscript. If this number has appeared before, add 1 to the corresponding array.
2. Use the remainder skillfully
1.Application
When traversing the array, out-of-bounds judgment will be performed. If the subscript is almost out of bounds, we will set it to 0 and traverse again. Especially in some ring-shaped arrays, such as queues implemented with arrays pos = (pos 1) % N
3. Use double pointers skillfully
1.Application
For double pointers, it is particularly useful when doing questions about singly linked lists.
2.Examples
1. Determine whether a singly linked list has a cycle
Set up a slow pointer and a fast pointer to traverse the linked list. The slow pointer moves one node at a time, while the fast pointer moves two nodes at a time. If the linked list does not have a cycle, the fast pointer will traverse the list first. If there is a cycle, the fast pointer will meet the slow pointer during the second traversal.
2. How to find the middle node of the linked list in one traversal
The same is to set a fast pointer and a slow pointer. The slow one moves one node at a time, while the fast one moves two. When traversing the linked list, when the fast pointer traverse is completed, the slow pointer just reaches the midpoint
3. The k-th node from the last in a singly linked list
Set two pointers, one of which moves k nodes first. After that both pointers move at the same speed. When the first moved pointer completes the traversal, the second pointer is exactly at the kth node from the bottom.
subtopic
4. Use shift operations skillfully
1.Application
1. Sometimes when we perform division or multiplication operations, such as n / 2, n / 4, n / 8, we can use the shift method to perform the operation. Through the shift operation, The execution speed will be faster
2. There are also some operations such as & (and) and | (or), which can also speed up the operation.
2.Examples
1. To determine whether a number is odd, it will be much faster to use the AND operation.
5. Set the sentinel position
1.Application
1. In related issues of linked lists, we often set a head pointer, and this head pointer does not store any valid data. Just for the convenience of operation, we can call this head pointer the sentinel bit.
2. When operating an array, you can also set a sentinel, using arr[0] as the sentinel.
2.Examples
1. When we want to delete the first node, if a sentinel bit is not set, the operation will be different from the operation of deleting the second node. But we have set up a sentinel, so deleting the first node and deleting the second node are the same in operation, without making additional judgments. Of course, the same is true when inserting nodes
2. When you want to judge whether two adjacent elements are equal, setting a sentinel will not worry about cross-border problems. You can directly arr[i] == arr[i-1]. Don’t be afraid of crossing the boundary when i = 0
6. Some optimizations related to recursion
1. Consider state preservation for problems that can be recursive.
1. When we use recursion to solve a problem, it is easy to repeatedly calculate the same sub-problem. At this time, we must consider state preservation to prevent repeated calculations.
2.Examples
0. A frog can jump up 1 step or 2 steps at a time. Find out how many ways the frog can jump up an n-level staircase.
1. This problem can be easily solved using recursion. Assume that f(n) represents the total number of steps for n steps, then f(n) = f(n-1) f(n - 2)
2. The end condition of recursion is when 0 <= n <= 2, f(n) = n, it is easy to write recursive code
3. However, for problems that can be solved using recursion, we must consider whether there are many repeated calculations. Obviously, for the recursion of f(n) = f(n-1) f(n-2), there are many repeated calculations.
4. This time we have to consider state preservation. For example, you can use hashMap to save. Of course, you can also use an array. At this time, you can use array subscripts as we said above. When arr[n] = 0, it means that n has not been calculated. When arr[n] != 0, it means that f(n) has been calculated. At this time, the calculated value can be returned directly.
5. In this way, the efficiency of the algorithm can be greatly improved. Some people also call this kind of state preservation the memo method.
2. Think bottom-up
1. For recursive problems, we usually recurse from top to bottom until the recursion reaches the bottom, and then return the value layer by layer.
2. However, sometimes when n is relatively large, such as when n = 10000, then it is necessary to recurse down 10000 levels until n <= 2 before slowly returning the result. If n is too large, the stack space may be not enough
3. For this situation, we can actually consider a bottom-up approach.
4. This bottom-up approach is also called recursion
3. Advantages over other languages
1.Array
1. The parameters are partial arrays
In Python, parameters can directly return part of the array nums[:i]. There is no need to redesign a method to intercept the array based on the subscript like in Java.
2. Get elements and subscripts at the same time
for i, v in enumerate(nums):
Commonly used data structures/algorithms
1. Linked list
2.Stack
1. Monotone stack
1.Definition
A stack in which the elements in the stack are monotonically increasing or decreasing. A monotonic stack can only be operated on the top of the stack.
2. Nature
1. The elements in the monotonic stack are monotonic.
2. Before elements are added to the stack, all elements that destroy the monotonicity of the stack will be deleted from the top of the stack.
3. Use the monotonic stack to find the element and traverse to the left to the first element that is smaller than it (incremental stack)/find the element and traverse to the left to the first element that is larger than it.
3. Queue
4.Tree
1.BST: Binary Search Tree Binary sorting tree
1.Definition
1. The keys of all nodes on the left subtree are less than the root node
2. The keywords of all nodes on the right subtree are greater than the root node
3. The left and right subtrees are each a binary sorting tree.
Inorder traversal can obtain increasing ordered sequence
2.AVL: Balanced Binary Tree
1.Definition
1. Balanced binary tree: The absolute value of the height difference between the left and right subtrees of any node does not exceed 1
2. Balance factor: The height difference between the left and right subtrees of the node -1,0,1
3.mct: Minimum spanning tree
1.Definition
Any tree that consists only of the edges of G and contains all the vertices of G is called a spanning tree of G.
5. Figure
1. Terminology
1. Cluster (complete subgraph)
Set of points: There is an edge connecting any two points.
1.1 Point independent set
Set of points: There is no edge between any two points
2. Hamilton graph Hamilton
An undirected graph goes from a specified starting point to a specified end point, passing through all other nodes only once. A closed Hamiltonian path is called a Hamiltonian cycle, and a path containing all vertices in the graph is called a Hamiltonian path.
6. Algorithm
1.BFS: Breadth First Search
1.Definition
A hierarchical traversal algorithm similar to a binary tree, giving priority to the earliest discovered node.
2.DFS: Depth First Search
1.Definition
Similar to pre-order traversal of a tree, the last discovered node is given priority.
common sense
1.How many operations per second?
2. Time complexity of recursive algorithm
Number of recursions * Number of operations in each recursion
3. Space complexity of recursive algorithm
Depth of recursion * space complexity of each recursion
labuladuo knowledge points
0.Must-read series
1. Framework thinking for learning algorithms and solving questions
1. Storage method of data structure
1. There are only two types: array (sequential storage) and linked list (linked storage)
2. There are also various data structures such as hash tables, stacks, queues, heaps, trees, graphs, etc., which all belong to the "superstructure", while arrays and linked lists are the "structural basis". After all, those diverse data structures Their sources are special operations on linked lists or arrays, and the APIs are just different.
3. Introduction to various structures
1. The two data structures "queue" and "stack" can be implemented using either linked lists or arrays. If you implement it with an array, you have to deal with the problem of expansion and contraction; if you implement it with a linked list, you don't have this problem, but you need more memory space to store node pointers.
2. Two representation methods of "graph", adjacency list is a linked list, and adjacency matrix is a two-dimensional array. The adjacency matrix determines connectivity quickly and can perform matrix operations to solve some problems, but it consumes space if the graph is sparse. Adjacency lists save space, but many operations are definitely not as efficient as adjacency matrices.
3. "Hash table" maps keys to a large array through a hash function. And as a method to solve hash conflicts, the zipper method requires linked list characteristics, which is simple to operate, but requires additional space to store pointers; the linear probing method requires array characteristics to facilitate continuous addressing and does not require storage space for pointers, but the operation is slightly complicated some
4. "Tree", implemented with an array is a "heap", because the "heap" is a complete binary tree. Using an array to store does not require node pointers, and the operation is relatively simple; using a linked list to implement it is a very common "tree", because It is not necessarily a complete binary tree, so it is not suitable for array storage. For this reason, various ingenious designs have been derived based on this linked list "tree" structure, such as binary search trees, AVL trees, red-black trees, interval trees, B-trees, etc., to deal with different problems.
4. Advantages and Disadvantages
1. Since arrays are compact and continuous storage, they can be accessed randomly, the corresponding elements can be found quickly through indexes, and storage space is relatively saved. But because of continuous storage, the memory space must be allocated at one time. Therefore, if the array wants to expand, it needs to reallocate a larger space and then copy all the data there. The time complexity is O(N); and if you want to When inserting and deleting in the middle of the array, all subsequent data must be moved each time to maintain continuity. The time complexity is O(N).
2. Because the elements of a linked list are not continuous, but rely on pointers to point to the location of the next element, there is no problem of array expansion; if you know the predecessor and successor of a certain element, you can delete the element or insert a new element by operating the pointer. Time complexity O(1). However, because the storage space is not continuous, you cannot calculate the address of the corresponding element based on an index, so random access is not possible; and because each element must store a pointer to the position of the previous and previous elements, it will consume relatively more storage space.
2. Basic operations of data structures
1. The basic operation is nothing more than traversal access. To be more specific, it is: add, delete, check and modify
2. From the highest level, there are only two forms of traversal and access to various data structures: linear and non-linear.
3. Linear is represented by for/while iteration, and nonlinear is represented by recursion.
4. Several typical traversals
1.Array
void traverse(int[] arr) { for (int i = 0; i < arr.length; i ) { // Iterate over arr[i] } }
2. Linked list
/* Basic singly linked list node */ class ListNode { int val; ListNode next; } void traverse(ListNode head) { for (ListNode p = head; p != null; p = p.next) { //Iteratively access p.val } } void traverse(ListNode head) { // Recursively access head.val traverse(head.next) }
3. Binary tree
/* Basic binary tree node */ classTreeNode { int val; TreeNode left, right; } void traverse(TreeNode root) { traverse(root.left) traverse(root.right) }
4.N-ary tree
/* Basic N-ary tree node */ classTreeNode { int val; TreeNode[] children; } void traverse(TreeNode root) { for (TreeNode child : root.children) traverse(child); }
5. The so-called framework is a routine. Regardless of adding, deleting, checking or modifying, these codes are a structure that can never be separated. You can use this structure as an outline and just add codes to the framework according to specific problems.
3. Algorithm question writing guide
1. Brush the binary tree first, brush the binary tree first, brush the binary tree first!
2. Binary trees are the easiest to cultivate framework thinking, and most algorithm techniques are essentially tree traversal problems.
3. Don’t underestimate these few lines of broken code. Almost all binary tree problems can be solved using this framework.
void traverse(TreeNode root) { // Preorder traversal traverse(root.left) // In-order traversal traverse(root.right) // Postorder traversal }
4. If you don’t know how to start or are afraid of the questions, you might as well start with the binary tree. The first 10 questions may be a bit uncomfortable; do another 20 questions based on the framework, and maybe you will have some understanding of your own; finish the entire topic before doing it. If you look back on the topic of dividing and conquering rules, you will find that any problem involving recursion is a tree problem.
5. What should I do if I can’t understand so many codes? By directly extracting the framework, you can see the core idea: In fact, many dynamic programming problems involve traversing a tree. If you are familiar with tree traversal operations, you will at least know how to convert ideas into code, and you will also know how to extract others. The core idea of the solution
2. Dynamic programming problem-solving routine framework
1. Basic concepts
1.Form
The general form of dynamic programming problem is to find the optimal value. Dynamic programming is actually an optimization method in operations research, but it is more commonly used in computer problems. For example, it asks you to find the longest increasing subsequence and the minimum edit distance.
2. Core issues
The core problem is exhaustion. Because we require the best value, we must exhaustively enumerate all possible answers and then find the best value among them.
3.Three elements
1. Overlapping subproblems
0. There are "overlapping sub-problems" in this type of problem. If the problem is brute force, the efficiency will be extremely inefficient. Therefore, a "memo" or "DP table" is needed to optimize the exhaustive process and avoid unnecessary calculations.
2. Optimal substructure
0. The dynamic programming problem must have an "optimal substructure", so that the optimal value of the original problem can be obtained through the optimal value of the sub-problem.
3. State transition equation
0. Problems can be ever-changing, and it is not easy to exhaustively enumerate all feasible solutions. Only by listing the correct "state transition equation" can we exhaustively enumerate correctly.
1. Thinking framework
Clear base case -> clear "state" -> clear "selection" -> define the meaning of dp array/function
#Initialize base case dp[0][0][...] = base # Perform state transfer for state 1 in all values of state 1: for state 2 in all values of state 2: for... dp[state 1][state 2][...] = find the maximum value (select 1, select 2...)
2. Fibonacci Sequence
1. Violent recursion
1.Code
int fib(int N) { if (N == 1 || N == 2) return 1; return fib(N - 1) fib(N - 2); }
2. Recursive tree
1. Whenever you encounter a problem that requires recursion, it is best to draw a recursion tree. This will be of great help to you in analyzing the complexity of the algorithm and finding the reasons for the inefficiency of the algorithm.
2.Pictures
3. Time complexity of recursive algorithm
0. Multiply the number of sub-problems by the time required to solve a sub-problem
1. First calculate the number of sub-problems, that is, the total number of nodes in the recursion tree. Obviously the total number of binary tree nodes is exponential, so the number of sub-problems is O(2^n)
2. Then calculate the time to solve a sub-problem. In this algorithm, there is no loop, only f(n - 1) f(n - 2) an addition operation, the time is O(1)
3. The time complexity of this algorithm is the multiplication of the two, that is, O(2^n), exponential level, explosion
4. Observing the recursion tree, it is obvious that the reason for the inefficiency of the algorithm is found: there are a large number of repeated calculations.
5. This is the first property of dynamic programming problems: overlapping subproblems. Next, we will try to solve this problem
2. Recursive solution with memo
1. Since the time-consuming reason is repeated calculations, we can create a "memo". Don't rush back after calculating the answer to a certain sub-question. Write it down in the "memo" before returning; every time you encounter a sub-question Check the problem in "Memo" first. If you find that the problem has been solved before, just take out the answer and use it instead of spending time calculating.
2. Generally, an array is used as this "memo". Of course, you can also use a hash table (dictionary)
3.Code
int fib(int N) { if (N < 1) return 0; //The memo is all initialized to 0 vector<int> memo(N 1, 0); // Perform recursion with memo return helper(memo, N); } int helper(vector<int>& memo, int n) { // base case if (n == 1 || n == 2) return 1; //Already calculated if (memo[n] != 0) return memo[n]; memo[n] = helper(memo, n - 1) helper(memo, n - 2); return memo[n]; }
4. Recursive tree
In fact, the recursive algorithm with "memo" transforms a recursive tree with huge redundancy into a recursive graph without redundancy through "pruning", which greatly reduces the number of sub-problems (i.e. recursion number of nodes in the graph)
5. Complexity
There is no redundant calculation in this algorithm, the number of sub-problems is O(n), and the time complexity of this algorithm is O(n). Compared with violent algorithms, it is a dimension reduction attack
6.Comparison with dynamic programming
The efficiency of the recursive solution with memo is the same as that of the iterative dynamic programming solution. However, this method is called "top-down" and dynamic programming is called "bottom-up"
7. Top-down
The recursion tree (or picture) drawn extends from top to bottom, starting from a larger original problem such as f(20), and gradually decomposes the scale downward until f(1) and f(2) These two base cases then return the answers layer by layer.
8. Bottom-up
Just start from the bottom, the simplest, and the smallest problem size f(1) and f(2) and push upward until we reach the answer we want f(20). This is the idea of dynamic programming, and this is why dynamic programming Planning generally breaks away from recursion, and instead completes calculations by loop iteration
3.Iterative solution of dp array
1. Thoughts
With the inspiration from the "memo" in the previous step, we can separate this "memo" into a table, let's call it DP table. Wouldn't it be nice to complete "bottom-up" calculations on this table?
2.Code
int fib(int N) { vector<int> dp(N 1, 0); // base case dp[1] = dp[2] = 1; for (int i = 3; i <= N; i ) dp[i] = dp[i - 1] dp[i - 2]; return dp[N]; }
3. State transition equation
1. It is the core of problem solving. And it is easy to find that in fact, the state transition equation directly represents the brute force solution
2. Don’t look down on violent solutions. The most difficult thing about dynamic programming problems is to write this violent solution, that is, the state transition equation. As long as you write a brute force solution, the optimization method is nothing more than using a memo or DP table, there is no mystery at all.
4. State compression
1. The current state is only related to the two previous states. In fact, you don’t need such a long DP table to store all states. You just need to find a way to store the two previous states. Therefore, it can be further optimized to reduce the space complexity to O(1)
2.Code
int fib(int n) { if (n == 2 || n == 1) return 1; int prev = 1, curr = 1; for (int i = 3; i <= n; i ) { int sum = prev curr; prev = curr; curr = sum; } return curr; }
3. This technique is the so-called "state compression". If we find that each state transfer only requires a part of the DP table, then we can try to use state compression to reduce the size of the DP table and only record the necessary data. Generally speaking, it is Compress a two-dimensional DP table into one dimension, that is, compress the space complexity from O(n^2) to O(n)
3. The problem of collecting change
0.Question
You are given k coins with face values of c1, c2...ck. The quantity of each coin is unlimited. Then you are given a total amount of money. I ask you how many coins you need at least to make up this amount. If it is impossible to make up the amount, , the algorithm returns -1
1. Violent recursion
1. First of all, this problem is a dynamic programming problem because it has an "optimal substructure". To comply with the "optimal substructure", the subproblems must be independent of each other.
2. Back to the problem of collecting change, why is it said to be in line with the optimal substructure? For example, if you want to find the minimum number of coins when amount = 11 (original question), if you know the minimum number of coins when amount = 10 (sub-question), you only need to add one to the answer to the sub-question (choose another face value 1 coin) is the answer to the original question. Because the number of coins is unlimited, there is no mutual control between the sub-problems and they are independent of each other.
3. Four major steps
1. Determine the base case. This is very simple. Obviously, when the target amount is 0, the algorithm returns 0.
2. Determine the "state", that is, the variables that will change in the original problem and sub-problems. Since the number of coins is infinite and the denomination of the coin is also given by the question, only the target amount will continue to approach the base case, so the only "state" is the target amount amount
3. Determine the "choice", which is the behavior that causes the "state" to change. Why does the target amount change? Because you are choosing coins. Every time you choose a coin, it is equivalent to reducing the target amount. So the face value of all coins is your "choice"
4. Clarify the definition of dp function/array. What we are talking about here is a top-down solution, so there will be a recursive dp function. Generally speaking, the parameter of the function is the amount that will change during the state transition, which is the "state" mentioned above; the return value of the function It is the quantity that the question requires us to calculate. As far as this question is concerned, there is only one status, which is "target amount". The question requires us to calculate the minimum number of coins required to make up the target amount. So we can define the dp function like this: The definition of dp(n): input a target amount n, and return the minimum number of coins to make up the target amount n
4. Pseudo code
def coinChange(coins: List[int], amount: int): # Definition: To make up the amount n, at least dp(n) coins are needed def dp(n): # Make a choice and choose the result that requires the least coins. for coin in coins: res = min(res, 1 dp(n - coin)) return res #The final result required by the question is dp(amount) return dp(amount)
5.Code
def coinChange(coins: List[int], amount: int): def dp(n): # base case if n == 0: return 0 if n < 0: return -1 # Find the minimum value, so initialize it to positive infinity res = float('INF') for coin in coins: subproblem = dp(n - coin) # Sub-problem has no solution, skip it if subproblem == -1: continue res = min(res, 1 subproblem) return res if res != float('INF') else -1 return dp(amount)
6. State transition equation
7. Recursive tree
8. Complexity
The total number of sub-problems is the number of recursive tree nodes. This is difficult to see. It is O(n^k). In short, it is exponential. Each subproblem contains a for loop with a complexity of O(k). So the total time complexity is O(k * n^k), exponential level
2. Recursion with memo
1.Code
def coinChange(coins: List[int], amount: int): # memo memo = dict() def dp(n): # Check memo to avoid double counting if n in memo: return memo[n] # base case if n == 0: return 0 if n < 0: return -1 res = float('INF') for coin in coins: subproblem = dp(n - coin) if subproblem == -1: continue res = min(res, 1 subproblem) # Record in memo memo[n] = res if res != float('INF') else -1 return memo[n] return dp(amount)
2. Complexity
Obviously, the "memo" greatly reduces the number of sub-problems and completely eliminates the redundancy of sub-problems, so the total number of sub-problems will not exceed the amount of money n, that is, the number of sub-problems is O(n). The time to deal with a sub-problem remains unchanged and is still O(k), so the total time complexity is O(kn)
3.Iterative solution of dp array
1.Definition of dp array
The dp function is reflected in the function parameters, while the dp array is reflected in the array index: The definition of dp array: when the target amount is i, at least dp[i] coins are needed to collect it
2.Code
int coinChange(vector<int>& coins, int amount) { //The size of the array is amount 1, and the initial value is also amount 1 vector<int> dp(amount 1, amount 1); // base case dp[0] = 0; // The outer for loop traverses all values of all states for (int i = 0; i < dp.size(); i ) { //The inner for loop is finding the minimum value of all choices for (int coin : coins) { // The sub-problem has no solution, skip it if (i - coin < 0) continue; dp[i] = min(dp[i], 1 dp[i - coin]); } } return (dp[amount] == amount 1) ? -1 : dp[amount]; }
3. Process
4.Details
Why is the dp array initialized to amount 1? Because the number of coins that make up the amount can only be equal to the amount at most (all coins with a face value of 1 yuan are used), so initializing it to amount 1 is equivalent to initializing it to positive infinity, which facilitates the subsequent minimum value
4. Summary
1. There are actually no magic tricks for computers to solve problems. Its only solution is to exhaustively exhaust all possibilities. Algorithm design is nothing more than thinking about "how to exhaustively" first, and then pursuing "how to exhaustively exhaustively"
2. Listing the dynamic transfer equation is to solve the problem of "how to exhaustively". The reason why it is difficult is that firstly, many exhaustive calculations need to be implemented recursively, and secondly, because the solution space of some problems is complex and it is not easy to complete the exhaustive calculation.
3. Memos and DP tables are all about pursuing “how to exhaustively exhaustively”. The idea of exchanging space for time is the only way to reduce time complexity. In addition, let me ask, what other tricks can you do?
Likou algorithm
0. Reversal thinking
6.Zigzag transformation
1. Sort by row
0.Title
Arrange a given string in a Z-shape from top to bottom and from left to right according to the given number of lines. After that, your output needs to be read line by line from left to right to generate a new string.
1. Ideas
Iterate over ss from left to right, adding each character to the appropriate line. The appropriate row can be tracked using the current row and current direction variables. The current direction will only change when we move up to the top row or down to the bottom row.
2.Code
class Solution { public String convert(String s, int numRows) { if (numRows == 1) return s; List<StringBuilder> rows = new ArrayList<>(); //Storage the characters of each row after transformation for (int i = 0; i < Math.min(numRows, s.length()); i ) rows.add(new StringBuilder()); int curRow = 0; boolean goingDown = false; for (char c : s.toCharArray()) { //Traverse s directly, convert it into a character array, and assign it to c one by one rows.get(curRow).append(c); if (curRow == 0 || curRow == numRows - 1) goingDown = !goingDown; curRow = goingDown ? 1 : -1; } StringBuilder ret = new StringBuilder(); for (StringBuilder row : rows) ret.append(row); return ret.toString(); } }
3. Complexity
Time O(n), where n=len(s), space O(n)
1. Pure Algorithm
7. Integer reversal
1. Question
Given a 32-bit signed integer, you need to reverse each bit of the integer. Assume that our environment can only store 32-bit signed integers. If the integer overflows after inversion, 0 will be returned.
2. Ideas
To "pop" and "push" numbers without the help of a auxiliary stack/array, we can use math, take out the last digit first, then divide by 10 to get rid of the last digit, invert the number and keep multiplying itself After 10, add the last digit taken out, and first determine whether it will overflow.
3.Code
class Solution { public int reverse(int x) { int rev = 0; while (x != 0) { int pop = x % 10; //Take out the last digit of x x /= 10; //Remove the last digit of x //When int occupies 32 bits, the value range is -2147483648~2147483647, so pop>7 or pop<-8 if (rev > Integer.MAX_VALUE/10 || (rev == Integer.MAX_VALUE / 10 && pop > Integer.MAX_VALUE % 10)) return 0; if (rev < Integer.MIN_VALUE/10 || (rev == Integer.MIN_VALUE / 10 && pop < Integer.MIN_VALUE % 10)) return 0; rev = rev * 10 pop; //After the whole moves forward one digit, add the last digit } return rev; } }
4. Complexity
Time: O(log(x)), space O(1)
2.Array
0.frequency
1,4,11,42,53,15,121,238,561,85,169,66,88,283,16,56,122,48,31,289,41,128,152,54,26,442,39
35.Search for insertion position
1. Dichotomy
0.Title
Given a sorted array and a target value, find the target value in the array and return its index. If the target value does not exist in the array, returns the position where it will be inserted sequentially. You can assume that there are no duplicate elements in the array
1. Thoughts
1. The return value is the position of the first value within [first, last) that is not less than value. Note that the right boundary of the initial interval cannot be obtained, that is, the left is closed and the right is open, which also determines the changes of left and right ( left=mid 1,right=mid)
2. Mid=first (last-first)/2 is written like this to prevent large numbers from overflowing
3. Finally, you can also return last. The two overlap. The above code is also applicable even if the interval is empty, the answer does not exist, there are repeated elements, and the upper/lower bounds of the search are open/closed, and the position adjustment of plus or minus 1 only appears. Once
4. What happens if nums[mid] < value is changed to nums[mid] <= value? This is not difficult to think about. The result will converge to the first position that is greater than value. Because when the if condition is satisfied when they are equal, the left end of the interval will still change and the equal position will not be obtained.
5. The above array is increasing. If it is decreasing, how should we change it? In fact, we only need to reverse the condition and change it to nums[mid]>=value. The result will converge to the first one no larger than The position of value
6. Returning to this question, find the target value in the array and return its index. If the target value does not exist in the array, return the position where it will be inserted in order. Then, we use the template to find a value that is not less than the target and is the first in the array. Then, to meet the requirements of the question, we only need to return the index of the calculated value, that is, first or last
2.Code
class Solution: def searchInsert(self, nums: List[int], target: int) -> int: left, right = 0, len(nums) #Use the left closed and right open interval [left, right) while left < right: # Open to the right, so there cannot be =, the interval does not exist mid = left (right - left)//2 # Prevent overflow, //Indicates integer division if nums[mid] < target: # The midpoint is smaller than the target value. On the right side, equal positions can be obtained left = mid 1 # Left closed, so 1 else: right = mid # Open right, the real right endpoint is mid-1 return left # When this algorithm ends, it is guaranteed that left = right, and the return will be the same for everyone.
3. Complexity
Time complexity: O(logn), where n is the length of the array. The time complexity required for binary search is O(logn)
Space complexity: O(1). We only need constant space to store several variables
1.The sum of two numbers
1. Violence law
0.Title
Given an integer array nums and a target value target, please find the two integers in the array whose sum is the target value and return their array subscripts. Assume that each input corresponds to only one answer. However, the same element in the array cannot be used twice
class Solution { public int[] twoSum(int[] nums, int target) { for (int i = 0; i < nums.length; i ) { for (int j = i 1; j < nums.length; j ) { if (nums[j] == target - nums[i]) { return new int[] { i, j }; } } } throw new IllegalArgumentException("No two sum solution"); } }
Time O(n^2), space O(1)
2. Double pass hash table
class Solution { public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i ) { map.put(nums[i], i); } for (int i = 0; i < nums.length; i ) { int complement = target - nums[i]; if (map.containsKey(complement) && map.get(complement) != i) { return new int[] { i, map.get(complement) }; } } throw new IllegalArgumentException("No two sum solution"); } }
Two iterations were used. In the first iteration, we add the value of each element and its index to the table. Then, in the second iteration, we will check whether the target element (target-nums[i]) corresponding to each element exists in the table. Note that the target element cannot be nums[i] itself! This method reduces the time complexity to O(n) and increases the space complexity to O(n).
Time O(n), space O(n)
3. Pass the hash table
class Solution { public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i ) { int complement = target - nums[i]; if (map.containsKey(complement)) { return new int[] { map.get(complement), i }; } map.put(nums[i], i); } throw new IllegalArgumentException("No two sum solution"); } }
There is a little optimization for method two, which is to merge the two iterations into one and create a hash table. For each x, we first query whether target - x exists in the hash table, and then insert x into the hash In the table, you can ensure that x will not match yourself.
4. Summary
Some people have raised objections to the algorithm using the hash table. There is a loop in the containsKey of HashMap, which is still O(n^2). Map also increases the space complexity and overhead. Overall, the brute force method is the most effective. , but this point of view also has some problems: the loop in the containsKey will only enter if there is a conflict. At the same time, if the conflicts are frequent, the getTreeNode method will be used to obtain the value. getTreeNode obtains the value from a red-black tree, and the time complexity is At most O(logN).
167. Sum of two numbers II ordered array
1. Dichotomy
0.Title
Given a sorted array sorted in ascending order, find two numbers such that their sum equals the target number. The function should return the two subscript values index1 and index2, where index1 must be less than index2 The returned index values (index1 and index2) are not zero-based. You can assume that each input corresponds to a unique answer, and you can't reuse the same elements
1.Code
class Solution { public int[] twoSum(int[] numbers, int target) { for (int i = 0; i < numbers.length; i) { int low = i 1, high = numbers.length - 1; while (low <= high) { int mid = (high - low) / 2 low; if (numbers[mid] == target - numbers[i]) { return new int[]{i 1, mid 1}; } else if (numbers[mid] > target - numbers[i]) { high = mid - 1; } else { low = mid 1; } } } return new int[]{-1, -1}; } }
2. Complexity
Time: O(nlogn), space O(1)
1.Double pointer
1.Code
class Solution { public int[] twoSum(int[] numbers, int target) { int low = 0, high = numbers.length - 1; while (low < high) { int sum = numbers[low] numbers[high]; if (sum == target) { return new int[]{low 1, high 1}; } else if (sum < target) { low; } else { --high; } } return new int[]{-1, -1}; } }
2. Complexity
Time: O(n), Space: O(1)
170.Sum of Two Numbers III Data Structure Design VIP
653. The sum of two numbers IV is input into BST
1.HashSet recursion
0.Title
Given a binary search tree and a target result, return true if there are two elements in the BST and their sum equals the given target result
1. Thoughts
Traverse its two subtrees (left subtree and right subtree) at each node of the tree to find another matching number. During the traversal process, put the value of each node into a set
2.Code
public class Solution { public boolean findTarget(TreeNode root, int k) { Set < Integer > set = new HashSet(); return find(root, k, set); } public boolean find(TreeNode root, int k, Set < Integer > set) { if (root == null) return false; if (set.contains(k - root.val)) return true; set.add(root.val); return find(root.left, k, set) || find(root.right, k, set); } }
Because the set collection needs to be updated every time it is traversed, it should be put into the new function as a parameter, so a new function is added.
3. Complexity
Time: O(n), space O(n)
2.HashSet BFS
1. Thoughts
Traverse a binary tree using breadth-first search
2.Code
public class Solution { public boolean findTarget(TreeNode root, int k) { Set < Integer > set = new HashSet(); Queue <TreeNode> queue = new LinkedList(); queue.add(root); while (!queue.isEmpty()) { if (queue.peek() != null) { TreeNode node = queue.remove(); if (set.contains(k - node.val)) return true; set.add(node.val); queue.add(node.right); queue.add(node.left); } else queue.remove(); } return false; } }
3. Complexity
Time: O(n), space O(n)
3. Properties of BST double pointers
1. Thoughts
The results of inorder traversal of BST are arranged in ascending order. Traverse the given BST in order and store the traversal results into the list. After the traversal is completed, use two pointers l and r as the head index and tail index of the list.
2.Code
public class Solution { public boolean findTarget(TreeNode root, int k) { List < Integer > list = new ArrayList(); inorder(root, list); int l = 0, r = list.size() - 1; while (l < r) { int sum = list.get(l) list.get(r); if (sum == k) return true; if (sum < k) l; else r--; } return false; } public void inorder(TreeNode root, List < Integer > list) { if (root == null) return; inorder(root.left, list); list.add(root.val); inorder(root.right, list); } }
3. Complexity
Time: O(n), space O(n)
1214. Find the sum vip of two binary search trees
560.The number of consecutive subarrays whose sum is k
1. Enumeration
0.Title
Given an array of integers and an integer k, you need to find the number of consecutive subarrays in the array that sum to k
1. Ideas
We can enumerate all the subscripts j in [0..i] to determine whether they meet the conditions. Some readers may think that assuming we have determined the beginning and end of the subarray, we still need O(n) time complexity to traverse the subarray. To sum the array, the complexity will reach O(n^3) and all test cases will not be passed. But if we know the sum of [j,i] subarrays, we can deduce the sum of [j-1,i] in O(1), so this part of the traversal and summation is not needed. We subscript j in the enumeration can already find the sum of the subarrays of [j,i] in O(1)
2.Code
public class Solution { public int subarraySum(int[] nums, int k) { int count = 0; for (int start = 0; start < nums.length; start) { int sum = 0; //Start from this number and find the sum of the sub-sequences in sequence. for (int end = start; end >= 0; --end) { sum = nums[end]; if (sum == k) { count ; } } } return count; } }
3. Complexity
Time: O(n^2), Space: O(1)
2. Prefix and hash table
1. Ideas
1. The bottleneck of method one is that for each i, we need to enumerate all j to determine whether it meets the conditions.
2. Define pre[i] as the sum of all numbers in [0..i], then pre[i] can be derived recursively from pre[i−1], that is: pre[i]=pre[i−1 ] nums[i], then the condition "the sum of the subarrays [j..i] is k" can be converted into pre[i]−pre[j−1]==k, and the following condition can be obtained by moving the terms Standard j needs to satisfy pre[j−1]==pre[i]−k
3. When considering the number of consecutive subarrays ending in i and summing to k, just count how many pre[j] prefixes sum to pre[i]−k. We build a hash table mp, with sum as the key, the number of occurrences as the corresponding value, record the number of occurrences of pre[i], update mp from left to right and calculate the answer, then the answer ending with i is mp[pre[i] −k] can be obtained in O(1) time. The final answer is the sum of the number of subarrays ending in all subscripts with a sum of k
4. It should be noted that when updating the edge calculation from left to right, it has been ensured that the subscript range of pre[j] recorded in mp[pre[i]−k] is 0≤j≤i. At the same time, since the calculation of pre[i] is only related to the answer to the previous item, we do not need to create a pre array and directly use the pre variable to record the answer to pre[i-1].
2.Code
public class Solution { public int subarraySum(int[] nums, int k) { int count = 0, pre = 0; //pre is the prefix sum HashMap < Integer, Integer > mp = new HashMap < > (); mp.put(0, 1); //Initialization, the sum is 0, and the number of occurrences is 1 for (int i = 0; i < nums.length; i ) { pre = nums[i];//The calculation of pre[i] is only related to the answer to the previous item, there is no need to create a pre array if (mp.containsKey(pre - k)) { //pre[i]−pre[j−1]==k count = mp.get(pre - k); } mp.put(pre, mp.getOrDefault(pre, 0) 1); //key is pre, value is the number of occurrences } return count; } }
3. Complexity
Time complexity: O(n), where n is the length of the array. The time complexity of traversing the array is O(n), and the complexity of using the hash table to query and delete is O(1), so the total time complexity is O(n)
Space complexity: O(n), where n is the length of the array. The hash table may have n different key values in the worst case, so it requires O(n) space complexity
523. Determine the continuous subarrays whose sum is a multiple of k
1. Prefix sum
0.Title
Given an array containing non-negative numbers and a target integer k, determine whether the array contains consecutive subarrays with a size of at least 2 and a sum that is a multiple of k, that is, the sum is n*k, where n is also an integer
1. Ideas
1. Use an additional sum array to store the cumulative sum of the array. sum[i] stores the prefix sum to the i-th element position.
2. We will not traverse the entire sub-array to find the sum. We only need to use the prefix sum of the array to find the i-th number to the j-th number. We only need to find sum[j]−sum[i] nums[i ]
2.Code
public class Solution { public boolean checkSubarraySum(int[] nums, int k) { int[] sum = new int[nums.length]; sum[0] = nums[0]; for (int i = 1; i < nums.length; i ) sum[i] = sum[i - 1] nums[i];//sum[i] saves the prefix sum to the i-th element position for (int start = 0; start < nums.length - 1; start ) { for (int end = start 1; end < nums.length; end ) { //To find the i-th number to the j-th number, just find sum[j] - sum[i] nums[i] int summ = sum[end] - sum[start] nums[start]; if (summ == k || (k != 0 && summ % k == 0)) return true; } } return false; } }
3. Complexity
Time: O(n^2), Space: O(n)
2. Dynamic programming
1. Ideas
Calculate the sum of the subarrays with lengths 2, 3,...m respectively, and determine whether it is a multiple of k. We copy the data of nums to dp When calculating the sum of subarrays of length 2: dp[j] = dp[j] nums[j 1]; where dp[j] is nums[j] When calculating the sum of subarrays of length 3: dp[j] = dp[j] nums[j 2]; where dp[j] is the updated dp[j], and one dp[j] is equivalent to nums[j ] nums[j 1] In this way, when calculating the size of the subarray of length p, you can use the calculated subarray of length p-1 to update, and you can optimize the original triple loop into a double loop.
2.Code
class Solution { int[] dp = new int[10010]; public boolean checkSubarraySum(int[] nums, int k) { if(nums.length < 2) return false; //When k==0, consider it separately. In fact, the only difference between it and k!=0 is whether to perform modular operation or not. if(k==0){ for(int i = 0; i < nums.length; i ){ for(int j = 0; j < nums.length-i; j ){ dp[j] = (dp[j] nums[j i]); if(i != 0 && dp[j] == 0) return true; } } return false; } //When i=k, dp[j] represents the sum of k 1 consecutive integers in nums with j as the starting subscript. //For example, when i=0, it is equivalent to copying nums to dp //When i=1, dp[0] is equivalent to starting with 0 as the subscript, and the sum of the two integers in nums, that is, nums[0] nums[1] //You can use the original dp to update each calculation instead of adding them one by one. for(int i = 0; i < nums.length; i ){ for(int j = 0; j < nums.length-i; j ){ dp[j] = (dp[j] nums[j i]) % k; if(i != 0 && dp[j] == 0) return true; } } return false; } }
3. Complexity
Time: O(n^2), Space: O(n)
3. Hash table
1. Ideas
1. Use HashMap to save the cumulative sum up to the i-th element, but we divide this prefix sum by k to take the remainder
2. We traverse the given array and record sum%k up to the current position. Once we find the new value of sum%k, we insert a record into the HashMap (sum%k, i)
3. Assume that the value of sum%k at the i-th position is rem. If the sum of any subarray with i as the left endpoint is a multiple of k, for example, the position is j, then the value stored in the jth element in the HashMap is (rem n*k)%k, and we will find (rem n ∗k)%k=rem, which is the same value as the i-th element saved in HashMap
4. Come to the conclusion: As long as the value of sum%k has been put into the HashMap, it means that there are two indexes i and j, and the sum of the elements between them is an integer multiple of k. As long as there is the same sum%k in the HashMap , we can directly return {True}
2.Code
public class Solution { public boolean checkSubarraySum(int[] nums, int k) { int sum = 0; HashMap < Integer, Integer > map = new HashMap < > (); map.put(0, -1); for (int i = 0; i < nums.length; i ) { sum = nums[i]; if (k != 0) sum = sum % k; //As long as the value of sum%k has been put into the HashMap, it means that there are two indexes i and j, and the sum of the elements between them is an integer multiple of k if (map.containsKey(sum)) { if (i - map.get(sum) > 1) return true; } else map.put(sum, i);//Save the cumulative sum up to the i-th element and divide it by k to get the remainder } return false; } }
3. Complexity
Time: O(n), Space: O(min(n,k)) HashMap contains at most min(n,k) different elements.
713.The number of consecutive subarrays whose product is less than k
1. Dichotomy
0.Title
Given an array of positive integers nums. Find the number of consecutive subarrays in the array whose product is less than k
1. Ideas
1. For a fixed i, binary search finds the largest j such that the product of nums[i] to nums[j] is less than k. However, since the product may be very large and cause numerical overflow, we need to take the logarithm of the nums array and convert multiplication into addition.
2. After taking the logarithm of each number in nums, we store its prefix and prefix. During binary search, for i and j, we can use prefix[j 1]−prefix[i] to get nums[i] The logarithm of the product to nums[j]. For a fixed i, when the largest j that satisfies the condition is found, it will contain j−i 1 continuous subarray whose product is less than k.
2.Code
class Solution { public int numSubarrayProductLessThanK(int[] nums, int k) { if (k == 0) return 0; double logk = Math.log(k); double[] prefix = new double[nums.length 1]; //Get the prefix sum after logarithms for (int i = 0; i < nums.length; i ) { prefix[i 1] = prefix[i] Math.log(nums[i]);//The subscript of the prefix sum starts from 1 } int ans = 0; for (int i = 0; i < prefix.length; i ) { //The dichotomy is performed in the prefix sum, which is equivalent to being performed in the result int lo = i 1, hi = prefix.length; while (lo < hi) { int mi = lo (hi - lo) / 2; //When i=1, prefix[1] is equivalent to removing nums[0], that is, counting from nums[1] //When i=2, prefix[2] is equivalent to removing nums[0] nums[1], that is, counting from nums[2] if (prefix[mi] < prefix[i] logk - 1e-9) lo = mi 1; else hi = mi; } ans = lo - i - 1;//When i=1, it is equivalent to the result after removing the first number } //When i=2, it is equivalent to the result after removing the first two numbers. return ans; //-1 is because lo itself is already greater than the target value } }
3. Complexity
Time: O(nlogn), Space: O(n)
2.Double pointers
1. Ideas
1. For each right, we need to find the smallest left, so that the product of numbers between them is less than k. Since when left increases, this product does not increase monotonically, so we can use the double pointer method to move left monotonically.
2. Use a loop to enumerate right, and set the initial value of left to 0. At each step of the loop, it means that right is moved one position to the right, and the product is multiplied by nums[right]. At this time we need to move left to the right until the condition that the product is less than k is met. On each move, the product needs to be divided by nums[left]. When the left movement is completed, for the current right, it contains right-left 1 consecutive sub-array whose product is less than k
2.Code
class Solution { public int numSubarrayProductLessThanK(int[] nums, int k) { if (k <= 1) return 0; int prod = 1, ans = 0, left = 0; for (int right = 0; right < nums.length; right ) { prod *= nums[right];//Current prefix product while (prod >= k) prod /= nums[left];//When not satisfied, move the left pointer to the right continuously //In order not to repeat calculations, after each right is positioned, only the number of substrings ending with the right pointer is calculated. //Anything that does not end with right has been calculated before and cannot be recalculated here. ans = right - left 1; } return ans; } }
3. Complexity
Time: O(n), Space: O(1)
53. Maximum subsequence sum
1. Dynamic programming
0.Title
Given an integer array nums, find a continuous subarray (at least one element) with the maximum sum and return its maximum sum
1. Ideas
1. Use ai to represent nums[i], and use f(i) to represent the "maximum sum of consecutive subarrays ending with the i-th number"
2. How to find f(i)? You can consider whether ai becomes a segment alone or joins the segment corresponding to f(i−1). This depends on the size of ai and f(i−1) ai. Write such a dynamic programming transfer equation: f(i)=max{ f(i−1) ai,ai}
3. Considering that f(i) is only related to f(i−1), we can only use one variable pre to maintain the value of f(i−1) for the current f(i), thus making the space complicated. The speed is reduced to O(1), which is somewhat similar to the idea of "rolling array"
2.Code
class Solution { public int maxSubArray(int[] nums) { int pre = 0, maxAns = nums[0]; for (int x : nums) { //pre to maintain the value of f(i−1) for the current f(i) pre = Math.max(pre x, x);//Determine whether pre is a negative number and whether it should be added to the current number maxAns = Math.max(maxAns, pre);//Get the maximum value } return maxAns; } }
3. Complexity
Time: O(n), Space: O(1)
2. Divide and Conquer: Line Segment Tree
1. Ideas
1. This divide and conquer method is similar to the pushUp operation of "Line Segment Tree Solving LCIS Problem". It is recommended to take a look at the line segment tree interval merging method to solve the "Longest Interval Continuous Rising Sequence Problem" and "The Maximum Interval Subsegment Sum Problem" that have been asked many times. "
2. How to merge the information in the [l,m] interval and the information in the [m 1,r] interval into the information in the interval [l,r]. The two most critical questions are: What information do we want to maintain in the interval? How do we combine this information?
3. For an interval [l, r], we can maintain four quantities:
4. After calculating the above three quantities, it is easy to calculate the mSum of [l, r]. We can consider whether the interval corresponding to the mSum of [l, r] spans m - it may not span m, that is to say, the mSum of [l, r] may be the mSum of the "left sub-interval" and the "right sub-interval" One of mSum; it may also span m, and may be the sum of rSum of the "left subinterval" and lSum of the "right subinterval". Whichever is greater of the three
2.Code
class Solution { public int maxSubArray(int[] nums) { if (nums == null || nums.length <= 0)// Input verification return 0; int len = nums.length;//Get the input length return getInfo(nums, 0, len - 1).mSum; } class wtevTree { //Line segment tree int lSum;//The maximum sum of subsegments with the left interval as the endpoint int rSum;//The maximum sum of subsegments with the right interval as the endpoint int iSum;//The sum of all numbers in the range int mSum;//The maximum sub-segment sum of this interval wtevTree(int l, int r, int i, int m) { // constructor lSum = l; rSum = r; iSum = i; mSum = m; } } // Calculate the attributes of the previous layer through the existing attributes, and return up step by step to obtain the line segment tree wtevTree pushUp(wtevTree leftT, wtevTree rightT) { // The lSum of the new subsection is equal to the lSum of the left interval or the sum of the intervals of the left interval plus the lSum of the right interval int l = Math.max(leftT.lSum, leftT.iSum rightT.lSum); // The rSum of the new subsegment is equal to the rSum of the right interval or the sum of the intervals of the right interval plus the rSum of the left interval int r = Math.max(leftT.rSum rightT.iSum, rightT.rSum); //The interval sum of the new subsegment is equal to the sum of the interval sums of the left and right intervals int i = leftT.iSum rightT.iSum; //The maximum sub-segment sum of the new sub-segment, its sub-segment may pass through the left and right intervals, or the left interval, or the right interval int m = Math.max(leftT.rSum rightT.lSum, Math.max(leftT.mSum, rightT.mSum)); return new wtevTree(l, r, i, m); } // Recursively create and obtain the structure of all subsegments of the input interval wtevTree getInfo(int[] nums, int left, int right) { if (left == right) // If the length of the interval is 1, its four subsegments are all their values return new wtevTree(nums[left], nums[left], nums[left], nums[left]); int mid = (left right) >> 1;// Obtain the middle point mid, shifting one position to the right is equivalent to dividing by 2, and the operation is faster wtevTree leftT = getInfo(nums, left, mid); wtevTree rightT = getInfo(nums, mid 1, right);//mid 1, there is no intersection between the left and right intervals. return pushUp(leftT, rightT);//After the recursion ends, do the last merge } }
3. Complexity
Time: O(n), Space: O(logn) recursive stack
4. Summary
1. "Method 2" has the same time complexity as "Method 1", but because it uses recursion and maintains four information structures, the running time is slightly longer and the space complexity is not as good as Method 1. Excellent, and difficult to understand. So what is the significance of this method?
2. But if you look closely at "Method 2", it can not only solve the problem of the interval [0, n-1], but also can be used to solve the problem of any sub-interval [l, r]. If we memorize the information of all the sub-intervals that appear after dividing and conquering [0, n-1] using heap storage, that is, after building a real tree, we can do it in O(logn) time We can even modify the values in the sequence and do some simple maintenance, and then we can still find the answer in any interval in O(logn) time. For large-scale queries, , the advantages of this method are reflected. This tree is a magical data structure mentioned above - line segment tree
152. Maximum word order product
1. Dynamic programming
0.Title
Given an integer array nums, please find the continuous subarray with the largest product in the array (the subarray contains at least one number), and return the product corresponding to the subarray.
1. Thoughts
1. Based on the experience of "53. Maximum subsequence sum", it is wrong to write the dynamic programming transfer equation: f(i)=max{f(i−1) ai, ai}
2. The definition here does not satisfy the "optimal substructure". The optimal solution at the current position may not be obtained by transferring the optimal solution at the previous position.
3. Classify and discuss based on positivity. If the current position is a negative number, then we hope that the product of a segment ending with its previous position is also a negative number, so that a negative can be a positive, and we hope that the product Try to "take as much as possible", that is, as small as possible. If the current position is a positive number, we prefer that the product of a segment ending at its previous position is also a positive number, and we want it to be as large as possible. We can maintain another fmin(i), which represents the Product of the smallest subarray of products ending with i elements
4. State transition equation
5. It represents the product fmax(i) of the subarray with the largest product at the end of the i-th element. You can consider adding ai to the product of the subarray with the largest or smallest product at the end of the i-1 element, and add ai to the two. , the greater of the three is the product of the largest subarray of products at the end of the i-th element. The product of the smallest subarray of products at the end of the i-th element fmin(i) is the same.
2.Code
class Solution { public int maxProduct(int[] nums) { int length = nums.length; int[] maxF = new int[length]; int[] minF = new int[length];//The product of the smallest subarray ending with the i-th element System.arraycopy(nums, 0, maxF, 0, length);//Copy nums starting from 0 to maxF starting from 0, length length System.arraycopy(nums, 0, minF, 0, length); for (int i = 1; i < length; i) { //Two state transition equations maxF[i] = Math.max(maxF[i - 1] * nums[i], Math.max(nums[i], minF[i - 1] * nums[i])); minF[i] = Math.min(minF[i - 1] * nums[i], Math.min(nums[i], maxF[i - 1] * nums[i])); } int ans = maxF[0]; for (int i = 1; i < length; i) { ans = Math.max(ans, maxF[i]); } return ans; } }
3. Complexity
Time and space are both O(n)
2. Optimize space
1. Thoughts
Since the i-th state is only related to the i-1th state, according to the "rolling array" idea, we can only use two variables to maintain the state at time i-1, one to maintain fmax and one to maintain fmin.
2.Code
class Solution { public int maxProduct(int[] nums) { int maxF = nums[0], minF = nums[0], ans = nums[0]; int length = nums.length; for (int i = 1; i < length; i) { int mx = maxF, mn = minF;//Only two variables are used to maintain the state at time i−1 and optimize the space maxF = Math.max(mx * nums[i], Math.max(nums[i], mn * nums[i])); minF = Math.min(mn * nums[i], Math.min(nums[i], mx * nums[i])); ans = Math.max(maxF, ans); } return ans; } }
3. Complexity
Time: O(n), Space: O(1)
198. Robbery and robbery
1. Dynamic programming rolling array
0.Title
You are a professional thief planning to steal houses along the street. There is a certain amount of cash hidden in each room. The only restriction factor that affects your theft is that the adjacent houses are equipped with interconnected anti-theft systems. If two adjacent houses are broken into by thieves on the same night, the system will automatically alarm . Given an array of non-negative integers representing the amount of money stored in each house, calculate the maximum amount of money you can steal in one night without triggering the alarm device.
1. Ideas
1. Consider the simplest case first. If there is only one house, then steal that house and you can steal up to the maximum total amount. If there are only two houses, since the two houses are adjacent, you cannot steal at the same time. You can only steal one of the houses. Therefore, choose the house with a higher amount to steal, and you can steal up to the maximum total amount.
2. If there are more than two houses, how to calculate the maximum total amount that can be stolen? For the (k>2)th house, there are two options: 1. If you steal the k-th house, you cannot steal the k-1 house. The total amount stolen is the sum of the maximum total amount of the first k-2 houses and the amount of the k-th house. 2. If the k-th house is not stolen, the total amount stolen is the maximum total amount of the first k-1 houses. Choose the option with the larger total amount of theft among the two options. The total amount of theft corresponding to this option is the maximum total amount of money that can be stolen from the first k houses.
3.dp[i] represents the maximum total amount that can be stolen from the first i houses, state transition equation: dp[i]=max(dp[i−2] nums[i],dp[i−1])
4. Boundary conditions: dp[0]=nums[0] and dp[1]=max(nums[0],nums[1]), the final answer is dp[n−1]
2.Code
class Solution { public int rob(int[] nums) { if (nums == nul|| nums.length == 0) { return 0; } int length = nums.length; if (length == 1) { return nums[0]; } int[] dp = new int[length]; dp[0] = nums[0];//Two boundary conditions dp[1] = Math.max(nums[0], nums[1]); for (int i = 2; i < length; i ) {//Dynamic transfer equation dp[i] = Math.max(dp[i - 2] nums[i], dp[i - 1]); } return dp[length - 1]; } }
3. Complexity
Time: O(n), Space: O(n)
4. Optimization
The above method uses an array to store the results. Considering that the maximum total amount of each house is only related to the maximum total amount of the first two houses of the house, you can use a rolling array to store only the maximum total amount of the first two houses at each moment.
class Solution { public int rob(int[] nums) { if (nums == nul|| nums.length == 0) { return 0; } int length = nums.length; if (length == 1) { return nums[0]; } //At each moment, only the highest total amount of the first two houses needs to be stored, rolling array int first = nums[0], second = Math.max(nums[0], nums[1]); for (int i = 2; i < length; i ) { int temp = second; second = Math.max(first nums[i], second); first = temp; } return second; } }
Time: O(n), Space: O(1)
213. Robbery II Form a circle ×
1. Dynamic programming
0.Title
You are a professional thief planning to steal houses along the street, with a certain amount of cash hidden in each room. All the houses in this place are in a circle, which means that the first house and the last house are right next to each other. At the same time, adjacent houses are equipped with interconnected anti-theft systems. If two adjacent houses are broken into by thieves on the same night, the system will automatically alarm. Given an array of non-negative integers representing the amount of money stored in each house, calculate the maximum amount of money you can steal without triggering the alarm device.
1. Ideas
1. The circular arrangement means that only one of the first house and the last house can be chosen to steal, so this circular arrangement of rooms problem can be reduced to two single-row arrangement of rooms sub-problems:
2. Without stealing the first house (i.e. nums[1]), the maximum amount is p1. Without stealing the last house (i.e. nums[n−1]), the maximum amount is p2. Comprehensive The maximum amount of theft is max(p1,p2)
3. State transition equation: dp[i]=max(dp[i−2] nums[i],dp[i−1])
4.dp[n] is only related to dp[n−1] and dp[n−2], so we can set two variables cur and pre to be recorded alternately, reducing the space complexity to O(1)
2.Code
class Solution { public int rob(int[] nums) { if(nums.length == 0) return 0; if(nums.length == 1) return nums[0]; //The length of the copy is left closed and right open [0, length-2] return Math.max(myRob(Arrays.copyOfRange(nums, 0, nums.length - 1)), myRob(Arrays.copyOfRange(nums, 1, nums.length))); } private int myRob(int[] nums) { int pre = 0, cur = 0, tmp; for(int num : nums) { tmp = cur; cur = Math.max(pre num, cur); pre = tmp; } return cur; } }
3. Complexity
Time: O(n), Space: O(1)
337. Robbery III Binary Tree
1. Dynamic programming
0.Title
After robbing a street and a circle of houses last time, the thief found a new area where he could steal. There is only one entrance to this area, which we call the "root". In addition to the "root", each house has one and only one "parent" house connected to it. After some reconnaissance, the clever thief realized that "all the houses in this place are arranged like a binary tree." If two directly connected houses are robbed on the same night, the house will automatically call the police. Calculate the maximum amount of money a thief can steal in one night without triggering the alarm
1. Ideas
1. Simplify this problem: a binary tree. Each point on the tree has a corresponding weight. Each point has two states (selected and unselected). Ask about the situation where points with a parent-child relationship cannot be selected at the same time. What is the maximum weight sum of the points that can be selected?
2. Use f(o) to represent the maximum weight sum of the selected nodes on the subtree of o node when o node is selected; g(o) represents the maximum weight sum of the selected node on the subtree of o node when o node is not selected. The maximum weight sum of the selected node; l and r represent the left and right children of o
3. When o is selected, neither the left or right children of o can be selected. Therefore, the maximum weight sum of the selected points on the subtree when o is selected is the sum of the maximum weight sums of l and r when they are not selected, that is, f (o)= g(l) g(r)f(o)=g(l) g(r)
4. When o is not selected, the left and right children of o can be selected or not selected. For a specific child x of o, its contribution to o is the larger value of the sum of weights when x is selected and when x is not selected. Therefore g(o)=max{f(l),g(l)} max{f(r),g(r)}
5. Use a hash map to store the function values of f and g, and use a depth-first search method to traverse the binary tree in order, and we can get the f and g of each node. The maximum value of f and g of the root node is the answer we are looking for
2.Code
class Solution { Map<TreeNode, Integer> f = new HashMap<TreeNode, Integer>(); Map<TreeNode, Integer> g = new HashMap<TreeNode, Integer>(); public int rob(TreeNode root) { dfs(root);//Depth first traverse the root node return Math.max(f.getOrDefault(root, 0), g.getOrDefault(root, 0)); } public void dfs(TreeNode node) { if (node == null) { return; } dfs(node.left);//Adopt post-order traversal method dfs(node.right); //When node is selected, the maximum weight sum of the selected point on the subtree is the sum of the maximum weight sums of l and r when they are not selected. f.put(node, node.val g.getOrDefault(node.left, 0) g.getOrDefault(node.right, 0)); //A specific child x when node is not selected, its contribution to node is the larger of the sum of weights when x is selected and when x is not selected. g.put(node, Math.max(f.getOrDefault(node.left, 0), g.getOrDefault(node.left, 0)) Math.max(f.getOrDefault(node.right, 0), g.getOrDefault(node.right, 0))); } }
3. Complexity
The above algorithm performs a post-order traversal of the binary tree, and the time complexity is O(n); since the recursion will use stack space, the space cost is O(n), and the space cost of the hash map is also O(n), so The space complexity is also O(n)
4. Optimization
Whether it is f(o) or g(o), their final values are only related to f(l), g(l), f(r), g(r), so for each node, we only care about its What are the f and g of the child nodes. We can design a structure to represent the f and g values of a certain node. Every time the recursion returns, the f and g corresponding to this point are returned to the previous level call, which can save the space of the hash map.
class Solution { public int rob(TreeNode root) { int[] rootStatus = dfs(root); return Math.max(rootStatus[0], rootStatus[1]); } public int[] dfs(TreeNode node) { if (node == null) { return new int[]{0, 0}; } int[] l = dfs(node.left); int[] r = dfs(node.right); //l[0], r[0] represents the weight of grabbing, l[1], r[1] represents the weight of not grabbing int selected = node.val l[1] r[1]; int notSelected = Math.max(l[0], l[1]) Math.max(r[0], r[1]); return new int[]{selected, notSelected}; } }
Time complexity: O(n). Analyzed above. Space complexity: O(n). Although the optimized version eliminates the hash map space, the cost of using the stack space is still O(n), so the space complexity remains unchanged.
15.Sum of three numbers
1. Sorting double pointer
0.Title
Given an array nums containing n integers, determine whether there are three elements a, b, c in nums such that a b c = 0? Please find all triples that meet the conditions and are not repeated
1. No repetition
You cannot simply use a triple loop to enumerate all triples. You need to use a hash table to perform deduplication operations, sort the elements in the array from small to large, and then use an ordinary triple loop to meet the above requirements.
For each cycle, the elements of two adjacent enumerations cannot be the same, otherwise it will also cause duplication.
2.Double pointers
When you need to enumerate two elements in an array, if you find that as the first element increases, the second element decreases, then you can use the double pointer method to reduce the time complexity of the enumeration from O( N^2) reduced to O(N)
Keep the second loop unchanged, and change the third loop into a pointer starting from the right end of the array and moving to the left.
To add details, you need to keep the left pointer to the left of the right pointer (that is, b ≤ c)
3.Code
class Solution { public List<List<Integer>> threeSum(int[] nums) { int n = nums.length; Arrays.sort(nums); //Sort the array List<List<Integer>> ans = new ArrayList<List<Integer>>(); // enum a for (int first = 0; first < n; first) { // Needs to be different from the last enumerated number if (first > 0 && nums[first] == nums[first - 1]) { continue; } // The pointer corresponding to c initially points to the rightmost end of the array int third = n - 1; int target = -nums[first]; // enum b for (int second = first 1; second < n; second) { // Needs to be different from the last enumerated number if (second > first 1 && nums[second] == nums[second - 1]) { continue; } // Need to ensure that the pointer of b is on the left side of the pointer of c while (second < third && nums[second] nums[third] > target) { --third; } if (second == third) { // If the pointers overlap, the condition will not be met subsequently, and you can exit the loop break; } if (nums[second] nums[third] == target) { ans.add(Arrays.asList(nums[first],nums[second],nums[third])); } } } return ans; } }
4. Complexity
Time complexity: O(N^2), where N is the length of the array nums
Space complexity: O(logN). We ignore the space for storing answers, and the space complexity of additional sorting is O(logN). However, we modified the input array nums, which may not be allowed in actual situations. Therefore, it can also be regarded as using an additional array to store a copy of nums and sort it. The space complexity is O(N)
18.Sum of four numbers
1. Sorting double pointer
0.Title
Given an array nums containing n integers and a target value target, determine whether there are four elements a, b, c and d in nums such that the value of a b c d is equal to target? Find all quadruples that satisfy the condition and are not repeated.
1. Ideas
It is to add a layer of loop based on the sum of three numbers, and then use double pointers, but use the maximum and minimum values to judge in advance, saving time
2.Code
class Solution { public List<List<Integer>> fourSum(int[] nums,int target){ List<List<Integer>> result=new ArrayList<>();/*Define a return value*/ if(nums==null||nums.length<4){ return result; } Arrays.sort(nums); int length=nums.length; /*Define 4 pointers k, i, j, h k starts traversing from 0, i starts traversing from k 1, leaving j and h, j points to i 1, h points to the maximum value of the array*/ for(int k=0;k<length-3;k ){ /*Ignore when the value of k is equal to the previous value*/ if(k>0&&nums[k]==nums[k-1]){ continue; } /*Get the current minimum value. If the minimum value is larger than the target value, it means that subsequent larger and larger values will not work at all*/ int min1=nums[k] nums[k 1] nums[k 2] nums[k 3]; if(min1>target){ break;//The break used here exits the loop directly, because the subsequent numbers will only be larger. } /*Get the current maximum value. If the maximum value is smaller than the target value, it means that the subsequent smaller and smaller values will not work at all, ignore it*/ int max1=nums[k] nums[length-1] nums[length-2] nums[length-3]; if(max1<target){ continue;//Use continue here to continue the next loop, because the next loop has a larger number } /*Second level loop i, initial value points to k 1*/ for(int i=k 1;i<length-2;i){ if(i>k 1&&nums[i]==nums[i-1]){/*Ignore when the value of i is equal to the previous value*/ continue; } int j=i 1;/*define pointer j to point to i 1*/ int h=length-1;/*Define pointer h to point to the end of the array*/ int min=nums[k] nums[i] nums[j] nums[j 1]; if(min>target){ break; } int max=nums[k] nums[i] nums[h] nums[h-1]; if(max<target){ continue; } /*Start the performance of j pointer and h pointer, calculate the current sum, if it is equal to the target value, j and deduplication, h-- and deduplication, when the current sum is greater than the target value, h--, when the current sum is less than the target value j */ while (j<h){ int curr=nums[k] nums[i] nums[j] nums[h]; if(curr==target){ result.add(Arrays.asList(nums[k],nums[i],nums[j],nums[h]));//Complete in one step j; while(j<h&&nums[j]==nums[j-1]){ j; } h--; while(j<h&&i<h&&nums[h]==nums[h 1]){ h--; } }else if(curr>target){ h--; }else { j; } } } } return result; } }
3. Complexity
Time: O(n^3), the time complexity of sorting is O(nlogn)
Space: O(logn), mainly depends on the additional space used by sorting
454. Addition of four arrays ×
Use map for pairwise grouping
0.Title
Given four array lists A, B, C, D containing integers, count how many tuples (i, j, k, l) there are such that A[i] B[j] C[k] D[l] = 0
1. Ideas
1. Divide it into two groups, store one group in HashMap, and compare the other group with HashMap
2. In this case, the situation can be divided into three types: 1.HashMap stores an array, such as A. Then calculate the sum of the three arrays, such as BCD. The time complexity is: O(n) O(n^3), which results in O(n^3). 2.HashMap stores the sum of three arrays, such as ABC. Then calculate an array, such as D. The time complexity is: O(n^3) O(n), which results in O(n^3). 3.HashMap stores the sum of two arrays, such as AB. Then calculate the sum of the two arrays, such as CD. The time complexity is: O(n^2) O(n^2), which results in O(n^2).
3. According to the second point, we can conclude that two arrays are counted as two arrays.
4. Let’s take storing the sum of two arrays AB as an example. First, find the sum sumAB of any two numbers A and B, use sumAB as the key, and the number of times sumAB appears as the value, and store it in the hashmap. Then calculate sumCD, the inverse of the sum of any two numbers in C and D, and find whether the key is sumCD in the hashmap.
2.Code
class Solution { public int fourSumCount(int[] A, int[] B, int[] C, int[] D) { HashMap<Integer, Integer> map = new HashMap<Integer,Integer>(); int len=A.length; for(int a:A) { for(int b:B) { //Storage the result of a b, if there is no assignment of 1, it exists on the original basis 1 map.merge(a b, 1, (old,new_)->old 1); } } int res=0; for(int c:C) { for(int d:D) { res =map.getOrDefault(0-c-d, 0); } } return res; } }
3. Complexity
Time: O(n^2), Space: O(1)
4. Summary
The new map methods merge and getOrDefault are used in the algorithm. Compared with the traditional judgment writing method, the efficiency is greatly improved.
subtopic
3. Linked list
0.frequency
2,21,206,23,237,148,138,141,24,234,445,147,143,92,25,160,328,142,203,19,86,109,83,61,82,430,817,
2. Add two numbers in reverse order
0.Title
Given two non-empty linked lists representing two non-negative integers. Among them, their respective digits are stored in reverse order, and each of their nodes can only store one digit. If, we add these two numbers, a new linked list will be returned to represent their sum. You can assume that except for the number 0, neither number will start with 0.
1.Solution
public class ListNode addTwoNumbers(ListNode l1, ListNode l2) { ListNode dummyHead = new ListNode(0); ListNode p = l1, q = l2, curr = dummyHead; int carry = 0; while (p != null || q != null) { int x = (p != null) ? p.val : 0; int y = (q != null) ? q.val : 0; int sum = carry x y; carry = sum / 10; curr.next = new ListNode(sum % 10); curr = curr.next; if (p != null) p = p.next; if (q != null) q = q.next; } if (carry > 0) { curr.next = new ListNode(carry); } return dummyHead.next; }
The most important point of this question is the carry situation after the addition, which is easy to ignore. Another important point is that after the addition of the last digit is completed, a carry may still occur. This situation needs to be handled separately. The initial thought was to convert the two linked lists into numbers and add them, but this method will cause overflow for numbers that are too long, so the linked lists can only be processed.
Time: O(max(m, n)), Space: O(max(m, n))
2. Add two numbers sequentially
public ListNode addTwoNumbersExt(ListNode l1, ListNode l2) { //Define a new linked list to store calculation results. The leading node is created by default, so the default is 0 ListNode temp = new ListNode(0); ListNode node = temp; // Remember the first node int carry = 0;//carry, that is, if the addition is greater than 10, we add 1 to the next digit, the default is 0 Stack<Integer> stackL1 = new Stack<>(); // Use stack to receive data from l1 linked list Stack<Integer> stackL2 = new Stack<>();//Use the stack to receive data from the l2 linked list // Traverse the two linked lists and add them to the stack while (l1 != null || l2 != null) { // Determine whether the l1 linked list is empty. If not, point to the next node. if (l1 != null) { stackL1.push(l1.val); l1 = l1.next; } // Determine whether the l2 linked list is empty. If not, point to the next node. if (l2 != null) { stackL2.push(l2.val); l2 = l2.next; } } // Traverse both stacks while (!stackL1.empty() || !stackL2.empty()) { int x = (!stackL1.empty()) ? stackL1.pop() : 0;//If the stackL1 stack is not null, pop the stack, otherwise it is 0 int y = (!stackL2.empty()) ? stackL2.pop() : 0;//If the stackL2 stack is not null, pop the stack, otherwise it is 0 int sum = x y carry;//Because single digits are added, the maximum index is equal to 19, and carry must be added // Store the result in the linked list, but it must be modulus 10. As long as it is greater than or equal to 10, it will get a single digit. temp.next = new ListNode(sum % 10); temp = temp.next;//points to the next node // Find the carry. If it is greater than 10, you can find that the carry is 1, otherwise it is 0. carry = sum / 10; } // There may be a situation where the final value calculation of the two linked lists is greater than or equal to 10 and both linked lists are empty, so the carry must be put into the last node if (carry > 0) { temp.next = new ListNode(carry); temp = temp.next; } // Because the leading node is created by default, we have to start from the next one in the temp linked list. return node.next; }
Instead of storing in reverse order, you should find a way to change it to reverse order. You can use the reverse order method that comes with the linked list, or you can use the stack to achieve reverse order. Finally, you need to reverse the result.
4. String
0.frequency
5,20,937,3,273,22,1249,68,49,415,76,10,17,91,6,609,93,227,680,767,12,8,67,126,13,336,
3. The longest substring without repeated characters
1. Sliding window
1. Ideological premise
Suppose we select the k-th character in the string as the starting position, and get the ending position of the longest substring that does not contain repeated characters as r_k. Then when we select the k 1th character as the starting position, the characters from k 1 to r_k are obviously not repeated, and since the original kth character is missing, we can try to continue to increase r_k until the right until repeated characters appear on the side.
2. Concrete thoughts
In this way, we can use "sliding window" to solve this problem: We use two pointers to represent a substring (the left and right boundaries) of a string. The left pointer represents the "starting position of the enumerated substring" mentioned above, and the right pointer is r_k mentioned above; In each step of the operation, we will move the left pointer one space to the right, indicating that we start enumerating the next character as the starting position, and then we can continue to move the right pointer to the right, but we need to ensure that the two pointers correspond There are no repeated characters in the substring. After the move is completed, this substring corresponds to the longest substring starting from the left pointer and not containing repeated characters. We record the length of this substring; At the end of the enumeration, the length of the longest substring we found is the answer.
3. Hash table to determine repeated characters
We also need to use a data structure to determine whether there are repeated characters. The commonly used data structure is a hash set. When the left pointer moves to the right, we remove a character from the hash set. When the right pointer moves to the right, When moving, we add a character to the hash set.
4.Code
class Solution { public int lengthOfLongestSubstring(String s) { // Hash collection, records whether each character appears or not Set<Character> occ = new HashSet<Character>(); int n = s.length(); // The right pointer, the initial value is -1, which is equivalent to us being on the left side of the left boundary of the string and not yet starting to move. int rk = -1, ans = 0; for (int i = 0; i < n; i) { if (i != 0) { // Move the left pointer one space to the right and remove one character occ.remove(s.charAt(i - 1)); } while (rk 1 < n && !occ.contains(s.charAt(rk 1))) { // Keep moving the right pointer occ.add(s.charAt(rk 1)); rk; } // The i to rk characters are an extremely long non-repeating character substring ans = Math.max(ans, rk - i 1); } return ans; } }
5. Complexity
Time: O(N), where N is the length of the string, space: O(∣Σ∣),∣Σ∣ represents the size of the character set
5. The longest palindrome substring
1. Dynamic programming dp
1. Thoughts
For a substring, if it is a palindrome string and the length is greater than 2, then after removing the first and last two letters, it is still a palindrome string.
2. State transition equation
P(i,j)=P(i 1,j−1)∧(Si == Sj)
3. Boundary conditions
P(i,i)=true single character P(i,i 1)=(Si==Si 1) two characters
4.Code
class Solution { public String longestPalindrome(String s) { int n = s.length(); boolean[][] dp = new boolean[n][n]; String ans = ""; for (int k = 0; k < n; k) { for (int i = 0; i k < n; i) { int j = i k; //First deal with two critical situations if (k == 0) { //When k=0, j=i, dp[i][j] is equivalent to a character, which must be a palindrome string dp[i][j] = true; } else if (k == 1) { //When k=1, j=i 1, dp[i][j] is equivalent to two consecutive characters. When the two characters are the same, it must be a palindrome string dp[i][j] = (s.charAt(i) == s.charAt(j)); } else { //When k>1, the transfer equation of dynamic programming needs to be satisfied: P(i,j)=P(i 1,j−1)∧(Si == Sj) dp[i][j] = (s.charAt(i) == s.charAt(j) && dp[i 1][j - 1]); } if (dp[i][j] && k 1 > ans.length()) { //Update the length of the palindrome string ans = s.substring(i, i k 1); //Pay attention to the specific usage of the substring method } } } return ans; } }
5. Complexity
Time complexity: O(n^2), where n is the length of the string. The total number of states in dynamic programming is O(n^2), and for each state, the time we need to transfer is O(1). Space complexity: O(n^2), that is, the space required to store dynamic programming state
2. Center expansion
1. Prerequisites
According to the state transition equation, it can be found that all states have unique possibilities when transitioning. In other words, we can start "expanding" from each boundary case, and we can also get the answers corresponding to all states.
The substring corresponding to the "boundary case" is actually the "palindrome center" of the palindrome string we "expanded"
2.Essence
Enumerate all "palindrome centers" and try to "expand" until it cannot be expanded. The length of the palindrome string at this time is the longest palindrome string length under this "palindrome center"
3.Code
class Solution { public String longestPalindrome(String s) { if (s == null || s.length() < 1){ return ""; } int start = 0;//Initialize the starting point and end point of the largest palindrome substring int end = 0; // Traverse each position and use it as the center position for (int i = 0; i < s.length(); i ) { // Get the lengths of odd and even palindrome substrings respectively int len_odd = expandCenter(s,i,i); int len_even = expandCenter(s,i,i 1); int len = Math.max(len_odd,len_even); // Compare the maximum length // Calculate the starting point and end point corresponding to the largest palindrome substring, and draw a picture to determine if (len > end - start){ //Why is len-1 needed here? Explain here, because the for loop starts from 0, //If it is an odd number of palindromes, assuming there are 3 palindromes, then len=3, at this time the center i is subscript 1 (starting from 0), then The results of (len-1)/2 and len/2 are both 1, because integers are rounded down. //But if it is an even number of palindromes, assuming there are 4 palindromes, then len=4, the center at this time is a dotted line, but the position of i But in 1, (because S is traversed from left to right, if from right to left, //The position of i will be at 2.) At this time, (len-1)/2=1, len/2=2. Obviously, in order to ensure that the subscript is correct, what we need is (len-1)/2. The reason is actually that i is one position to the left of the center line, //So reduce it by 1. start = i - (len - 1)/2; end = i len/2; } } return s.substring(start,end 1);// Note: The end 1 here is because of the left-closed and right-open function of java. } private int expandCenter(String s,int left,int right){ //Initial left and right boundaries // When left = right, the center of the palindrome is one character, and the length of the palindrome string is an odd number. // When right = left 1, the center of the palindrome is a gap, and the length of the palindrome string is an even number. // When breaking out of the loop, s.charAt(left) is exactly satisfied! = s.charAt(right) while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)){ left--; right ; } return right - left - 1; // The length of the palindrome string is right-left 1-2 = right - left - 1 } }
4. Complexity
Time complexity: O(n^2), where n is the length of the string. There are n and n-1 palindrome centers of length 1 and 2 respectively, and each palindrome center will expand outward at most O(n) times. Space complexity: O(1)
3.Manacher horse-drawn cart
5.Binary search
0.frequency
4,50,33,167,287,315,349,29,153,240,222,327,69,378,410,162,1111,35,34,300,363,350,209,354,278,374,981,174
4. Find the median of two positive-order arrays
1. Conventional thinking
1. Use the merge method to merge two ordered arrays to obtain a large ordered array. The element in the middle of a large ordered array is the median.
2. There is no need to merge two ordered arrays, just find the position of the median. Since the lengths of the two arrays are known, the sum of the subscripts of the two arrays corresponding to the median is also known. Maintain two pointers, initially pointing to the subscript 0 position of the two arrays, and move the pointer pointing to the smaller value back one bit each time (if one pointer has reached the end of the array, you only need to move the pointer of the other array ), until reaching the median position
The time complexity of the first idea is O(m n), and the space complexity is O(m n). Although the second idea can reduce the space complexity to O(1), the time complexity is still O(m n). The time complexity required by the question is O(log(m n)), so the above two ideas do not meet the time complexity required by the question.
2.Binary search:kth decimal
1. Three situations
If A[k/2-1] < B[k/2-1], then the numbers smaller than A[k/2-1] are at most the first k/2−1 numbers of A and the first k/ of B 2-1 numbers, that is, there are only k-2 numbers smaller than A[k/2-1] at most, so A[k/2-1] cannot be the k-th number, A[0] to A[ k/2−1] is also impossible to be the k-th number, and all can be excluded. If A[k/2-1] > B[k/2-1], then B[0] to B[k/2-1] can be excluded. If A[k/2-1] = B[k/2-1], it can be classified into the first case.
2. Processing results
After comparing A[k/2−1] and B[k/2−1], k/2 numbers that cannot be the kth smallest number can be eliminated, and the search range is reduced by half. At the same time, we will continue to perform binary search on the new array after elimination, and reduce the value of k according to the number of numbers we exclude. This is because the numbers we exclude are not larger than the kth smallest number.
3. Three special situations
If A[k/2−1] or B[k/2−1] is out of bounds, then we can select the last element in the corresponding array. In this case, we must reduce the value of k according to the number of exclusions, rather than directly subtracting k/2 from k. If an array is empty, it means that all elements in the array have been excluded, and we can directly return the kth smallest element in another array. If k=1, we only need to return the minimum value of the first elements of the two arrays.
4. Algorithm
class Solution { public double findMedianSortedArrays(int[] nums1, int[] nums2) { int length1 = nums1.length, length2 = nums2.length; int totalLength = length1 length2; if (totalLength % 2 == 1) { int midIndex = totalLength / 2; double median = getKthElement(nums1, nums2, midIndex 1); return median; } else { int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2; double median = (getKthElement(nums1, nums2, midIndex1 1) getKthElement(nums1, nums2, midIndex2 1)) / 2.0; return median; } } public int getKthElement(int[] nums1, int[] nums2, int k) { /* Main idea: To find the kth (k>1) smallest element, then compare pivot1 = nums1[k/2-1] and pivot2 = nums2[k/2-1] * The "/" here means integer division * There are nums1[0 .. k/2-2] elements in nums1 that are less than or equal to pivot1, a total of k/2-1 * There are nums2[0 .. k/2-2] elements in nums2 that are less than or equal to pivot2, a total of k/2-1 * Take pivot = min(pivot1, pivot2), the total number of elements less than or equal to pivot in the two arrays will not exceed (k/2-1) (k/2-1) <= k-2 * In this way, the pivot itself can only be the k-1th smallest element. * If pivot = pivot1, then nums1[0 .. k/2-1] cannot be the kth smallest element. "Delete" all these elements and use the rest as a new nums1 array * If pivot = pivot2, then nums2[0 .. k/2-1] cannot be the kth smallest element. "Delete" all these elements and use the rest as a new nums2 array * Since we "delete" some elements (these elements are smaller than the k-th smallest element), we need to modify the value of k, minus the number of deleted elements. */ int length1 = nums1.length, length2 = nums2.length; int index1 = 0, index2 = 0; int kthElement = 0; while (true) { // Boundary cases if (index1 == length1) { return nums2[index2 k - 1]; } if (index2 == length2) { return nums1[index1 k - 1]; } if (k == 1) { return Math.min(nums1[index1], nums2[index2]); } // Under normal circumstances, index1 is used as the starting point and is constantly updated. int half = k / 2; int newIndex1 = Math.min(index1 half, length1) - 1; //The length of the array may be smaller than the former int newIndex2 = Math.min(index2 half, length2) - 1; int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2]; if (pivot1 <= pivot2) { // Combine the two situations k -= (newIndex1 - index1 1); //The index1 below is also changing at the same time, this is the length that is subtracted index1 = newIndex1 1; } else { k -= (newIndex2 - index2 1); index2 = newIndex2 1; } } } }
5. Complexity
Time complexity: O(log(m n)), where m and n are the lengths of the arrays nums1 and nums2 respectively. Initially, there is k=(m n)/2 or k=(m n)/2 1. Each round of loop can reduce the search range by half, so the time complexity is O(log(m n)). Space complexity: O(1).
3. Divide the array
1. The role of the median
Divide a set into two subsets of equal length, such that the elements in one subset are always larger than the elements in the other subset
2. Two situations
When the total length of A and B is an even number, if it can be confirmed: len(left_part)=len(right_part) max(left_part)≤min(right_part) Then, all elements in {A,B} have been divided into two parts of the same length, and the elements in the former part are always less than or equal to the elements in the latter part. The median is the average of the maximum value of the previous part and the minimum value of the next part.
When the total length of A and B is an odd number, if it can be confirmed: len(left_part)=len(right_part) 1 max(left_part)≤min(right_part) Then, all the elements in {A,B} have been divided into two parts, the former part has one more element than the latter part, and the elements in the former part are always less than or equal to the elements in the latter part. The median is the maximum value of the previous part
3. Merge the two situations
To ensure these two conditions, just ensure: i j=m−i n−j (when m n is an even number) or i j=m−i n−j 1 (when m n is an odd number). The left side of the equal sign is the number of elements in the previous part, and the right side of the equal sign is the number of elements in the latter part. Move all i and j to the left side of the equal sign, and we can get i j=(m n 1)/2. The fractional result here only retains the integer part
It is stipulated that the length of A is less than or equal to the length of B, that is, m≤n. In this way, for any i∈[0,m], there is j=(m n 1)/2−i∈[0,n]. If the length of A is longer, you only need to exchange A and B. If m> n, then the resulting j may be a negative number.
B[j−1]≤A[i] and A[i−1]≤B[j], that is, the maximum value of the former part is less than or equal to the minimum value of the latter part
4. Simplify analysis
Assume that A[i−1],B[j−1],A[i],B[j] always exist. For critical conditions such as i=0, i=m, j=0, j=n, we only need to stipulate that A[−1]=B[−1]=−∞, A[m]=B[n]= ∞ is enough. This is also relatively intuitive: when an array does not appear in the previous part, the corresponding value is negative infinity, which will not affect the maximum value of the previous part; when an array does not appear in the latter part, the corresponding value is positive Infinity will not affect the minimum value of the latter part.
5. work
Find i in [0,m] such that B[j-1]≤A[i] and A[i-1]≤B[j], where j=(m n 1)/2−i It is equivalent to finding i in [0,m] so that A[i-1]≤B[j]
6. Algorithm
class Solution { public double findMedianSortedArrays(int[] nums1, int[] nums2) { if (nums1.length > nums2.length) { return findMedianSortedArrays(nums2, nums1); } int m = nums1.length; int n = nums2.length; int left = 0, right = m, ansi = -1; //median1: the maximum value of the previous part //median2: the minimum value of the latter part int median1 = 0, median2 = 0; while (left <= right) { // The previous part contains nums1[0 .. i-1] and nums2[0 .. j-1] // The latter part contains nums1[i .. m-1] and nums2[j .. n-1] int i = (left right) / 2; //dichotomy, i starts from the middle of the interval int j = (m n 1) / 2 - i;//The operation of 1 combines the total number of odd and even numbers into one case //nums_im1, nums_i, nums_jm1, nums_j respectively represent nums1[i-1], nums1[i], nums2[j-1], nums2[j] //When an array does not appear in the previous part, the corresponding value is negative infinity, which will not affect the maximum value of the previous part. int nums_im1 = (i == 0 ? Integer.MIN_VALUE : nums1[i - 1]); //When an array does not appear in the latter part, the corresponding value is positive infinity, which will not affect the minimum value of the latter part. int nums_i = (i == m ? Integer.MAX_VALUE : nums1[i]); int nums_jm1 = (j == 0 ? Integer.MIN_VALUE : nums2[j - 1]); int nums_j = (j == n ? Integer.MAX_VALUE : nums2[j]); if (nums_im1 <= nums_j) { ansi = i; median1 = Math.max(nums_im1, nums_jm1); median2 = Math.min(nums_i, nums_j); left = i 1; } else { right = i - 1; } } return (m n) % 2 == 0 ? (median1 median2) / 2.0 : median1; } }
7. Complexity
Time complexity: O(log min(m,n)), where m and n are the lengths of arrays nums1 and nums2 respectively Space complexity: O(1)
6.Tree
0.frequency
104,226,96,617,173,1130,108,297,100,105,95,124,654,669,99,979,199,110,236,101,235,114,94,222,102,938,437
1. Binary tree
0. Traverse and organize common templates
144. Preorder traversal of binary tree
1. Iteration
1. Thoughts
Similar to the iteration of in-order traversal, with slight changes
2.Code
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); //Stack is implemented based on dynamic arrays, while LinkedList is implemented based on two-way linked lists. LinkedList is more efficient in adding, deleting, and modifying than Stack Deque<TreeNode> stk = new LinkedList<TreeNode>();//Double-ended queue //The node is not empty or the stack is not empty and the loop continues while (root != null || !stk.isEmpty()) { while (root != null) { res.add(root.val);//The root node is directly accessed and then pushed onto the stack stk.push(root); root = root.left;//After the root node ends, access its left child and become the new root node } root = stk.pop();//When jumping out of the loop, all left children have been accessed, and then the right child is accessed root = root.right; } return res; } }
2.1 Python
class Solution: def preorderTraversal(self, root: TreeNode) -> List[int]: res = list() if not root: return res stack = [] node=root while stack or node:#The node is not empty or the stack is not empty and the loop continues while node: res.append(node.val) #The root node is directly accessed and then pushed onto the stack stack.append(node) node = node.left #After the root node ends, access its left child and become the new root node node = stack.pop() #When jumping out of the loop, all left children have been accessed, and then the right child is accessed node = node.right return res
3.Another method
Starting from the root node, each iteration pops the top element of the current stack and pushes its child node into the stack, first pressing the right child and then the left child.
class Solution { public List<Integer> preorderTraversal(TreeNode root) { LinkedList<TreeNode> stack = new LinkedList<>(); LinkedList<Integer> output = new LinkedList<>(); if (root == null) { return output; } stack.add(root); while (!stack.isEmpty()) { TreeNode node = stack.pollLast();//Retrieve and remove the last element of this list output.add(node.val); if (node.right != null) {//Only push one right and left child of the current node onto the stack, regardless of the others stack.add(node.right); } if (node.left != null) { stack.add(node.left); } } return output; } }
4. Complexity
Time: O(n), Space: O(n)
2.Morris Traversal
1. Thoughts
1. Access the predecessor nodes of the pre-order traversal from the current node downwards, and each predecessor node is visited exactly twice.
2. First start from the current node, walk one step to the left child and then visit all the way down along the right child until reaching a leaf node (the in-order traversal predecessor node of the current node), so we update the output and create a pseudo edge predecessor. right = root updates the next point of this predecessor. If we visit the predecessor node for the second time, since it already points to the current node, we remove the pseudo edge and move to the next vertex.
3. If the movement to the left in the first step does not exist, directly update the output and move to the right
2.Code
class Solution { public List<Integer> preorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); TreeNode predecessor = null;//The predecessor node of the current node, the rightmost node of the left subtree while (root != null) { //According to whether there is a left child, determine whether to find its predecessor node or directly access the right child if (root.left != null) { //Find the predecessor first, then assign a value to its right child, and then traverse the left child of the current node //The predecessor node is the current root node, take one step to the left, and then keep walking to the right until it can no longer go. predecessor = root.left; while (predecessor.right != null && predecessor.right != root) { predecessor = predecessor.right; } //According to whether the right child of the predecessor is empty, determine whether to directly access the left subtree and then assign the value, or whether the left subtree access is completed. //Let predecessor's right pointer point to root and continue traversing the left subtree if (predecessor.right == null) { res.add(root.val); predecessor.right = root; root = root.left; } //Indicates that the left subtree has been visited. We need to disconnect the link and continue to visit the right subtree. else { predecessor.right = null; root = root.right; } } // If there is no left child, access the right child directly else { res.add(root.val); root = root.right; } } return res; } }
2.1 Python
class Solution: def preorderTraversal(self, root: TreeNode) -> List[int]: res = list() if not root: return res p1=root while p1: p2 = p1.left #The predecessor node of the current node, the rightmost node of the left subtree if p2: #According to whether there is a left child, determine whether to find its predecessor node or directly access the right child while p2.right and p2.right != p1: p2 = p2.right #According to whether the right child of p2 is empty, determine whether to directly access the left subtree and then assign the value, or whether the left subtree access is completed. if not p2.right:#Let the right pointer of p2 point to p1 and continue traversing the left subtree res.append(p1.val) p2.right = p1 p1 = p1.left continue else: #Indicates that the left subtree has been visited, we need to disconnect the link and continue to visit the right subtree p2.right = None else: #If there is no left child, access the right child directly res.append(p1.val) p1 = p1.right return res
3. Complexity
Time: O(n), Space: O(1)
94. In-order traversal of binary tree
1. Recursion
1. Thoughts
This tree is traversed by visiting the left subtree - root node - right subtree, and when visiting the left subtree or right subtree, we traverse in the same way until the complete tree is traversed. Therefore, the entire traversal process is naturally recursive. We can directly use recursive functions to simulate this process.
2.Code
class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); inorder(root, res); return res; } public void inorder(TreeNode root, List<Integer> res) { if (root == null) { return; } inorder(root.left, res); res.add(root.val); inorder(root.right, res); } }
2.1 Python
class Solution: def preorderTraversal(self, root: TreeNode) -> List[int]: def dfs(cur): if not cur: return # Preorder recursion res.append(cur.val) dfs(cur.left) dfs(cur.right) # # In-order recursion #dfs(cur.left) # res.append(cur.val) #dfs(cur.right) # # Postorder recursion #dfs(cur.left) #dfs(cur.right) # res.append(cur.val) res = [] dfs(root) return res
3. Complexity
Time complexity: O(n), where n is the number of binary tree nodes. In binary tree traversal, each node will be visited once and only once.
Space complexity: O(n). The space complexity depends on the stack depth of the recursion, and the stack depth will reach the O(n) level when the binary tree is a chain.
2.Stack iteration
1. Ideas
We can also implement the recursive function of method 1 in an iterative way. The two methods are equivalent. The difference is that a stack is implicitly maintained during recursion, and we need to explicitly simulate this stack during iteration.
2.Code
class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); //Stack is implemented based on dynamic arrays, while LinkedList is implemented based on two-way linked lists. LinkedList is more efficient in adding, deleting, and modifying than Stack Deque<TreeNode> stk = new LinkedList<TreeNode>();//Double-ended queue //The node is not empty or the stack is not empty and the loop continues while (root != null || !stk.isEmpty()) { //Traverse the left child first until the left child is empty and end the loop while (root != null) { stk.push(root); root = root.left; } //When jumping out of the loop, the left child is empty. At this time, the one popped out of the stack is equivalent to the root node, and then it is the right child's turn. root = stk.pop(); res.add(root.val); root = root.right; } return res; } }
2.1 Python
class Solution: def inorderTraversal(self, root: TreeNode) -> List[int]: res = list() if not root: return res stack = [] node=root while stack or node:#The node is not empty or the stack is not empty and the loop continues while node: stack.append(node) #Traverse the left child first until the left child is empty and end the loop node = node.left node = stack.pop() #The left child is empty when jumping out of the loop. At this time, the one popped out of the stack is equivalent to the root node, and then it is the right child's turn. res.append(node.val) node = node.right return res
3. Complexity
Time: O(n), Space: O(n)
3.Morris in-order traversal
1. Thoughts
1. If x has no left child, first add the value of x to the answer array, and then access the right child of x, that is, x=x.right
2. If x has a left child, find the rightmost node on the left subtree of x (that is, the last node of the left subtree in the in-order traversal, and the predecessor node of x in the in-order traversal), which we record as predecessor. Depending on whether the right child of the predecessor is empty, perform the following operations
3. If the right child of the predecessor is empty, point its right child to x, and then access the left child of x, that is, x=x.left
4. If the right child of the predecessor is not empty, then its right child points to x at this time, indicating that we have traversed the left subtree of x. We leave the right child of the predecessor empty, add the value of x to the answer array, and then access The right child of x, that is, x=x.right
5. Repeat the above operations until the complete tree is visited
6. In fact, we will do one more step in the whole process: assuming that the currently traversed node is x, point the right child of the rightmost node in the left subtree of x to x, so that after the left subtree traversal is completed, we will use this pointer to Returns
2.Code
class Solution { public List<Integer> inorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); TreeNode predecessor = null;//The predecessor node of the current node, the rightmost node of the left subtree while (root != null) { //According to whether there is a left child, determine whether to find its predecessor node or directly access the right child if (root.left != null) { //Find the predecessor first, then assign a value to its right child, and then traverse the left child of the current node //The predecessor node is the current root node, take one step to the left, and then keep walking to the right until it can no longer go. predecessor = root.left; while (predecessor.right != null && predecessor.right != root) { predecessor = predecessor.right; } //Determine whether assignment or left subtree access ends based on whether the predecessor's right child is empty. //Let predecessor's right pointer point to root and continue traversing the left subtree if (predecessor.right == null) { predecessor.right = root; root = root.left; } //Indicates that the left subtree has been visited. At this time, visit the root node, then disconnect the link, and continue to visit the right subtree. else { res.add(root.val); predecessor.right = null; root = root.right; } } // If there is no left child, access the right child directly else { res.add(root.val); root = root.right; } } return res; } }
2.1 Python
class Solution: def inorderTraversal(self, root: TreeNode) -> List[int]: res = list() if not root: return res p1=root while p1: p2 = p1.left #The predecessor node of the current node, the rightmost node of the left subtree if p2: #According to whether there is a left child, determine whether to find its predecessor node or directly access the right child while p2.right and p2.right != p1: p2 = p2.right #According to whether the right child of p2 is empty, determine whether to directly access the left subtree and then assign the value, or whether the left subtree access is completed. if not p2.right:#Let the right pointer of p2 point to p1 and continue traversing the left subtree p2.right = p1 p1 = p1.left continue else: #Indicates that the left subtree has been visited. At this time, visit the root node, then disconnect the link, and continue to visit the right subtree. res.append(p1.val) p2.right = None else: #If there is no left child, access the right child directly res.append(p1.val) p1 = p1.right return res
3. Complexity
Time complexity: O(n), where n is the number of nodes in the binary search tree. Each node in Morris traversal will be visited twice, so the total time complexity is O(2n)=O(n)
Space complexity: O(1)
4. Color marking method (universal)
1. Thoughts
0. It has the efficiency of the stack iteration method and is as concise and easy to understand as the recursive method. More importantly, this method can write completely consistent code for pre-order, mid-order, and post-order traversal.
1. Use colors to mark the status of nodes. New nodes are white and visited nodes are gray. If the node encountered is white, mark it as gray, and then push its right child node, itself, and left child node onto the stack in sequence. If the node encountered is gray, the value of the node is output
2. If you want to implement pre-order and post-order traversal, you only need to adjust the stacking order of the left and right child nodes.
2.Code
class Solution: def inorderTraversal(self, root: TreeNode) -> List[int]: WHITE, GRAY = 0, 1 #WHITE has not been visited, GRAY has visited res = [] stack = [(WHITE, root)] while stack: color, node = stack.pop() if node is None: continue if color == WHITE: #The order of mid-order is left, root, right, and the order of pushing into the stack is right, root, left. Adjust the order to complete other traversals. stack.append((WHITE, node.right)) stack.append((GRAY, node)) #The current node turns gray and has been visited stack.append((WHITE, node.left)) else: res.append(node.val) return res
3. Complexity
Time: O(n), Space: O(n)
145.Post-order traversal of binary tree
1. Iteration
1. Ideas
When returning to the root node, mark the current node to determine whether to return from the left subtree or the right subtree. When returning from the right subtree, start traversing the node
2.Code
class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); Stack<TreeNode> stack = new Stack<>(); //The pre node is used to record the previously visited node, distinguishing between returning the root node from the left subtree or the right subtree. TreeNode pre = null; while(root!=null || !stack.empty()){ while(root!=null){ stack.push(root); //Continuously push the left node onto the stack root = root.left; } root = stack.peek();//Return from the left subtree, only get the root node and not pop it up, it has not been visited yet if(root.right==null || root.right==pre){ //If the right node is empty or the right node has been visited res.add(root.val); //You can access the root node at this time pre = root; stack.pop(); root = null; //At this time, do not push the left subtree onto the stack in the next cycle. It has already been pushed, and directly determine the top element of the stack. }else{ root = root.right; //Don't pop it off the stack yet, push its right node onto the stack } } return res; } }
2.1 Python
class Solution: def postorderTraversal(self, root: TreeNode) -> List[int]: if not root: return list() res = list() stack = list() prev = None #The prev node is used to record the previously visited node, which distinguishes whether to return the root node from the left subtree or from the right subtree. while root or stack: while root: stack.append(root) #Continuously push the left node onto the stack root = root.left #This application uses peek, but there is no such method in the list. You can only pop first and then append. root = stack.pop() #Return from the left subtree, it has not been visited yet, and it will be added to the stack later. if not root.right or root.right == prev:#If the right node is empty or the right node has been visited res.append(root.val) #You can access the root node at this time prev = root #Record the current node root = None #At this time, do not push the left subtree onto the stack in the next cycle. It has already been pushed, and directly determines the top element of the stack. else: stack.append(root) #If it has not been accessed before, then add it to the stack root = root.right return res
3. Transform preorder traversal
//Modify the code for writing nodes into the result linked list in the preorder traversal code: change the insertion into the end of the queue to the insertion into the head of the queue //Modify the logic of checking the left node first and then the right node in the preorder traversal code: change to check the right node first and then the left node. //The pre-order traversal is the left and right roots, the reverse order is the right-left root, and the post-order traversal is the left and right roots. class Solution { public List<Integer> postorderTraversal(TreeNode root) { LinkedList res = new LinkedList(); Stack<TreeNode> stack = new Stack<>(); TreeNode pre = null; while(root!=null || !stack.empty()){ while(root!=null){ res.addFirst(root.val); //Insert the first of the queue stack.push(root); root = root.right; //First right then left } root = stack.pop(); root = root.left; } return res; } }
4. Complexity
Time: O(n), Space: O(n)
2.Morris Traversal
1. Ideas
1. The core idea of Morris traversal is to use a large number of free pointers in the tree to achieve the ultimate reduction in space overhead. The subsequent rules for postorder traversal are summarized as follows:
2. If the left child node of the current node is empty, traverse the right child node of the current node.
3. If the left child node of the current node is not empty, find the predecessor node of the current node under in-order traversal in the left subtree of the current node.
4. If the right child node of the predecessor node is empty, set the right child node of the predecessor node to the current node, and update the current node to the left child node of the current node.
5. If the right child node of the predecessor node is the current node, reset its right child node to empty. Output all nodes on the path from the left child node of the current node to the predecessor node in reverse order. The current node is updated to the right child node of the current node
2.Code
class Solution { public List<Integer> postorderTraversal(TreeNode root) { List<Integer> res = new ArrayList<Integer>(); if (root == null) { return res; } TreeNode p1 = root, p2 = null; //p1 current node, p2 predecessor node while (p1 != null) { p2 = p1.left; if (p2 != null) { while (p2.right != null && p2.right != p1) { p2 = p2.right; } if (p2.right == null) { p2.right = p1; p1 = p1.left; continue; } else { p2.right = null; addPath(res, p1.left); } } p1 = p1.right; } addPath(res, root); return res; } public void addPath(List<Integer> res, TreeNode node) { int count = 0; while (node != null) { count; res.add(node.val); node = node.right; } //Reverse the newly added nodes int left = res.size() - count, right = res.size() - 1; while (left < right) { int temp = res.get(left); res.set(left, res.get(right)); res.set(right, temp); left ; right--; } } }
2.1 Python
class Solution: def postorderTraversal(self, root: TreeNode) -> List[int]: def addPath(node: TreeNode): count = 0 while node: count = 1 res.append(node.val) node = node.right #Reverse the newly added nodes i, j = len(res) - count, len(res) - 1 while i < j: res[i], res[j] = res[j], res[i] #No intermediate variables required i = 1 j -= 1 if not root: return list() res = list() p1=root while p1: p2 = p1.left #p2 predecessor node if p2: while p2.right and p2.right != p1: p2 = p2.right if not p2.right: p2.right = p1 p1 = p1.left continue else: p2.right = None addPath(p1.left) p1 = p1.right addPath(root) return res
3. Complexity
Time: O(n), Space: O(1)
102.Level-order traversal of binary tree
0.Title
Returns the node values of the level-order traversal of the binary tree, and the nodes at each level are placed together.
1. Ideas
1. The simplest method is to use a tuple (node, level) to represent the state, which represents a node and the level it is in. The level value of each newly entered node is the level value of the parent node. plus one. Finally, the points are classified according to the level of each point. When classifying, we can use a hash table to maintain an array with level as the key and the corresponding node value as the value. After the breadth-first search is completed, press the key level to retrieve all Value, just form the answer and return it
2. Consider how to optimize space overhead: how to implement this function without using hash mapping and using only one variable node to represent the state.
3. Modify BFS in a clever way: first, the root element is added to the queue. When the queue is not empty, find the length Si of the current queue, then take Si elements from the queue to expand, and then enter the next iteration.
4. The difference between it and BFS is that BFS only takes one element at a time to expand, while here it takes Si elements each time. In the i-th iteration of the above process, Si elements of the i-th layer of the binary tree are obtained.
2.Code
class Solution { public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> ret = new ArrayList<List<Integer>>(); if (root == null) { return ret; } Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.offer(root);//Add the specified element to the end of this list (the last element) while (!queue.isEmpty()) { List<Integer> level = new ArrayList<Integer>(); int currentLevelSize = queue.size(); //Dequeue all elements in the current queue for (int i = 1; i <= currentLevelSize; i) { TreeNode node = queue.poll(); level.add(node.val); if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } } ret.add(level); } return ret; } }
2.1 Python
class Solution: def levelOrder(self, root: TreeNode) -> List[List[int]]: if not root: return [] # In special cases, return directly if root is empty from collections import deque #The following is the content of the BFS template. The key to BFS is the use of queues. layer = deque() layer.append(root) # Push the initial node res = [] # Result set while layer: cur_layer = [] # Temporary variable, records the nodes of the current layer for _ in range(len(layer)): # Traverse the nodes of a certain layer node = layer.popleft() # Pop the node to be processed cur_layer.append(node.val) if node.left: # If the current node has left and right nodes, push them into the queue. According to the meaning of the question, pay attention to the left and then the right nodes. layer.append(node.left) if node.right: layer.append(node.right) res.append(cur_layer) # After all nodes of a certain layer are processed, push the results of the current layer into the result set return res
Friends who use other languages, to be on the safe side, the value of len(layer) should be saved before traversing, because elements are also being added to the layer while traversing, and the value of the layer will change at this time. Maybe Python is a special language. , indeed when for _ in range(len(layer)) is called, the number of loops has been determined based on the generated corresponding sequence, and the change in the length of the layer in the loop will not have an impact if a while loop is used. len(layer) will be refreshed every time
3. Complexity
Time complexity: Each point enters and exits the queue once, so the asymptotic time complexity is O(n)
Space complexity: The number of elements in the queue does not exceed n, so the asymptotic space complexity is O(n)
104.The maximum depth of a binary tree
1. Recursion
1. Ideas
1. If we know the maximum depth l and r of the left subtree and the right subtree, then the maximum depth of the binary tree is max(l,r) 1
2. The maximum depth of the left subtree and right subtree can be calculated in the same way. Therefore, when we calculate the maximum depth of the current binary tree, we can first recursively calculate the maximum depth of its left subtree and right subtree, and then calculate the maximum depth of the current binary tree in O(1) time. Recursion exits when an empty node is reached
2.Code
class Solution { public int maxDepth(TreeNode root) { if(root==null) return 0;//When the last 1 is a leaf node, the height will be returned to 1 return Math.max(maxDepth(root.left),maxDepth(root.right)) 1; } }
2.1 Python
class Solution: def maxDepth(self, root: TreeNode) -> int: if not root: return 0 lefth = self.maxDepth(root.left) #You must use self.self when calling yourself righth = self.maxDepth(root.right) return max(lefth,righth) 1
3. Complexity
Time complexity: O(n), where n is the number of binary tree nodes. Each node is traversed only once in the recursion
Space complexity: O(height), where height represents the height of the binary tree. Recursive functions require stack space, and the stack space depends on the depth of the recursion, so the space complexity is equivalent to the height of the binary tree
2. Breadth-first search
1. Ideas
The queue of breadth-first search stores "all nodes of the current layer". Every time we expand the next layer, unlike breadth-first search which only takes out one node from the queue at a time, we need to take out all the nodes in the queue for expansion, so as to ensure that the queue is expanded every time Stored in are all the nodes of the current layer, that is, we expand layer by layer. Finally, we use a variable ans to maintain the number of expansions. The maximum depth of the binary tree is ans
2. Algorithm
class Solution { public int maxDepth(TreeNode root) { if (root == null) { return 0; } Queue<TreeNode> queue = new LinkedList<TreeNode>(); queue.offer(root); int ans = 0; while (!queue.isEmpty()) { int size = queue.size(); while (size > 0) { //operate one layer of elements at a time TreeNode node = queue.poll(); if (node.left != null) { queue.offer(node.left); } if (node.right != null) { queue.offer(node.right); } size--; } ans ;//After one layer of operation is completed, the number of layers is 1 } return ans; } }
2.1 Python
import collections class Solution: def maxDepth(self, root: TreeNode) -> int: if not root: return 0 queue = collections.deque() queue.append(root) ans = 0 while queue: ans = 1 for _ in range(len(queue)): #Traverse the elements of one level each time node = queue.popleft() if node.left: queue.append(node.left) if node.right: queue.append(node.right) return ans
3. Complexity
Time complexity: O(n), where n is the number of nodes in the binary tree. The same analysis as method 1, each node will only be visited once
Space complexity: The space consumption of this method depends on the number of elements stored in the queue, which will reach O(n) in the worst case
226.Reverse a binary tree
1. Recursion
1. Thoughts
We start from the root node, recursively traverse the tree, and start flipping from the leaf nodes first. If the left and right subtrees of the currently traversed node root have been flipped, then we only need to exchange the positions of the two subtrees to complete the flipping of the entire subtree with root as the root node.
2.Code
class Solution { public TreeNode invertTree(TreeNode root) { if (root == null) { return null; } //Traverse the tree recursively and start flipping from the leaf nodes first TreeNode left = invertTree(root.left); TreeNode right = invertTree(root.right); root.left = right; root.right = left; return root; } }
2.1 python
class Solution: def invertTree(self, root: TreeNode) -> TreeNode: if not root: return root #Traverse the tree recursively and start flipping from the leaf nodes first left = self.invertTree(root.left) right = self.invertTree(root.right) root.left, root.right = right, left return root
3. Complexity
Time complexity: O(N), where N is the number of binary tree nodes. We will traverse each node in the binary tree, and for each node, we exchange its two subtrees in constant time
Space complexity: O(N). The space used is determined by the depth of the recursion stack, which is equal to the height of the current node in the binary tree. Under average circumstances, the height of a binary tree has a logarithmic relationship with the number of nodes, which is O(logN). In the worst case, the tree forms a chain, and the space complexity is O(N)
96. Number of different binary search trees
1. Dynamic programming
0.Title
Given an integer n, how many binary search trees are there with 1...n as nodes?
1. Ideas
1. Traverse each number i, use the number as the root of the tree, use the 1⋯(i−1) sequence as the left subtree, and use the (i 1)⋯n sequence as the right subtree. Then we can recursively construct the left subtree and right subtree in the same way
2. Since the root values are different, we can ensure that each binary search tree is unique
3. The original problem can be decomposed into two smaller sub-problems, and the solutions to the sub-problems can be reused. Dynamic programming can be used to solve this problem
4. We can define two functions: G(n): The number of different binary search trees that can be formed by a sequence of length n. F(i,n): The number of different binary search trees with i as the root and sequence length n (1≤i≤n). It can be seen that G(n) is the function we need to solve
5. The total number of different binary search trees G(n) is the sum of F(i,n) that traverses all i(1≤i≤n)
6. For boundary cases, when the sequence length is 1 (only the root) or 0 (empty tree), there is only one situation, namely: G(0)=1, G(1)=1
7. Select the number i as the root, then the set of all binary search trees with the root i is the Cartesian product of the left subtree set and the right subtree set
8. Combine the two formulas:
2.Code
class Solution { public int numTrees(int n) { int[] G = new int[n 1]; G[0] = 1; G[1] = 1; for (int i = 2; i <= n; i) { for (int j = 1; j <= i; j) { G[i] = G[j - 1] * G[i - j]; } } return G[n]; } }
2.1 Python
class Solution: def numTrees(self, n): G = [0]*(n 1) #List of length n 1 G[0], G[1] = 1, 1 for i in range(2, n 1): #Traverse from G[2] to n for j in range(1, i 1): #Corresponding summation formula from 1 to n G[i] = G[j-1] * G[i-j] return G[n]
3. Complexity
Time complexity: O(n^2), where n represents the number of nodes in the binary search tree. The G(n) function has a total of n values that need to be solved. Each solution requires O(n) time complexity, so the total time complexity is O(n^2)
Space complexity: O(n). We need O(n) space to store G array
2. Catalan number
1. Ideas
1. The value of the G(n) function derived in method 1 is mathematically called the Catalan number Cn
2.Code
class Solution { public int numTrees(int n) { // Tip: We need to use the long type here to prevent overflow during calculation. long C = 1; for (int i = 0; i < n; i) { C = C * 2 * (2 * i 1) / (i 2); } return (int) C; } }
2.1 Python
class Solution(object): def numTrees(self, n): C=1 for i in range(0, n): C = C * 2*(2*i 1)/(i 2) return int(C)
3. Complexity
Time complexity: O(n), where n represents the number of nodes in the binary search tree. We only need to loop through it once
Space complexity: O(1). We only need constant space to store several variables
95. Different combinations of binary search trees
1. Recursion
0.Title
Given an integer n, generate all binary search trees consisting of nodes 1...n
1. Ideas
1. Assume that the current sequence length is n, and if we enumerate the value of the root node as i, then according to the properties of the binary search tree, we can know that the set of node values of the left subtree is [1...i−1], and the set of node values of the right subtree is [1...i−1]. The set of node values of the tree is [i 1…n]. Compared with the original problem, the generation of left subtree and right subtree is a sub-problem with reduced sequence length, so we can think of using recursive methods to solve this problem.
2. Define the generateTrees(start, end) function to indicate that the current value set is [start, end] and return all feasible binary search trees generated by the sequence [start, end]. According to the above idea, we consider that the value ii in the enumeration [start, end] is the root of the current binary search tree, then the sequence is divided into two parts: [start, i−1] and [i 1, end]. We call these two parts recursively, namely generateTrees(start, i - 1) and generateTrees(i 1, end), to obtain all feasible left subtrees and feasible right subtrees. Then in the last step, we only need to collect the feasible left subtrees from the feasible left subtrees Select one, then select one from the feasible right subtree set and splice it to the root node, and put the generated binary search tree into the answer array.
3. The entry point of recursion is generateTrees(1, n), and the exit point is when \textit{start}>\textit{end}start>end, the current binary search tree is empty, just return an empty node.
2.Code
class Solution: def generateTrees(self, n: int) -> List[TreeNode]: def generateTrees(start, end): if start > end: return [None,] allTrees = [] for i in range(start, end 1): # Enumerate feasible root nodes #First complete the recursive construction of the left and right subtrees, and then use the completed set of left and right subtrees to build a complete tree leftTrees = generateTrees(start, i - 1) # Get the set of all feasible left subtrees rightTrees = generateTrees(i 1, end) # Get the set of all feasible right subtrees # Select a left subtree from the left subtree set, select a right subtree from the right subtree set, and splice them to the root node for l in leftTrees: #Relationship of Descartes product for r in rightTrees: currTree = TreeNode(i) #Root node currTree.left = l currTree.right = r allTrees.append(currTree) return allTrees return generateTrees(1, n) if n else []
3. Complexity
Time complexity: The time complexity of the entire algorithm depends on the "number of feasible binary search trees", and the number of binary search trees generated for n points is equivalent to the nth "Cattleya number" in mathematics, using G_n means. Readers are asked to check the specific details of Cattleya number by themselves. I will not go into details here and only give the conclusion. Generating a binary search tree requires O(n) time complexity. There are G_n binary search trees in total, which is O(nG_n)
Space complexity: There are Gn binary search trees generated by n points, each tree has n nodes, so the storage space requires O(nG_n)
617.Merge binary trees
1. Depth-first search
0.Title
Given two binary trees, imagine that when you overlay one of them onto the other, some of the nodes of the two binary trees overlap. You need to merge them into a new binary tree. The rule of merging is that if two nodes overlap, then their values are added as the new value after the node is merged. Otherwise, the node that is not NULL will be directly used as the node of the new binary tree.
1. Ideas
1. Traverse two binary trees simultaneously starting from the root node, and merge the corresponding nodes. The corresponding nodes of the two binary trees may have the following three situations, and use different merging methods for each situation.
2. If the corresponding nodes of the two binary trees are empty, the corresponding nodes of the merged binary tree will also be empty.
3. If only one of the corresponding nodes of the two binary trees is empty, the corresponding node of the merged binary tree will be the non-empty node.
4. If the corresponding nodes of the two binary trees are not empty, the value of the corresponding node of the merged binary tree is the sum of the values of the corresponding nodes of the two binary trees. At this time, the two nodes need to be merged explicitly.
5. After merging a node, the left and right subtrees of the node must be merged respectively. This is a recursive process
2.Code
class Solution { public TreeNode mergeTrees(TreeNode t1, TreeNode t2) { if (t1 == null) { return t2; } if (t2 == null) { return t1; } //First merge the current node, and then recurse its left and right subtrees TreeNode merged = new TreeNode(t1.val t2.val); merged.left = mergeTrees(t1.left, t2.left); merged.right = mergeTrees(t1.right, t2.right); return merged; } }
2.1 Python
class Solution: def mergeTrees(self, t1: TreeNode, t2: TreeNode) -> TreeNode: if not t1: return t2 if not t2: return t1 #First merge the current node, and then recurse its left and right subtrees merged = TreeNode(t1.val t2.val) merged.left = self.mergeTrees(t1.left, t2.left) merged.right = self.mergeTrees(t1.right, t2.right) return merged
3. Complexity
Time complexity: O(min(m,n)), where m and n are the number of nodes of the two binary trees respectively. Depth-first search is performed on two binary trees at the same time. Only when the corresponding nodes in the two binary trees are not empty, the node will be explicitly merged. Therefore, the number of nodes visited will not exceed the number of the smaller binary tree. Number of nodes
Space complexity: O(min(m,n)), where m and n are the number of nodes of the two binary trees respectively. The space complexity depends on the number of levels of recursive calls. The number of levels of recursive calls will not exceed the maximum height of the smaller binary tree. In the worst case, the height of the binary tree is equal to the number of nodes.
173.Binary search tree iterator
1. Flatten binary search tree
0.Title
Implement a binary search tree iterator. You will initialize the iterator using the root node of the binary search tree. Calling next() will return the next smallest number in the binary search tree
1. Ideas
1. The simplest way to implement an iterator is on an array-like container interface. If we have an array, we only need a pointer or index to easily implement the functions next() and hasNext()
2. Use an additional array and expand the binary search tree to store it. We want the elements of the array to be sorted in ascending order, then we should perform an in-order traversal of the binary search tree, and then we build an iterator function in the array
3. Once all nodes are in the array, we only need a pointer or index to implement the next() and hasNext functions. Whenever hasNext() is called, we just need to check if the index reaches the end of the array. Whenever next() is called, we simply return the element pointed to by the index and move forward one step to simulate the progress of the iterator
2. Algorithm
class BSTIterator { ArrayList<Integer> nodesSorted; int index; public BSTIterator(TreeNode root) { this.nodesSorted = new ArrayList<Integer>(); this.index = -1; this._inorder(root); } private void _inorder(TreeNode root) { if (root == null) return; this._inorder(root.left); this.nodesSorted.add(root.val); this._inorder(root.right); } public int next() { return this.nodesSorted.get( this.index); } public boolean hasNext() { return this.index 1 < this.nodesSorted.size(); } }
2.1 Python
class BSTIterator: self.nodes_sorted = [] self.index = -1 self._inorder(root) def __init__(self, root: TreeNode): self.nodes_sorted = [] self.index = -1 self._inorder(root) def _inorder(self, root): if not root: return self._inorder(root.left) self.nodes_sorted.append(root.val) self._inorder(root.right) def next(self) -> int: self.index = 1 return self.nodes_sorted[self.index] def hasNext(self) -> bool: return self.index 1 < len(self.nodes_sorted)
3. Complexity
Time complexity: The time spent constructing the iterator is O(N). The problem statement only requires us to analyze the complexity of the two functions, but when implementing the class, we must also pay attention to the time required to initialize the class object; in this case Below, the time complexity is linearly related to the number of nodes in the binary search tree: next(): O(1), hasNext(): O(1)
Space complexity: O(N), since we created an array to contain all node values in the binary search tree, which does not meet the requirements in the problem statement, the maximum space complexity of any function should be O(h) , where h refers to the height of the tree. For a balanced binary search tree, the height is usually logN
2. Controlled recursion
1. Ideas
1. The method used previously has a linear relationship in space with the number of nodes in the binary search tree. However, the reason we have to use this approach is that we can iterate over the array, and we cannot pause the recursion and then start it at some point. However, if we can simulate controlled recursion with inorder traversal, we don't actually need to use any other space than the space on the stack used to simulate the recursion
2. Use an iterative method to simulate in-order traversal instead of a recursive method; in the process of doing this, we can easily implement the calls of these two functions instead of using other extra space
3. Initialize an empty stack S, used to simulate in-order traversal of a binary search tree. For in-order traversal we use the same method as before, except that we now use our own stack instead of the system's stack. Since we use a custom data structure, the recursion can be paused and resumed at any time
4. A helper function must also be implemented, which will be called again and again during the implementation. This function is called _inorder_left, which adds all left children of a given node to the stack until the node has no left children.
5. When the next() function is called for the first time, the minimum element of the binary search tree must be returned, and then we simulate the recursion and must move forward one step, that is, move to the next minimum element of the binary search tree. The top of the stack always contains the element returned by the next() function. hasNext() is easy to implement since we only need to check if the stack is empty
6. First, given the root node of the binary search tree, we call the function _inorder_left, which ensures that the top of the stack always contains the element returned by the next() function
7. Suppose we call next(), we need to return the next smallest element in the binary search tree, which is the top element of the stack. There are two possibilities: One is that the node at the top of the stack is a leaf node. This is the best case scenario because we don't have to do anything but pop the node off the stack and return its value. So this is a constant time operation. Another case is when the node at the top of the stack owns the right node. We don't need to check the left node because it has already been added to the stack. The top element of the stack either has no left node or the left node has been processed. If there is a right node at the top of the stack, then we need to call the helper function on the right node. The time complexity depends on the structure of the tree
2. Algorithm
class BSTIterator { Stack<TreeNode> stack; public BSTIterator(TreeNode root) { this.stack = new Stack<TreeNode>();//Use stack to simulate recursion this._leftmostInorder(root);//The algorithm starts with the call of the helper function } //Helper function, add all left child nodes to the stack private void _leftmostInorder(TreeNode root) { while (root != null) { this.stack.push(root); root = root.left; } } public int next() { TreeNode topmostNode = this.stack.pop();//The top element of the stack is the smallest element //Keep the top element of the stack as the smallest element. If the node has a right child, call the help function if (topmostNode.right != null) { this._leftmostInorder(topmostNode.right); } return topmostNode.val; } public boolean hasNext() { return this.stack.size() > 0; } }
2.1 Python
class BSTIterator: def __init__(self, root: TreeNode): self.stack = [] #Use stack to simulate recursion self._leftmost_inorder(root) #The algorithm starts with the call of the helper function #Help function, add all left child nodes to the stack def _leftmost_inorder(self, root): while root: self.stack.append(root) root = root.left def next(self) -> int: topmost_node = self.stack.pop() #The top element of the stack is the smallest element #Keep the top element of the stack as the smallest element. If the node has a right child, call the helper function if topmost_node.right: self._leftmost_inorder(topmost_node.right) return topmost_node.val def hasNext(self) -> bool: return len(self.stack) > 0
3. Complexity
Time complexity: hasNext(): If there are still elements in the stack, it returns true, otherwise it returns false. So this is an O(1) operation. next(): Contains two main steps. One is to pop an element from the stack, which is the next smallest element. This is an O(1) operation. However, then we have to call the helper function _inorder_left, which needs to be recursive and adds the left node to the stack, which is a linear time operation, O(N) in the worst case. But we only call it on the node containing the right node, and it will not always process N nodes. Only if we have a skewed tree, there will be N nodes. Therefore, the average time complexity of this operation is still O(1), which is in line with the requirements of the problem.
Space complexity: O(h), using a stack to simulate recursion
1130. Minimum cost spanning tree of leaf values ×
1. Dynamic programming
0.Title
Given an array of positive integers arr, consider all binary trees that satisfy the following conditions: Each node has 0 or 2 child nodes. The values in the array arr correspond one-to-one to the value of each leaf node in the inorder traversal of the tree. The value of each non-leaf node is equal to the product of the maximum value of the leaf node in its left subtree and right subtree. In all such binary trees, return the smallest possible sum of the values of each non-leaf node
1. Ideas
The core of this question is: You must know that in-order traversal determines that all the left elements (including itself) of the k-th element in the arr array (0...n-1) are in the left subtree, and its right elements are all in the right subtree. , and at this time, the product of the maximum values selected from the left and right subtrees is the root at this time, which is the non-leaf node mentioned in the question, so we can assume that from i to j, the minimum sum may be: k at this moment The product sub-problem of the maximum value of the elements on the left and right sides of k is the minimum value of the sub-problem k on the left (i, k), the minimum value of the k-digit right side (k 1, j), That is: dp[i][j]=min(dp[i][j], dp[i][k] dp[k 1][j] max[i][k]*max[k 1][j ]) This question is the same as leetcode1039
2.Code
class Solution { public int mctFromLeafValues(int[] arr) { int n = arr.length; //Find the maximum value of the elements in arr from i to j, and store it in max[i][j]. i and j can be equal. int[][] max = new int[n][n]; for (int j=0;j<n;j) { int maxValue = arr[j]; for (int i=j;i>=0;i--) { maxValue = Math.max(maxValue, arr[i]); max[i][j] = maxValue; } } int[][] dp = new int[n][n]; for (int j=0; j<n; j ) { for (int i=j; i>=0; i--) { //k is a value between i and j, i<=k<j int min = Integer.MAX_VALUE; for (int k=i; k 1<=j; k ) { min = Math.min(min,dp[i][k] dp[k 1][j] max[i][k]*max[k 1][j]); dp[i][j] = min;//Always maintain the minimum sum between i and j } } } return dp[0][n-1]; } }
2.1 Python
class Solution: def mctFromLeafValues(self, arr: List[int]) -> int: n = len(arr) dp = [[float('inf') for _ in range(n)] for _ in range(n)] # Set the initial value to the maximum maxval = [[0 for _ in range(n)] for _ in range(n)] # Set the initial range query maximum value to 0 for i in range(n):# Find the largest element in the interval [i, j] for j in range(i, n): maxval[i][j] = max(arr[i:j 1]) for i in range(n):# Leaf nodes do not participate in calculation dp[i][i] = 0 for l in range(1, n): # Enumeration interval length for s in range(n - l): # Enumerate the starting point of the range for k in range(s, s l):# Enumerate and divide two subtrees, k is a value in the middle between s and l dp[s][s l] = min(dp[s][s l], dp[s][k] dp[k 1][s l] maxval[s][k] * maxval[k 1][s l]) return dp[0][n - 1]
3. Complexity
Time: O(n^3), Space: O(n^2)
2. Decrement the stack
0.Link
1. Ideas
1. If you want to minimize the mct value, then the leaf nodes with smaller values should be placed at the bottom as much as possible, and the leaf nodes with larger values should be placed as high as possible. Because the leaf nodes at the bottom are used for multiplication more times. This determines that we need to find a minimum value. You can find a minimum value by maintaining a monotonically decreasing stack, because since it is a monotonically decreasing stack, the node on the left must be greater than the node on the top of the stack, and the current node (on the right) is also greater than the node on the top of the stack (because the current node is smaller than the top of the stack) , it is pushed directly into the stack)
2. After finding this minimum value, you need to look left and right to see which value is smaller on the left or right. The purpose is to put the smaller value at the bottom as much as possible.
2.Code
class Solution { public int mctFromLeafValues(int[] arr) { Stack<Integer> st = new Stack(); st.push(Integer.MAX_VALUE); int mct = 0; //Start to construct a decreasing stack. If the current element is greater than the element on the top of the stack, the element on the top of the stack will be popped out of the stack to find the minimum value. for (int i = 0; i < arr.length; i ) { while (arr[i] >= st.peek()) { //The top element of the stack is popped out of the stack and combined with the smaller elements on the left and right sides mct = st.pop() * Math.min(st.peek(), arr[i]); } st.push(arr[i]); } while (st.size() > 2) { mct = st.pop() * st.peek(); } return mct; } }
2.1 Python
class Solution: def mctFromLeafValues(self, A: List[int]) -> int: res, n = 0, len(A) stack = [float('inf')] #Put the maximum value first on the stack #Start to construct a decreasing stack. If the current element is greater than the top element of the stack, the top element of the stack will be popped out of the stack. The minimum value can be found. for a in A: while a >= stack[-1]: mid = stack.pop() #The top element of the stack is popped out of the stack and combined with the smaller elements on the left and right sides res = mid * min(stack[-1], a) stack.append(a) while len(stack) > 2: res = stack.pop() * stack[-1] return res
3. Complexity
Time: O(n), Space: O(n)
108. Convert ordered array to balanced binary search tree
1. In-order recursion
0.Title
Convert an ordered array in ascending order into a height-balanced binary search tree. A height-balanced binary tree means that the absolute value of the height difference between the left and right subtrees of each node of a binary tree does not exceed 1.
1. Ideas
1. Select the middle number as the root node of the binary search tree, so that the number of numbers assigned to the left and right subtrees is the same or only differs by 1, which can keep the tree balanced.
2. After determining the root node of the balanced binary search tree, the remaining numbers are located in the left subtree and right subtree of the balanced binary search tree respectively. The left subtree and the right subtree are also balanced binary search trees respectively, so it can Create a balanced binary search tree recursively
3. Why can such an achievement be guaranteed to be "balanced"? Here you can refer to "1382. Balance the binary search tree"
4. The basic situation of recursion is that the balanced binary search tree does not contain any numbers. At this time, the balanced binary search tree is empty.
5. In the case of a given in-order traversal sequence array, the numbers in each subtree must be continuous in the array. The numbers contained in the subtree can be determined by the array subscript range. The subscript range is marked as [left, right], when left>right, the balanced binary search tree is empty
2.Code
class Solution: def sortedArrayToBST(self, nums: List[int]) -> TreeNode: def helper(left, right): if left > right: #Dichotomy termination condition return None mid = (left right) // 2 #Always select the number to the left of the middle position as the root node root = TreeNode(nums[mid]) #First determine the root node, then recurse the left and right subtrees root.left = helper(left, mid - 1) root.right = helper(mid 1, right) return root return helper(0, len(nums) - 1)
3. Complexity
Time complexity: O(n), where n is the length of the array. Visit each number only once
Space complexity: O(logn), where n is the length of the array. The space complexity does not consider the return value, so the space complexity mainly depends on the depth of the recursive stack. The depth of the recursive stack is O(logn)
109. Ordered linked list conversion to balanced binary search tree
1. Convert an ordered linked list into an ordered array
1. Complexity
Time: O(n), Space: O(n)
2. Fast and slow pointers divide and conquer
1. Thoughts
1. Set left closed and right open intervals
Assume that the left endpoint of the current linked list is left, the right endpoint is right, and the inclusion relationship is "left closed, right open". The given linked list is a one-way linked list. It is very easy to access subsequent elements, but it cannot directly access the predecessor elements. Therefore, after finding the median node mid of the linked list, if you set the relationship of "left closed, right open", you can directly use (left, mid) and (mid.next, right) to represent the list corresponding to the left and right subtrees No need for mid.pre, and the initial list can also be conveniently represented by (head, null)
2. Use the fast and slow pointers to find the median
Initially, the fast pointer fast and the slow pointer slow point to the left endpoint left of the linked list. While we move the fast pointer fast to the right twice, we move the slow pointer to the right once until the fast pointer reaches the boundary (that is, the fast pointer reaches the right endpoint or the next node of the fast pointer is the right endpoint). At this time, the element corresponding to the slow pointer is the median
3. Recursively construct the tree
After finding the median node, we use it as the element of the current root node, and recursively construct the left subtree corresponding to the linked list on the left side, and the right subtree corresponding to the linked list on the right side.
2. Algorithm
class Solution: def sortedListToBST(self, head: ListNode) -> TreeNode: def getMedian(left: ListNode, right: ListNode) -> ListNode: fast=slow=left while fast != right and fast.next != right:#Define the interval as left closed and right open fast = fast.next.next#The fast pointer moves twice and the slow pointer moves once slow = slow.next return slow #The slow pointer is the median at this time def buildTree(left: ListNode, right: ListNode) -> TreeNode: if left == right: return None mid = getMedian(left, right) #The interval is left closed and right open root = TreeNode(mid.val) #First determine the root node, then construct the left and right subtrees root.left = buildTree(left, mid) #No need for mid.pre root.right = buildTree(mid.next, right) return root return buildTree(head, None)
3. Complexity
Time complexity: O(nlogn), where n is the length of the linked list. Assume that the time to construct a binary search tree from a linked list of length n is T(n), and the recurrence formula is T(n)=2⋅T(n/2) O(n). According to the main theorem, T(n)= O(nlogn)
Space complexity: O(logn), only the space other than the returned answer is calculated here. The height of the balanced binary tree is O(logn), which is the maximum depth of the stack during the recursive process, which is the required space.
3. Divide and conquer in-order traversal optimization
1. Thoughts
1. Assume that the left endpoint number of the current linked list is left, the right endpoint number is right, the inclusion relationship is "double-closed", the number of the linked list nodes is [0, n), and the order of in-order traversal is "left subtree - root node - Right subtree", then in the process of divide and conquer, we don't have to rush to find the median node of the linked list, but use a placeholder node, and then fill in its value when the node is traversed in in-order
2. Perform mid-order traversal by calculating the number range: the number corresponding to the median node is mid=(left right 1)/2, and the number ranges corresponding to the left and right subtrees are [left, mid−1] and [mid 1 respectively. ,right], if left>right, then the traversed position corresponds to an empty node, otherwise it corresponds to a node in the binary search tree
3. We already know the structure of this binary search tree, and the question gives its in-order traversal result. Then we only need to perform an in-order traversal on it to restore the entire binary search tree.
4. Recursive process diagram
2.Code
class Solution: def sortedListToBST(self, head: ListNode) -> TreeNode: def getLength(head: ListNode) -> int: ret = 0 while head: ret=1 head = head.next return ret def buildTree(left: int, right: int) -> TreeNode: if left > right: #End condition of mid-order traversal return None mid = (left right 1) // 2 root = TreeNode() #Use inorder traversal, first construct the left subtree, then return to the root node, and then construct the right subtree, starting from the first element root.left = buildTree(left, mid - 1) nonlocal head #can modify the outer non-global variable head root.val = head.val #Start assigning values from the first node head = head.next root.right = buildTree(mid 1, right) return root length = getLength(head) return buildTree(0, length - 1)
3. Complexity
Time complexity: O(n), where n is the length of the linked list. Assume that the time to construct a binary search tree from a linked list of length n is T(n), and the recurrence formula is T(n)=2⋅T(n/2) O(1). According to the main theorem, T(n)= O(n)
Space complexity: O(logn), only the space other than the returned answer is calculated here. The height of the balanced binary tree is O(logn), which is the maximum depth of the stack during the recursive process, which is the required space.
297. Serialization and deserialization of binary trees
1. Preorder traversal
0.Title
Serialization is the operation of converting a data structure or object into continuous bits. The converted data can then be stored in a file or memory, and can also be transmitted to another computer environment through the network and reconstructed in the opposite way. Get the original data. Please design an algorithm to implement serialization and deserialization of binary trees. Your sequence/deserialization algorithm execution logic is not limited here. You only need to ensure that a binary tree can be serialized into a string and deserialize the string into the original tree structure.
1. Ideas
1. Serialization
1. Preorder traversal is chosen because the printing order of root|left|right makes it easier to locate the value of the root node during deserialization.
2. When encountering a null node, it must be translated into the corresponding symbol. Only when deserializing does it know that it corresponds to null.
3. Recursive graph
2.Deserialization
1. To build a tree, first build the root node. The auxiliary function buildTree receives the list array converted from the serialized string.
2. Pop up the first item of the list in turn and build the root node of the current subtree. Following the list array, the root node, the left subtree, and the right subtree will be built first. If the character that pops up is 'X', a null node is returned. If the character that pops up is not 'X', create a node, recursively build its left and right subtrees, and return the current subtree.
2.Code
class Codec: def serialize(self, root): #Preorder traversal implements serialization if root == None: return 'X,' leftserilized = self.serialize(root.left) rightserilized = self.serialize(root.right) return str(root.val) ',' leftserilized rightserilized def deserialize(self, data): data = data.split(',') root = self.buildTree(data) return root def buildTree(self, data): val = data.pop(0) #Pop the first character if val == 'X': return None #Return empty node node = TreeNode(val) node.left = self.buildTree(data) node.right = self.buildTree(data) return node
3. Complexity
Time: O(n), Space: O(n)
100. Identical trees
1. Depth first
0.Title
Given the root nodes p and q of two binary trees, write a function to check whether the two trees are the same
1. Ideas
1. If both binary trees are empty, then the two binary trees are the same. If one and only one of the two binary trees is empty, then the two binary trees must not be the same
2. If both binary trees are not empty, then first determine whether the values of their root nodes are the same. If they are not the same, the two binary trees must be different. If they are the same, then determine whether the left subtrees and right subtrees of the two binary trees are the same. subtrees are the same. This is a recursive process, so you can use depth-first search to recursively determine whether two binary trees are the same.
2.Code
class Solution: def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: if not p and not q: #Both trees are empty and the same return True elif not p or not q: #Only one of the trees is empty, the two trees are not the same return False elif p.val != q.val: return False else: return self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
3. Complexity
Time complexity: O(\min(m,n))O(min(m,n)), where mm and n are the number of nodes of the two binary trees respectively. Perform a depth-first search on two binary trees at the same time. The node will be accessed only when the corresponding nodes in the two binary trees are not empty, so the number of nodes visited will not exceed the number of nodes in the smaller binary tree.
Space complexity: O(\min(m,n))O(min(m,n)), where mm and n are the number of nodes of the two binary trees respectively. The space complexity depends on the number of levels of recursive calls. The number of levels of recursive calls will not exceed the maximum height of the smaller binary tree. In the worst case, the height of the binary tree is equal to the number of nodes.
2.Breadth first
1. Ideas
1. First determine whether the two binary trees are empty. If both binary trees are not empty, start the breadth-first search from the root nodes of the two binary trees.
2. Use two queues to store the nodes of two binary trees respectively. Initially, the root nodes of the two binary trees are added to the two queues respectively. Each time, one node is taken out from the two queues and the following comparison operation is performed. Compare the values of two nodes. If the values of the two nodes are not the same, the two binary trees must be different; If the values of two nodes are the same, determine whether the child nodes of the two nodes are empty. If only the left child node of one node is empty, or the right child node of only one node is empty, the structures of the two binary trees are different. Therefore the two binary trees must be different; If the structures of the child nodes of the two nodes are the same, add the non-empty child nodes of the two nodes to the two queues respectively. You need to pay attention to the order when adding the child nodes to the queue. If the left and right child nodes are not empty, add the left child node first. , then add the right child node
3. If both queues are empty at the same time at the end of the search, the two binary trees are the same. If only one queue is empty, the structure of the two binary trees is different, so the two binary trees are different
2.Code
class Solution: def isSameTree(self, p: TreeNode, q: TreeNode) -> bool: if not p and not q: #Both trees are empty and the same return True if not p or not q: #Only one of the trees is empty, the two trees are not the same return False queue1 = collections.deque([p]) queue2 = collections.deque([q]) while queue1 and queue2: node1 = queue1.popleft() node2 = queue2.popleft() if node1.val != node2.val: return False left1, right1 = node1.left, node1.right left2, right2 = node2.left, node2.right if (not left1) ^ (not left2): # ^ means XOR, if they are different, they are 1, if they are the same, they are 0 return False if (not right1) ^ (not right2): return False if left1: #Enter the node into the queue when it is not empty queue1.append(left1) if right1: queue1.append(right1) if left2: queue2.append(left2) if right2: queue2.append(right2) return not queue1 and not queue2
3. Complexity
Time complexity: O(\min(m,n))O(min(m,n)), where mm and n are the number of nodes of the two binary trees respectively. Perform a breadth-first search on two binary trees at the same time. The node will be accessed only when the corresponding nodes in the two binary trees are not empty, so the number of nodes visited will not exceed the number of nodes in the smaller binary tree.
Space complexity: O(\min(m,n))O(min(m,n)), where mm and n are the number of nodes of the two binary trees respectively. The space complexity depends on the number of elements in the queue. The number of elements in the queue will not exceed the number of nodes of the smaller binary tree.
105. Construct a binary tree from preorder and inorder traversal sequences
1. Recursion
1. Ideas
1. For any tree, the form of preorder traversal is always [Root node, [result of preorder traversal of left subtree], [result of preorder traversal of right subtree]] That is, the root node is always the first node in the preorder traversal. The form of inorder traversal is always [ [In-order traversal result of left subtree], root node, [In-order traversal result of right subtree] ]
2. As long as we locate the root node in the in-order traversal, then we can know the number of nodes in the left subtree and right subtree respectively. Since the lengths of pre-order traversal and in-order traversal of the same subtree are obviously the same, we can correspond to the results of the pre-order traversal and locate all the left and right brackets in the above form.
3. In this way, we know the results of the pre-order traversal and in-order traversal of the left subtree, and the results of the pre-order traversal and in-order traversal of the right subtree. We can recursively construct the left subtree and the right subtree. tree, and then connect these two subtrees to the left and right positions of the root node
4. Optimization: When locating the root node in in-order traversal, a simple method is to directly scan the entire in-order traversal result and find the root node list.index(x), but the time complexity of doing so is relatively high. high. We can consider using a HashMap to help us quickly locate the root node. For each key-value pair in the hash map, the key represents an element (the value of the node) and the value represents its occurrence position in the in-order traversal. Before constructing the binary tree, we can scan the list traversed in order once to construct this hash map. In the subsequent process of constructing the binary tree, we only need O(1) time to locate the root node.
2.Code
1. Officially optimized, but there are many parameters. You can use two parameters. See the combination of post-order and mid-order.
class Solution: def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: def myBuildTree(preorder_left: int, preorder_right: int, inorder_left: int, inorder_right: int): if preorder_left > preorder_right: return None preorder_root = preorder_left # The first node in the preorder traversal is the root node inorder_root = index[preorder[preorder_root]] # Locate the subscript of the root node in inorder traversal root = TreeNode(preorder[preorder_root]) # First create the root node size_left_subtree = inorder_root - inorder_left # Get the number of nodes in the left subtree # Recursively construct the left subtree and connect it to the root node # The elements "size_left_subtree starting from the left border 1" in the pre-order traversal correspond to the elements "starting from the left border to the root node positioning -1" in the in-order traversal. root.left = myBuildTree(preorder_left 1, preorder_left size_left_subtree, inorder_left, inorder_root - 1) # Recursively construct the right subtree and connect to the root node # In the pre-order traversal, the elements "from the left boundary 1 and the number of left subtree nodes to the right boundary" correspond to the elements "from the root node positioning 1 to the right boundary" in the in-order traversal. root.right = myBuildTree(preorder_left 1 size_left_subtree, preorder_right, inorder_root 1, inorder_right) return root n = len(preorder) index = {element: i for i, element in enumerate(inorder)} #Construct a hash map, the key is the element, the value is the subscript, quickly locate the root node return myBuildTree(0, n - 1, 0, n - 1)
2. Simple version
class Solution: def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: def recur_func(inorder): x = preorder.pop(0) # Take out the leftmost element of the preorder list each time node = TreeNode(x) # Use this element to generate a node idx = inorder.index(x) # Find the index of the element in the inorder list left_l = inorder[:idx] # Use this element to split the inorder list right_l = inorder[idx 1:] node.left = recur_func(left_l) if left_l else None node.right = recur_func(right_l) if right_l else None # Explore all the way to the leftmost leaf at the bottom, and then return layer by layer from bottom to top. return node if not preorder or not inorder: return None # Test empty return recur_func(inorder)
3. Complexity
Time complexity: O(n), where n is the number of nodes in the tree
Space complexity: O(n), in addition to the O(n) space required to return the answer, we also need to use O(n) space to store the hash map, and O(h) (where h is the height of the tree ) space represents the stack space during recursion
2.Iteration
1. Ideas
1. We use a stack and a pointer to assist in the construction of the binary tree. Initially, the root node (the first node of the pre-order traversal) is stored in the stack, and the pointer points to the first node of the in-order traversal "the final node reached by the current node continuously moving to the left."
1. The stack is used to maintain "all ancestor nodes of the current node that have not yet considered the right son". The top of the stack is the current node. In other words, only nodes in the stack may connect to a new right son
2. Enumerate each node in the preorder traversal except the first node. If index happens to point to the top node of the stack, then we continuously pop the top node of the stack and move index to the right, and use the current node as the right child of the last popped node
1. For any two consecutive nodes u and v in pre-order traversal, according to the process of pre-order traversal, we can know that u and v have only two possible relationships: v is the left son of u. This is because after traversing to u, the next traversed node is the left son of u, which is v; u has no left son, and v is the right son of an ancestor node of u (or u itself). If u has no left son, then the next traversed node is the right son of u. If u has no right son, we will backtrack upward until we encounter the first node ua that has a right son (and u is not in the subtree of its right son), then v is the right son of ua
2. When we traverse 10, the situation is different. We find that index just points to the current top node 4, which means that 4 has no left son, so 10 must be the right son of a node in the stack.
3. The order of the nodes in the stack is consistent with the order in which they appear in the pre-order traversal, and the right son of each node has not been traversed yet, then the order of these nodes is consistent with the order in which they appear in the in-order traversal. The order must be reversed
4. We can keep moving the index to the right and compare it with the top node of the stack. If the element corresponding to index is exactly equal to the top node of the stack, it means that we found the top node of the stack during in-order traversal, so we increase index by 1 and pop the top node of the stack until the element corresponding to index is not equal to the top node of the stack. According to this process, the last node x we pop up is the parent node of 10. This is because 10 appears between x and the in-order traversal of the next node of x in the stack, so 10 is the right son of x
3. If index is different from the top node of the stack, we use the current node as the left son of the top node of the stack.
1. We traverse 9. 9 must be the left son of node 3 at the top of the stack. We use proof by contradiction, assuming that 9 is the right son of 3, then 3 has no left son, and the index should just point to 3, but it is actually 4, thus creating a contradiction. So we take 9 as the left son of 3 and push 9 onto the stack
4. No matter what the situation is, we will finally push the current node onto the stack
2.Code
class Solution: def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: if not preorder: return None root = TreeNode(preorder[0]) stack = [root] inorderIndex = 0 #The final node reached by the current node continuously moving to the left, consistent with in-order traversal for i in range(1, len(preorder)):#Traverse preorder traversal starting from the second node preorderVal = preorder[i] node = stack[-1] #The top node of the stack if node.val != inorder[inorderIndex]: #When the top node of the stack is not equal, it is the left child node.left = TreeNode(preorderVal) #Construct the left child directly stack.append(node.left) #Push the left child onto the stack else: #When the top nodes of the stack are equal, it has been traversed to the leftmost point, and it needs to be popped out of the stack and then traversed to the right child of a certain node. while stack and stack[-1].val == inorder[inorderIndex]: node = stack.pop() #The top element of the stack is popped out of the stack inorderIndex = 1 #Replace the leftmost element node.right = TreeNode(preorderVal) #When the two are different, the parent node of the current element is found stack.append(node.right) #Also push the current element onto the stack return root
3. Complexity
Time complexity: O(n), where n is the number of nodes in the tree
Space complexity: O(n). In addition to the O(n) space required for the returned answer, we also need to use O(h) (where h is the height of the tree) space to store the stack.
106. Construct a binary tree from inorder and postorder traversal sequences
1. Recursion
1. Ideas
1. The last element of the array traversed in post-order represents the root node, and then divided into in-order traversals.
2. Note that there is a dependency relationship where you need to create the right subtree first and then the left subtree. It can be understood that in the array of post-order traversal, the entire array first stores the nodes of the left subtree, then the nodes of the right subtree, and finally the root node. If you select "the last node of post-order traversal" as the root each time node, the first constructed one should be the right subtree
2.Code
1.Official: two parameters
class Solution: def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: def helper(in_left, in_right): if in_left > in_right:# If there are no nodes here to construct a binary tree, it will end return None val = postorder.pop()# Select the element at post_idx position as the current subtree root node root = TreeNode(val) index = idx_map[val] # Divide into left and right subtrees according to the location of root #The right subtree must be constructed first, because post-order traversal is accessed from the end, so the right subtree must be accessed first root.right = helper(index 1, in_right) root.left = helper(in_left, index - 1) return root # Create a hash table of (element, subscript) key-value pairs idx_map = {val:idx for idx, val in enumerate(inorder)} return helper(0, len(inorder) - 1)
2. Simple version
class Solution: def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: def recur_func(inorder): x = postorder.pop() # Take out the leftmost element of the previous order list each time node = TreeNode(x) # Use this element to generate a node idx = inorder.index(x) # Find the index of the element in the inorder list left_l = inorder[:idx] # Use this element to split the inorder list right_l = inorder[idx 1:] #The right subtree must be constructed first, because post-order traversal is accessed from the end, so the right subtree must be accessed first node.right = recur_func(right_l) if right_l else None node.left = recur_func(left_l) if left_l else None # Explore all the way to the leftmost leaf at the bottom, and then return layer by layer from bottom to top. return node if not postorder or not inorder: return None # Test empty return recur_func(inorder)
3. Complexity
Time complexity: O(n), where n is the number of nodes in the tree
Space complexity: O(n), in addition to the O(n) space required to return the answer, we also need to use O(n) space to store the hash map, and O(h) (where h is the height of the tree ) space represents the stack space during recursion
2.Iteration
1. Ideas
1. If you reverse the in-order traversal, you will get a reverse in-order traversal, that is, traverse the right child each time, then traverse the root node, and finally traverse the left child. If you reverse the post-order traversal, you get the reverse pre-order traversal, that is, traverse the root node each time, then traverse the right child, and finally traverse the left child.
2. The difference from the previous question is: all left children become right children, all right children become left children, and forward order traversal is changed to reverse order traversal.
2.Code
class Solution: def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode: if not postorder: return None root = TreeNode(postorder[-1]) stack = [root] inorderIndex = -1 #The final node reached by the current node continuously moving to the right, consistent with in-order traversal for i in range(len(postorder)-2, -1,-1):#Traverse post-order traversal starting from the penultimate node postorderVal = postorder[i] node = stack[-1] #The top node of the stack if node.val != inorder[inorderIndex]: #When the top node of the stack is not equal, it is the right child node.right = TreeNode(postorderVal) #Construct the right child directly stack.append(node.right) #Push the right child onto the stack else: #When the top nodes of the stack are equal, it has been traversed to the rightmost point. It needs to be popped out of the stack and then traversed to the left child of a certain node. while stack and stack[-1].val == inorder[inorderIndex]: node = stack.pop() #The top element of the stack is popped out of the stack inorderIndex -= 1 #Replace the rightmost element node.left = TreeNode(postorderVal) #When the two are different, the parent node of the current element is found stack.append(node.left) #Also push the current element onto the stack return root
3. Complexity
Time complexity: O(n), where n is the number of nodes in the tree
Space complexity: O(n). In addition to the O(n) space required for the returned answer, we also need to use O(h) (where h is the height of the tree) space to store the stack.
124. Maximum path sum in binary tree
1. Recursion
0.Title
A path is defined as a sequence starting from any node in the tree, connecting along the parent node-child node, and reaching any node. The same node appears at most once in a path sequence. The path contains at least one node and does not necessarily pass through the root node. The path sum is the sum of the values of each node in the path. Give you the root node root of a binary tree and return its maximum path sum
1. Ideas
1. First, consider implementing a simplified function maxGain(node), which calculates the maximum contribution value of a node in the binary tree. Specifically, it searches for the node as the starting point in the subtree with the node as the root node. a path that maximizes the sum of node values on the path
2. Specifically, the function is calculated as follows. The maximum contribution value of an empty node is equal to 00. The maximum contribution value of a non-empty node is equal to the sum of the node value and the maximum contribution value in its child nodes (for leaf nodes, the maximum contribution value is equal to the node value
3. The above calculation process is a recursive process. Therefore, by calling the function maxGain on the root node, the maximum contribution value of each node can be obtained.
4. After getting the maximum contribution value of each node according to the function maxGain, how to get the maximum path sum of the binary tree? For a node in a binary tree, the maximum path sum of the node depends on the value of the node and the maximum contribution value of the left and right child nodes of the node. If the maximum contribution value of the child node is positive, it is included in the maximum path sum of the node. , otherwise it will not be included in the maximum path sum of the node. Maintain a global variable maxSum to store the maximum path sum, and update the value of maxSum during the recursive process. The final value of maxSum is the maximum path sum in the binary tree.
2.Code
class Solution: def __init__(self): self.maxSum = float("-inf") #The maximum value is initialized to negative infinity def maxPathSum(self, root: TreeNode) -> int: def maxGain(node): #Get the maximum contribution value of the node if not node: return 0 # Recursively calculate the maximum contribution value of the left and right child nodes # Only when the maximum contribution value is greater than 0, the corresponding child node will be selected leftGain = max(maxGain(node.left), 0) rightGain = max(maxGain(node.right), 0) # First get the maximum path value with the current node as the root node, and then return the maximum contribution value of the node #The maximum path sum of a node depends on the value of the node and the maximum contribution value of the left and right child nodes of the node priceNewpath = node.val leftGain rightGain self.maxSum = max(self.maxSum, priceNewpath) # Update answer # Return the maximum contribution value of the node, which is determined by the larger of the node value and the left subtree/right subtree return node.val max(leftGain, rightGain) maxGain(root) return self.maxSum
3. Complexity
Time complexity: O(N)O(N), where NN is the number of nodes in the binary tree. No more than 2 visits to each node
Space complexity: O(N)O(N), where NN is the number of nodes in the binary tree. The space complexity mainly depends on the number of recursive call levels. The maximum number of levels is equal to the height of the binary tree. In the worst case, the height of the binary tree is equal to the number of nodes in the binary tree.
654. Maximum binary tree
1. Recursion
0.Title
Given an integer array nums without duplicate elements. A maximum binary tree constructed directly and recursively from this array is defined as follows: The root of the binary tree is the largest element in the array nums. The left subtree is the largest binary tree constructed recursively through the left part of the maximum value in the array. The right subtree is the largest binary tree constructed recursively through the right part of the maximum value in the array. Returns the largest binary tree constructed with the given array nums
1. Ideas
Still a trilogy 1. Recursion termination condition: When the arrays of the left subtree and the right subtree are both empty 2. What this recursion does: Take out the maximum value of the array, divide the array into left and right arrays based on the maximum value, and then perform recursive assignment 3. What is returned: The root node of the subtree
2.Code
class Solution: def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode: if not nums: return None # Applicable when there are repeated elements # max_v, m_i = float(-inf), 0 # for i, v in enumerate(nums): # if v>max_v: # max_v = v # m_i = i max_v = max(nums) m_i = nums.index(max_v) root = TreeNode(max_v) root.left = self.constructMaximumBinaryTree(nums[:m_i]) root.right = self.constructMaximumBinaryTree(nums[m_i 1:]) return root
3. Complexity
Time complexity: O(n^2), method construct is called n times in total. Each time you recursively search for the root node, you need to traverse all elements in the current index range to find the maximum value. In general, the complexity of each traversal is O(logn), and the total complexity is O(nlogn). In the worst case, the array numsnums is sorted, and the total complexity is O(n^2)
Space complexity: O(n)O(n). The recursive call depth is nn. On average, the recursive call depth for an array of length n is O(logn)
2. Monotonically decreasing stack
1. Thoughts
1. Construct a decreasing stack and push it into the stack in sequence. If the element on the top of the stack is smaller than the current element, the element on the top of the stack is popped off the stack.
2. Compare the size of the top element after being popped from the stack with the size of the current element. If it is larger than the current element, the current element is used as the parent node, and the popped element is used as the left child; if the top element of the stack is smaller than the current element, the top element of the stack is used as the parent node. And pop it off the stack. The previously popped element is used as the right child, and continues until the current node is pushed onto the stack.
3. Until the end of the last element, a decreasing stack is formed, and the stack is popped sequentially. The popped elements are used as the right children. The last one popped out of the stack is the root node. Return to this node.
Example
Take the test case as an example, an input sequence: [3, 2, 1, 6, 0, 5]. Set up an auxiliary stack to store from large to small. The process is as follows: Push 3 first 2 is smaller than 3, pushed onto the stack 1 is smaller than 2, pushed onto the stack 6 is greater than 1, so 1 and 1 are to be popped up. Choose the smaller element between 2 and 6 as the parent node, so 2 is selected. 1 is on the right side of 2, making 1 the right child node of 2. After popping up 1, 6 is still larger than 2. Similarly, 2 needs to choose one between 3 and 6 as the parent node. 3 is smaller than 6, so 3 is selected. 2 is to the right of 3, so 2 is the right child of 3. In the same way, pop up 3, let 3 be the left child node of 6 Push 6 Push 0 to the stack When 5 is pushed onto the stack, it is larger than 0. To pop 0, select 5 as the parent node, and 0 is the left child of 5. 5 pops up, with 6 on the left as the parent node of 5 6 finally pops up, which is the root node
2. Code Java
public TreeNode constructMaximumBinaryTree(int[] nums) { TreeNode root = null, node = null; LinkedList<TreeNode> stack = new LinkedList<>(); for (int i = 0; i < nums.length; i) { node = new TreeNode(nums[i]); //Construct a decreasing stack. When the top element of the stack is smaller than the current element, the top element of the stack will be popped off the stack. while (!stack.isEmpty() && stack.peek().val < node.val) { root = stack.pop();//The top element of the current stack, the smallest value //Compare the size of the top of the stack after popping with the size of the current element. If it is larger than the current element, the current element will be used as the parent node. if (stack.isEmpty() || stack.peek().val > node.val) { node.left = root; } else {//If it is less than the current element, the top element of the stack will be used as the parent node stack.peek().right = root; } } stack.push(node);//Push the current element onto the stack } // The decrementing stack is completely formed and can be popped out of the stack as the right child. while (!stack.isEmpty()) { root = stack.pop(); if (!stack.isEmpty()) { stack.peek().right = root; } } return root;//The last thing to pop off the stack is root, return to this node }
3. Complexity
Time complexity: O(n), Space complexity: O(n)
998.Maximum Binary Tree II×
1. Traverse insertion
0.Title
Maximum tree definition: A tree in which the value of each node is greater than any other value in its subtree gives the root node root of the largest tree. Like the previous question, the given tree is constructed recursively from the list A, we are not given A directly, only a root node root, assuming B is a copy of A with the value val appended to the end. The question data guarantees that the values in B are different. Return Construct(B)
1. Thoughts
Each time it is compared with the head node, if it is smaller, its right node is checked. Update: 1) The existing node becomes the left node of the new node, 2) the new node becomes the right node of the parent node
2.Code
def insertIntoMaxTree(self, root: TreeNode, val: int) -> TreeNode: # dummy dummy variable, its right child points to the root node dummy = TreeNode(0) dummy.right = root # search If the current root node value is greater than val, continue to query its right child p, c = dummy, dummy.right while c and c.val > val: p, c = c, c.right # insert At this time, val is greater than the root node, val is used as the right child of the previous root node, and the current root node is used as the left child of val n = TreeNode(val) p.right = n n.left = c return dummy.right #dummy has not changed, its right child points to the root node
3. Complexity
2. Recursion
1. Thoughts
The same processing is done when it is greater than the root node. When it is less than the root node, the right child of the root node is processed recursively.
2.Code
def insertIntoMaxTree(self, root: TreeNode, val: int) -> TreeNode: if root is None: # Recursive termination condition returnTreeNode(val) if val > root.val: #How to handle val greater than the current root node tmp = TreeNode(val) tmp.left = root return tmp # Recursively process the right child of the root node root.right = self.insertIntoMaxTree(root.right, val) return root # Recursive return value, root node
3. Complexity
subtopic
subtopic
110. Determine balanced binary tree
1. Top-down
0.Title
Given a binary tree, determine whether it is a height-balanced binary tree. In this question, a height-balanced binary tree is defined as: the absolute value of the height difference between the left and right subtrees of each node of a binary tree does not exceed 1
1. Ideas
1. Define the function height, which is used to calculate the height of any node p in the binary tree.
2. Similar to the pre-order traversal of a binary tree, that is, for the currently traversed node, first calculate the height of the left and right subtrees. If the height difference between the left and right subtrees does not exceed 1, then recursively traverse the left and right subnodes respectively, and determine the left and right subtrees. Whether the subtree and the right subtree are balanced. This is a top-down recursive process
2.Code
class Solution: #Top-down, similar to pre-order traversal, will repeatedly calculate the height of the left and right subtrees def isBalanced(self, root: TreeNode) -> bool: def height(root: TreeNode) -> int: if not root: return 0 return max(height(root.left), height(root.right)) 1 if not root: return True return abs(height(root.left) - height(root.right)) <= 1 and self.isBalanced(root.left) and self.isBalanced(root.right)
3. Complexity
Time complexity: O(n), where n is the number of nodes in the binary tree. Using bottom-up recursion, the calculation height of each node and the judgment of whether it is balanced only need to be processed once. In the worst case, all nodes in the binary tree need to be traversed, so the time complexity is O(n)
Space complexity: O(n), where n is the number of nodes in the binary tree. The space complexity mainly depends on the number of levels of recursive calls. The number of levels of recursive calls will not exceed nn.
2. Bottom-up
1. Ideas
1. Since method one is top-down recursive, the function \texttt{height}height will be called repeatedly for the same node, resulting in high time complexity. If the bottom-up approach is used, the function \texttt{height}height will only be called once for each node.
2. The bottom-up recursive approach is similar to post-order traversal. For the currently traversed node, first recursively determine whether its left and right subtrees are balanced, and then determine whether the subtree rooted at the current node is balanced. If a subtree is balanced, its height is returned (the height must be a non-negative integer), otherwise -1−1 is returned. If there is an unbalanced subtree, the entire binary tree must be unbalanced
2.Code
class Solution: #Bottom-up, similar to post-order traversal, first determine the left and right subtrees, and then determine the root node, ensuring that the height of each node is only calculated once def isBalanced(self, root: TreeNode) -> bool: def height(root: TreeNode) -> int: if not root: return 0 leftHeight = height(root.left) rightHeight = height(root.right) if leftHeight == -1 or rightHeight == -1 or abs(leftHeight - rightHeight) > 1: return -1 else: return max(leftHeight, rightHeight) 1 return height(root) >= 0
3. Complexity
Time complexity: O(n), where n is the number of nodes in the binary tree. Using bottom-up recursion, the calculation height of each node and the judgment of whether it is balanced only need to be processed once. In the worst case, all nodes in the binary tree need to be traversed, so the time complexity is O(n)
Space complexity: O(n), where n is the number of nodes in the binary tree. The space complexity mainly depends on the number of levels of recursive calls. The number of levels of recursive calls will not exceed nn.
111.Minimum depth of binary tree
1. Depth first
0.Title
Given a binary tree, find its minimum depth. The minimum depth is the number of nodes on the shortest path from the root node to the nearest leaf node
1. Ideas
1. The key to this question is to figure out the recursive end condition The definition of a leaf node is that when both the left child and the right child are null, it is called a leaf node. When the left and right children of the root node are empty, return 1 When one of the left and right children of the root node is empty, return the depth of the child node that is not empty. When both the left and right children of the root node are empty, return the node values of the smaller depth of the left and right children.
2.Code
class Solution: def minDepth(self, root: TreeNode) -> int: if not root: return 0 #When both the left and right subtrees are empty, it is a leaf node, and the return distance is 1 if not root.left and not root.right: return 1 min_depth = 10**9 if root.left: #The left subtree exists, compare the minimum distance of the left subtree with the current minimum distance min_depth = min(self.minDepth(root.left), min_depth) if root.right: min_depth = min(self.minDepth(root.right), min_depth) return min_depth 1
3. Complexity
Time complexity: O(n), where n is the number of nodes in the tree. Visit each node once
Space complexity: O(h), where h is the height of the tree. The space complexity mainly depends on the overhead of stack space during recursion. In the worst case, the tree appears in a chain shape and the space complexity is O(n). On average, the height of the tree is positively related to the logarithm of the number of nodes, and the space complexity is O(logN)
2.Breadth first
1. Ideas
When we find a leaf node, we directly return the depth of the leaf node. The nature of breadth-first search ensures that the depth of the leaf node searched first must be the smallest.
2.Code
class Solution: def minDepth(self, root: TreeNode) -> int: if not root: return 0 que = collections.deque([(root, 1)]) while que: node, depth = que.popleft() #The first leaf node must be the closest leaf node if not node.left and not node.right: return depth if node.left: que.append((node.left, depth 1)) if node.right: que.append((node.right, depth 1)) return 0
3. Complexity
Time complexity: O(n), where n is the number of nodes in the tree. Visit each node once
Space complexity: O(n), where n is the number of nodes in the tree. The space complexity mainly depends on the overhead of the queue. The number of elements in the queue will not exceed the number of nodes in the tree.
104,226,96,617,173,1130,108,297,100,105,95,124,654,669,99,979,199,110,236,101,235,114,94,222,102,938,437
7.Breadth first
0.frequency
200,279,301,199,101,127,102,407,133,107,103,126,773,994,207,111,847,417,529,130,542,690,,,743,210,913,512
8. Depth first
0.frequency
200,104,1192,108,301,394,100,105,695,959,124,99,979,199,110,101,114,109,834,116,679,339,133,,,257,546,364,
9.Double pointers
0.frequency
11,344,3,42,15,141,88,283,16,234,26,76,27,167,18,287,349,28,142,763,19,30,75,86,345,125,457,350
10. Sorting
0.frequency
148,56,147,315,349,179,253,164,242,220,75,280,327,973,324,767,350,296,969,57,1329,274,252,1122,493,1057,1152,1086
11.Backtracking method
0.frequency
22,17,46,10,39,37,79,78,51,93,89,357,131,140,77,306,1240,401,126,47,212,60,216,980,44,52,784,526
12. Hash table
0.frequency
1,771,3,136,535,138,85,202,149,49,463,739,76,37,347,336,219,18,217,36,349,560,242,187,204,500,811,609
13.Stack
0.frequency
42,20,85,155,739,173,1130,316,394,341,150,224,94,84,770,232,71,496,103,144,636,856,907,682,975,503,225,145
14. Dynamic programming
509. Fibonacci numbers
1. Dynamic programming
0.Title
Fibonacci numbers, usually represented by F(n), form a sequence called the Fibonacci sequence. The sequence starts with 0 and 1, and each subsequent number is the sum of the previous two numbers.
1. Ideas
1. Determine the meaning of the dp array and subscripts
The definition of dp[i] is: the Fibonacci value of the i-th number is dp[i]
2. Determine the recursion formula
State transition equation dp[i] = dp[i - 1] dp[i - 2]
3.How to initialize the dp array
The question also directly gives us how to initialize dp[0] = 0;dp[1] = 1;
4. Determine the traversal order
dp[i] depends on dp[i - 1] and dp[i - 2], so the order of traversal must be from front to back.
5. Derivation of dp array with examples
When N is 10, the dp array should be the following sequence: 0 1 1 2 3 5 8 13 21 34 55 If you write the code and find that the result is wrong, print out the dp array to see if it is consistent with the sequence we derived.
2.Code
class Solution: def fib(self, n: int) -> int: if n < 2: return n # Scroll array idea, optimize time complexity p, q, r = 0, 0, 1 # No need to maintain the entire sequence, only 3 values can be maintained for i in range(2, n 1): p, q = q, r r = p q return r
3. Complexity
Time complexity: O(n) Space complexity: O(1)
2. Matrix fast exponentiation
1. Ideas
1. The time complexity of method 1 is O(n)O(n). Using matrix fast exponentiation method can reduce time complexity
2. Recurrence relationship
3. As long as we can quickly calculate the nth power of matrix MM, we can get the value of F(n)F(n). If M^n is calculated directly, the time complexity is O(n)O(n). You can define matrix multiplication and then use fast power algorithm to speed up the calculation of M^n here.
2.Code
class Solution: def fib(self, n: int) -> int: if n < 2: return n q = [[1, 1], [1, 0]] res = self.matrix_pow(q, n - 1) return res[0][0] # Matrix raised to the nth power, quickly solved using the bisection method def matrix_pow(self, a: List[List[int]], n: int) -> List[List[int]]: ret = [[1, 0], [0, 1]] while n > 0: if n & 1: # n/2 remainder 1 ret = self.matrix_multiply(ret, a) n >>= 1 # n/2, find the nth power of the matrix by bisection method a = self.matrix_multiply(a, a) return ret #Matrix multiplication def matrix_multiply(self, a: List[List[int]], b: List[List[int]]) -> List[List[int]]: c = [[0, 0], [0, 0]] for i in range(2): for j in range(2): c[i][j] = a[i][0] * b[0][j] a[i][1] * b[1][j] return c
3. Complexity
Time complexity: O(logn) Space complexity: O(1)
3. General formula
1. Ideas
1. Fibonacci numbers F(n)F(n) are homogeneous linear recursions. According to the recursion equation F(n)=F(n−1) F(n−2), such characteristics can be written Equation: x^2 = x 1
2.
3. Substituting the initial conditions F(0)=0, F(1)=1, we get
4. Therefore, the general formula of Fibonacci numbers is as follows:
2.Code
class Solution: def fib(self, n: int) -> int: sqrt5 = 5**0.5 # root number 5 fibN = ((1 sqrt5) / 2) ** n - ((1 - sqrt5) / 2) ** n return round(fibN/sqrt5)
3. Complexity
The time and space complexity of the pow function used in the code is related to the instruction set supported by the CPU. We will not analyze it in depth here.
70. Climb the stairs
1. Dynamic programming
0.Title
Suppose you are climbing stairs. It takes n steps for you to reach the top of the building. You can climb 1 or 2 steps at a time. How many different ways can you climb to the top of a building?
1. Ideas
0.Guide
There is one way to climb the stairs to the first floor, and there are two ways to climb the stairs to the second floor. Then take two more steps on the first flight of stairs to reach the third floor, and one more step on the second flight of stairs to reach the third floor. So the state of the stairs to the third floor can be deduced from the state of the stairs to the second floor and the state of the stairs to the first floor, then you can think of dynamic programming.
1. Determine the meaning of the dp array and subscripts
dp[i]: There are dp[i] ways to climb to the i-th staircase
2. Determine the recursion formula
It can be seen from the definition of dp[i] that dp[i] can be derived in two directions. The first is dp[i - 1]. There are dp[i - 1] ways to go up the i-1 stairs. Then jumping one step at a time is dp[i]. There is also dp[i - 2]. There are dp[i - 2] ways to go up the i-2 stairs. Then jumping two steps in one step is dp[i]. Then dp[i] is the sum of dp[i - 1] and dp[i - 2]! So dp[i] = dp[i - 1] dp[i - 2]
3.How to initialize the dp array
So if i is 0, what should dp[i] be? There are many explanations for this, but they are basically explained directly towards the answer.
For example, there is a way to force yourself to comfort yourself and climb to the 0th floor. Doing nothing is also a way: dp[0] = 1, which is equivalent to standing directly on the top of the building.
I thought that when I ran to floor 0, the method was 0. I could only walk one or two steps at a time. However, the floor was 0 and I just stood on the roof. There was no method, so dp[0] should be 0.
Most of the reasons why dp[0] should be 1 are actually because if dp[0]=1, i can pass the problem by starting from 2 during the recursion process, and then rely on the result to explain dp[0 ] = 1
The question says that n is a positive integer, but the question does not say that n is 0 at all. Therefore, this question should not discuss the initialization of dp[0].
My principle is: do not consider the initialization of dp[0], only initialize dp[1] = 1, dp[2] = 2, and then start recursion from i = 3, so that it meets the definition of dp[i]
4. Determine the traversal order
It can be seen from the recursive formula dp[i] = dp[i - 1] dp[i - 2]; that the traversal order must be from front to back.
5. Derivation of dp array with examples
When N is 10, the dp array should be the following sequence: 0 1 1 2 3 5 8 13 21 34 55
2.Code
class Solution: def climbStairs(self, n: int) -> int: # Scroll array idea, optimize time complexity if n <= 1 : return n dp = [0] * (n 1) # When you need to use list subscripts explicitly, you must create a list dp[1], dp[2] = 1, 2 # There is no need to maintain the entire sequence, just maintain 2 values. Use p and q directly without creating them. for i in range(3, n 1): sum = dp[1] dp[2] dp[1], dp[2] = dp[2], sum return dp[2]
3. Complexity
Time complexity: O(n) Space complexity: O(1)
2. Matrix fast exponentiation
Same as 509
3. General formula
Same as 509
theme
Classic interview questions
15,
Algorithm basics
1. Time complexity
1.Definition
Time complexity is a function that qualitatively describes the running time of the algorithm
2. What is Big O?
Big O is used to represent the upper bound. When it is used as the upper bound of the worst-case running time of the algorithm, it is the upper bound of the running time for any data input. In the interview, it refers to the time complexity of the algorithm. generally
3. Differences in different data sizes
Because Big O is the time complexity shown when the data magnitude breaks through a point and the data magnitude is very large. This amount of data is the amount of data where the constant coefficient no longer plays a decisive role, so we call time complexity The constant term coefficients are omitted because the default data size is generally large enough. Based on this fact, a ranking of the time complexity of the algorithm given is as follows:
O(1) constant order < O(logn) logarithmic order < O(n) linear order < O(n^2) square order < O(n^3) (cubic order) < O(2^n) (exponential order)
4.What is the base of log in O(logn)?
But we collectively say logn, that is, ignoring the description of the base, the logarithm of n with base 2 = the logarithm of 10 with base 2 * the logarithm of n with base 10, and the logarithm of 10 with base 2 is a constant that can be ignored
Question summary
0.Interesting
One pass of coding is as fierce as a tiger, defeating 500% in submission
A string of ideas bloomed with laughter, and the submission beat 80%
1. Don’t understand
1.Tree
145. Postorder traversal Morris traversal
105. Why is it not possible to use index mapping in the concise method?
2. Interview frequency
1.Binary search
4,50,33,167,287,315,349,29,153,240,222,327,69,378,410,162,1111,35,34,300,363,350,209,354,278,374,981,174
2.Breadth first
200,279,301,199,101,127,102,407,133,107,103,126,773,994,207,111,847,417,529,130,542,690,,,743,210,913,512
3. Hash table
1,771,3,136,535,138,85,202,149,49,463,739,76,37,347,336,219,18,217,36,349,560,242,187,204,500,811,609
4. Backtracking method
22,17,46,10,39,37,79,78,51,93,89,357,131,140,77,306,1240,401,126,47,212,60,216,980,44,52,784,526
5. Linked list
2,21,206,23,237,148,138,141,24,234,445,147,143,92,25,160,328,142,203,19,86,109,83,61,82,430,817,
6. Sort
148,56,147,315,349,179,253,164,242,220,75,280,327,973,324,767,350,296,969,57,1329,274,252,1122,493,1057,1152,1086
7. Depth first
200,104,1192,108,301,394,100,105,695,959,124,99,979,199,110,101,114,109,834,116,679,339,133,,,257,546,364,
8.Tree
104,226,96,617,173,1130,108,297,100,105,95,124,654,669,99,979,199,110,236,101,235,114,94,222,102,938,437
9.Array
1,4,11,42,53,15,121,238,561,85,169,66,88,283,16,56,122,48,31,289,41,128,152,54,26,442,39
10.Double pointers
11,344,3,42,15,141,88,283,16,234,26,76,27,167,18,287,349,28,142,763,19,30,75,86,345,125,457,350
11.Stack
42,20,85,155,739,173,1130,316,394,341,150,224,94,84,770,232,71,496,103,144,636,856,907,682,975,503,225,145
12 strings
5,20,937,3,273,22,1249,68,49,415,76,10,17,91,6,609,93,227,680,767,12,8,67,126,13,336,
subtopic
Similar questions
1.The sum of two numbers
1,15,
Code related
1. Code style
1. Core Principles
1.Indentation
It is preferred to use 4 spaces. At present, almost all IDEs convert tabs to 4 spaces by default, so there is no big problem.
2. Maximum length of line
79 characters, it would look better to use backslashes for line breaks
3.Import
1.Format
The import is at the top of the file, after the file comment. The import is usually a single line import or from ... import
2. Sequence
1. Standard library import 2. Relevant third-party imports 3. Specific local application/library imports Put a blank line between each import group. Absolute imports are recommended because they are more readable; explicit relative imports can be used instead of absolute imports when dealing with complex package layouts, which are too verbose
3. Attention
1. Based on practical experience, it is recommended to remove all unnecessary imports
2. Import this part, which can be perfectly solved through the Python library isort (vscode uses isort by default)
3. When from .. import ... exceeds the line length limit, start a new line: --sl/--force-single-line-imports
4. Force sorting by package name: --fss/--force-sort-within-sections
Configured in vscode as
4. Comments
Avoid inconsistencies between comments and code! !, which is even more maddening than no comments at all. Mainly comply with the following points: 1. When modifying the code, modify the comments first; 2. Comments should be complete sentences. Therefore, the first letter of the first word must be capitalized, unless the first word is an identifier starting with a lowercase letter; 3. Short comments do not need to end with a period, but comments with complete sentences must end with a period; 4. Each line of comments starts with a # and a space; 5. Block comments need to use the same level of indentation; 6. Inline comments must be separated from the code by at least two spaces; 7. Try to make your code "speakable" and avoid unnecessary comments.
5. Document strings docstrings
Write docstrings for all public modules, functions, classes and methods
For docstrings, CLion has good support, and vscode can implement Python Docstring Generator through plug-ins.
6. Naming convention
Functions, variables, and properties are spelled in lowercase letters, with underscores between words, for example, lowercase_underscore. Classes and exceptions should be named with the first letter of each word capitalized (high camel case), such as CapitalizedWord. Protected instance attributes should begin with a single underscore. Private instance properties should start with two underscores. Module-level constants should be spelled in all capital letters, with underscores between words. The first parameter of the instance method in the class should be named self, which represents the object itself. The first parameter of the class method (cls method) should be named cls, indicating the class itself
2. Pay attention to details
0.Tools
By using the Python code formatting tool yapf, you can automatically solve some detailed formatting problems. Combined with isort, you can complete the formatting or format checking of Python code completely through scripts. Or configure the IDE to automatically perform code formatting when editing and saving. This is a good practice. You can install the PEP8 library and set it in pycharm, so that the IDE can help you organize the code into the PEP8 style.
1. Code wrap
Newlines should precede binary operators
2. Blank line
There are two blank lines between the top-level function and the class definition. There is a blank line between function definitions inside the class
3. String quotes
Double quotes and single quotes are the same, but it is best to follow a style. I am used to using single quotes because: No need to hold down the Shift key when writing code to improve efficiency Some language strings must use double quotes, and Python does not need to add backslash escaping when processing them.
4. Space
Use space for indentation instead of the tab key. Use four spaces for each level of syntax-related indentation. For long expressions that span multiple lines, all but the first line should be indented 4 spaces above the usual level. When using subscripts to retrieve list elements, call functions, or assign values to keyword arguments, do not add spaces around them. When assigning a value to a variable, a space should be written on the left and right sides of the assignment symbol, and only one
Immediately inside parentheses, brackets, or braces Immediately before a comma, semicolon or colon, there must be a space after it. Immediately before the opening parenthesis of a function parameter list Immediately before the opening parenthesis of an index or slicing operation Always place 1 space on either side of the following binary operators: assignment (=), incremental assignment (=, -=, etc.), comparison (==, <, >, !=, <>, <=, > =, in, not, in, is, is not), Boolean (and, or, not) Put spaces around math operators When used to specify keyword arguments or default argument values, do not use spaces around = return magic(r=real, i=imag)
Add necessary spaces, but avoid unnecessary spaces. Always avoid trailing whitespace, including any invisible characters. Therefore, if your IDE supports displaying all invisible characters, turn it on! At the same time, if the IDE supports deleting blank content at the end of the line, then please enable it too! yapf can help us solve this part. We only need to format the code after writing it.
5. Compound statement
It is not recommended to include multiple statements in one line
6. Trailing comma
When list elements, parameters, and import items may continue to increase in the future, leaving a trailing comma is a good choice. The usual usage is that each element is on its own line, with a comma at the end, and a closing tag is written on the next line after the last element. If all elements are on the same line, there is no need to do this
7. Fix problems detected by linter
Use flake8 to check Python code and modify all checked Errors and Warnings unless there are good reasons.
2. Commonly used codes
1. Dictionary
1. Count the number of occurrences
counts[word] = counts.get(word,0) 1
2. List
1. List specified keyword sorting
items.sort(key=lambda x:x[1], reverse=True)# Sort the second element in reverse order
2. Convert each element in the string list into a number list
list(map(eval,list))
3. Reverse all elements in the list
res[::-1]
4. Exchange two numbers in the list
res[i],res[j] = res[j],res[i] #No intermediate variables required
5. Allocate a fixed-length list
G = [0]*(n 1) #List of length n 1
6. Find the largest element in the interval [i,j] in the arr list
max(arr[i:j 1])
7. Use this element to split the in-order list
left_l = inorder[:idx] right_l = inorder[idx 1:]
3. Loop/judgment
1. When you only need to determine the number of loops and do not need to obtain the value
for _ in range(len(queue)):
2.Abbreviation of if...else
node.left = recur_func(left_l) if left_l else None
3. Reverse cycle
for i in range(len(postorder)-2, -1,-1)
4. Get elements and subscripts at the same time
for i, v in enumerate(nums):
5. Traverse multiple lists at the same time and return multiple values
for net, opt, l_his in zip(nets, optimizers, losses_ his):
Without zip, only one value will be output each time
6. Traverse multiple lists and return a value
for label in ax.get_xticklabels() ax.get_yticklabels():
4. Mapping
1. Quickly convert traversable structures into mappings corresponding to subscripts
index = {element: i for i, element in enumerate(inorder)}
It can also be achieved using list.index(x), but the list must be traversed every time, and the time is O(n). The above is O(1)
3. Commonly used methods
1. Comes with the system
0.Classification
1.Type conversion
1.int(x)
1. When the floating point type is converted to an integer type, the decimal part is directly discarded.
2.float(x)
3.str(x)
1.sorted(num)
1. Sort the specified elements
2.map(func,list)
1. Apply the function of the first parameter to each element of the second parameter
map(eval,list)
3.len(i)
1. Get the length, which can be of any type
4.enumerate()
1. Combine a traversable data object (such as a list, tuple or string) into an index sequence, and list the data subscript and data at the same time. Generally used in for loops: for i, element in enumerate(seq) :
2.enumerate(sequence, [start=0]), the subscript starts from 0, returns the enumerate (enumeration) object
5.type(x)
1. Determine the type of variable x, applicable to any data type
2. If you need to use the variable type as a condition in conditional judgment, you can use the type() function for direct comparison.
if type(n) == type(123):
2.List[]
1. When stack is used
1. Push into the stack
stack.append()
2. Pop out of the stack
stack.pop()
Pop the last element
3. Return the top element of the stack
There is no peek method in the list. You can only pop first and then append.
2. When used in a queue
1. Join the team
queue.append()
2.Leave the team
queue.pop(0)
Dequeue the first element
3. Get element subscript
m_i = nums.index(max_v)
3. Queue deque
1. Double-ended queue is used as a queue
1. Join the team
queue.append()
2.Leave the team
queue.popleft()
4. Common mistakes/differences
1. Question format
1.IndentationError:expected an indented block
There is a problem with indentation. The most common mistake is to mix the Tab and Space keys to achieve code indentation.
Another common reason for the above error is that there is no first line indentation. For example, when writing an if statement, add a colon after it. If you directly change the line, many code editors will automatically indent the first line. However, some code editors may not have this function. In this case, you need to manually indent. It is best to develop a habit. Please don't hit the space bar several times in a row. It is recommended to just press the Tab key.
1.pycharm problem
1. How to get rid of the prompt that the server certificate is not trusted in pycharm
Click File > Settings > Tools > Server Certificates > Accept non-trusted certificates automatically
1.pycharm skills
1. Import Python files into the current project
Directly copy the corresponding file in the file manager to the directory corresponding to the current project, and it will appear directly in pycharm. If the version problem does not occur, collapse the current project directory yourself and then expand it again.
2. Command line installation error
0. To look at the error type, be sure to look at the error on the line above the last dividing line.
Other errors
1."no module named XX"
As everyone's development level improves and the complexity of the program increases, more and more modules and third-party libraries will be used in the program. The reason for this error is that the library "XX" is not installed, pip install ww
1.error
1.Error: Command errored out with exit status 1
Download the corresponding third-party installation package and enter the download directory to install the full name of the downloaded file.
2.error: Microsoft Visual C 14.0 is required
Install Microsoft Visual C 14.0, the blog has the download address visualcppbuildtools_full
3.error: invalid command 'bdist_wheel'
pip3 install wheel
2.AttributeError Property error
0. General solution
If it prompts which module file has an error, find this module, delete it, and replace it with the same file from a friend that can run.
1.AttributeError: module 'xxx' has no attribute 'xxx'
1. The file name conflicts with python’s own keywords and the like.
2. Two py files import each other, causing one of them to be deleted.
2.AttributeError: module 'six' has no attribute 'main'
pip version problem 10.0 does not have main(), Need to downgrade the version: python -m pip install –upgrade pip==9.0.1
Some APIs have changed after Pip v10, resulting in incompatibility between the old and new versions, which affects our installation and update of packages. Just update the IDE and you’re done! When updating, the IDE also gave us friendly prompts. PyCharm -> Help -> Check for Updates
Do not update the cracked version, otherwise the activation information will become invalid.
3.AttributeError: module 'async io' has no attribute 'run'
You named an asyncio py file If the check is not the first one, you need to check your python version because python3.7 and later only support the run method. 1 Upgrade python version 2 run is rewritten as follows loop = asyncio.get_event_loop() result = loop.run_until_complete(coro)
4.AttributeError: module 'asyncio.constants' has no attribute '_SendfileMode'
Replace constants files in asyncio
3.TypeError
1. "TypeError: 'tuple' object cannot be interpreted as an integer"
t=('a','b','c') for i in range(t):
Typical type error problem. In the above code, the range() function expects the incoming parameter to be an integer (integer), but the incoming parameter is a tuple (tuple). The solution is to change the input parameter tuple t to The number of tuples can be of integer type len(t). For example, change range(t) in the above code to range(len(t))
2. "TypeError: 'str' object does not support item assignment"
Caused by trying to modify the value of a string, which is an immutable data type
s[3] = 'a' becomes s = s[:3] 'a' s[4:]
3. "TypeError: Can't convert 'int' object to str implicitly"
Caused by trying to concatenate a non-string value with a string, just cast the non-string to a string with str()
4. "TypeError: unhashable type: 'list'
When using the set function to construct a set, the elements in the given parameter list cannot contain lists as parameters.
5.TypeError: cannot use a string pattern on a bytes-like object
When switching between python2 and python3, you will inevitably encounter some problems. In python3, Unicode strings are in the default format (str type), and ASCII-encoded strings (bytes type). The bytes type contains byte values and is not actually a character. String, python3 and bytearray byte array type) must be preceded by operator b or B; in python2, it is the opposite, ASCII encoded string is the default, and Unicode string must be preceded by operator u or U
import chardet #Need to import this module and detect the encoding format encode_type = chardet.detect(html) html = html.decode(encode_type['encoding']) #Decode accordingly and assign it to the original identifier (variable)
4.IOError
1. "IOError: File not open for writing"
>>> f=open ("hello.py") >>> f.write ("test")
The cause of the error is that the read-write mode parameter mode is not added to the incoming parameters of open("hello.py"), which means that the default way to open the file is read-only.
The solution is to change the mode mode to write mode permission w, f = open("hello. py", "w ")
5.SyntaxError Grammatical errors
1.SyntaxError:invalid syntax”
Caused by forgetting to add colons at the end of statements such as if, elif, else, for, while, class and def
Incorrectly using "=" instead of "==". In Python programs, "=" is an assignment operator, and "==" is an equal comparison operation.
6.UnicodeDecodeError Encoding interpretation error
1.'gbk' codec can't decode byte
Most of the time this is because the file is not UTF8 encoded (for example, it may be GBK encoded) and the system uses UTF8 decoding by default. The solution is to change to the corresponding decoding method: with open('acl-metadata.txt','rb') as data:
open(path, ‘-mode-‘, encoding=’UTF-8’) i.e. open(path file name, read-write mode, encoding)
Commonly used reading and writing modes
7.ValueError
1.too many values to unpack
When calling a function, there are not enough variables to accept the return value.
8.OSError
1.WinError 1455] The page file is too small and the operation cannot be completed.
1. Restart pycharm (basically useless)
2. Set num_works to 0 (maybe useless)
3. Increase the size of the page file (completely solve the problem)
9.ImportError
1.DLL load failed: The page file is too small and the operation cannot be completed.
1. Not only is one project running, but the python program of another project is also running, just turn it off.
2. The windows operating system does not support python’s multi-process operation. The neural network uses multiple processes in data set loading, so set the parameter num_workers in DataLoader to 0.
1.DLL load failed: The operating system cannot run %1
0. Recently when I was running a scrapy project, the scrapy framework that was installed suddenly reported an error and I was caught off guard.
1. Because it is not difficult to install scrapy in Anaconda, it is not as simple and efficient as reinstalling to find a solution.
2. I can't completely uninstall it by just using the conda remove scrapy command. The reason may be that I installed scrapy twice using pip and conda respectively. Readers can try both pip and conda uninstall commands.
pip uninstall scrapy conda remove scrapy
Reinstall pip install scrapy
class error
1. Property issues of multiple inheritance of classes
We only modified A.x, why was C.x also modified? In Python programs, class variables are treated internally as dictionaries, which follow the commonly cited method resolution order (MRO). So in the above code, since the x attribute in class C is not found, it will look up its base class (although Python supports multiple inheritance, there is only A in the above example). In other words, class C does not have its own x attribute, which is independent of A. Therefore, C.x is actually a reference to A.x
scope error
1. Global variables and local variables
1. Local variable x has no initial value, and external variable X cannot be introduced internally.
2. Different operations on lists
Fool did not assign a value to lst, but fool2 did. You know, lst = [5] is the abbreviation of lst = lst [5], and we are trying to assign a value to lst (Python treats it as a local variable). In addition, our assignment to lst is based on lst itself (which is once again treated as a local variable by Python), but it has not been defined yet, so an error occurs! So here we need to distinguish between the use of local variables and external variables.
2.1 Python2 upgrade Python3 error
1.print becomes print()
1. In the Python 2 version, print is used as a statement. In the Python 3 version, print appears as a function. In the Python 3 version, all print content must be enclosed in parentheses.
2.raw_Input becomes input
In the Python 2 version, input functionality is implemented through raw_input. In the Python 3 version, it is implemented through input
3. Problems with integers and division
1. "TypeError: 'float* object cannot be interpreted as an integer"
2. In Python 3, int and long are unified into the int type. Int represents an integer of any precision. The long type has disappeared in Python 3, and the suffix L has also been deprecated. When using int exceeds the local integer size, it will not Then cause OverflowError exception
3. In previous Python 2 versions, if the parameter is int or long, the rounded down (floor) of the division result will be returned, and if the parameter is float or complex, then the equivalent result will be returned. a good approximation of the result after division
4. "/" in Python 3 always returns a floating point number, which always means downward division. Just change "/" to "//" to get the result of integer division
4. Major upgrade of exception handling
1. In Python 2 programs, the format of catching exceptions is as follows: except Exception, identifier
except ValueError, e: # Python 2 handles single exceptions except (ValueError, TypeError), e: # Python 2 handles multiple exceptions
2. In Python 3 programs, the format of catching exceptions is as follows: except Exception as identifier
except ValueError as e: # Python3 handles a single exception except (ValueError, TypeError) as e: # Python3 handles multiple exceptions
3. In Python 2 programs, the format of throwing exceptions is as follows: raise Exception, args
4. In Python 3 programs, the format of throwing exceptions is as follows: raise Exception(args)
raise ValueError, e # Python 2.x method raise ValueError(e) # Python 3.x method
5.xrange() becomes range()
"NameError: name 'xrange' is not definedw"
6. Reload cannot be used directly
"name 'reload' is not defined and AttributeError: module 'sys' has no att"
import importlib importlib.reload(sys)
7. No more Unicode types
"python unicode is not defined"
In Python 3, the Unicode type no longer exists and is replaced by the new str type. The original str type in Python 2 was replaced by bytes in Python 3
8.has_key has been abandoned
"AttributeError: 'diet' object has no attribute 'has_key' "
has_key has been abandoned in Python 3. The modification method is to use in instead of has_key.
9.urllib2 has been replaced by urllib.request
"lmportError: No module named urllib2"
The solution is to modify urllib2 to urllib.request
10. Encoding issues
TypeError: cannot use a string pattern on a bytes-like object
When switching between python2 and python3, you will inevitably encounter some problems. In python3, Unicode strings are in the default format (str type), and ASCII-encoded strings (bytes type). The bytes type contains byte values and is not actually a character. String, python3 and bytearray byte array type) must be preceded by operator b or B; in python2, it is the opposite, ASCII encoded string is the default, and Unicode string must be preceded by operator u or U
import chardet #Need to import this module and detect the encoding format encode_type = chardet.detect(html) html = html.decode(encode_type['encoding']) #Decode accordingly and assign it to the original identifier (variable)
2.1 Command prompt line command
1. Operation directory
1.cd changes the current subdirectory, you can directly copy the path and enter it in one go
2. The CD command cannot change the current disk. CD.. returns to the previous directory. CD\ means returning to the directory of the current disk. When CD has no parameters, the current directory name is displayed.
3.d: Change the disk location
2.Python related
1.pip
0.Get help
pip help
0. Change pip source
1. Temporary use
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple package name
2. Set as default
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
After setting it as default, the future installation libraries will be downloaded from Tsinghua source, and there is no need to add the mirror source URL.
3. Mainstream mirror source address
1. Install the specified library
pip install package name
2. Install the specified version of the specified library
pip install package_name==1.1.2
3. Check which libraries are installed
pip list
4. View the specific information of the library
pip show -f package name
5.Where is pip installed?
pip -V
6. Batch installation library
pip install -r d:\\requirements.txt Directly write package name == version number in the file
7. Install the library using the wheel file
1. Find the .whl file of the corresponding library on the website below, search with Ctrl F, and pay attention to the corresponding version. https://www.lfd.uci.edu/~gohlke/pythonlibs/
2. In the folder where .whl is located, press the Shift key and right-click the mouse to open the CMD window or PowerShell (Or enter this folder through the command line)
3. Enter the command: pip install matplotlib‑3.4.1‑cp39‑cp39‑win_amd64.whl
8. Uninstall the library
pip uninstall package_name
9.Upgrade library
pip install --upgrade package_name
10. Save the library list to the specified file
pip freeze > filename.txt
11. Check the libraries that need to be upgraded
pip list -o
12. Check for compatibility issues
Verify whether the installed library has compatible dependencies pip check package-name
13. Download the library to local
Download the library to a local specified file and save it in whl format pip download package_name -d "File path to save"
2. Package the program into an executable file
pyinstaller -F filename.py
3. Code writing
1. No format
1. Without i, it can only be written as i =1
2. Different formats
1. Class
1. Parameters
0. When defining a class, the first parameter must be self, and the same applies to all methods. When calling members of this class, self. members must be used.
1. For parameters in the class, first write the variable name, add a colon, and then write the type name, separated by commas
2. After the class definition is completed, you can add -> type name at the end to indicate the type of return value.
2. Call
1. To call itself for recursion in a class, you must use self.self function (you do not need to add self in the parameters)
2. When using a class, there is no need to create a new class, just use it directly root = TreeNode(max_num)
3.Method
1._Start with an underscore to define the protection method
2.__Start with two underscores to define private methods
subtopic
2. Numbers
1. Expression of plus and minus infinity
float("inf"), float("-inf")
3. Symbols
1. In Python / means normal division with remainder, // means integer division with no remainder.
2. Non! in Python is represented by not
3.The square in Python is **
4. Statement
1. A colon must be added after all statements related to keywords: except return
2. There is no need to add () to the conditions in loops, judgments and other similar statements, and there are no {} in statement blocks. Use indentation to strictly represent the format.
5. Comments
1. Single line comment #
2. Multi-line comments ''' or """
4.Code
1. List
1. When you need to use list subscripts explicitly, you must use G = [0]*(n 1) # to create a list with a length of n 1, otherwise the subscript will be out of bounds.
2. When creating a fixed-length two-dimensional list, if *n fails, try using a loop
dp = [[float('inf') for _ in range(n)] for _ in range(n)]
3. If the function return value is a list, but only one variable is received, add a comma
line, = ax.plot(x, np.sin(x))
5.Python programming
1. File problem
1. When importing other projects that require a file, use the absolute path of the file.
2. Command line execution file
Python filename.py
3. Third-party package mirror resources
When using pycharm to download third-party packages, add it in Manage Repositories http://mirrors.aliyun.com/pypi/simple/, delete the original URL
6.Pycharm shortcut keys
1. Comment (add/delete)
Ctrl/
Single line comments#
2.Code right shift
Tab
3. Code left shift
Shift Tab
4. Automatic indentation
Ctrl alt I
5. Run
Ctrl shift F10
6.PEP8 standard formatting
Ctrl alt L
It is also the QQ lock shortcut key. Cancel the setting in QQ.
6.1 Quick Fix
alt enter and press enter
7. Copy one line/multiple lines/selected part
Ctrl D
8. Delete one/multiple lines
Ctrl Y
9.Find
Ctrl F
9.1 Global search
Ctrl shift F
10.Replacement
Ctrl R
10.1 Global replacement
Ctrl shift R
11.Move the cursor to the next line
shift Enter
12.Multi-line cursor click
alt left mouse button click
13. Jump to the next breakpoint
alt F9
14.Cancellation
Ctrl Z
14.1 Anti-cancellation
Ctrl shift Z
15. Copy the parent class code
Ctrl o
16. Select word/code block
Ctrl W
17. Quickly view documents (code information)
Ctrl Q
18. Insert a line downwards at any position
shift enter
19. Insert a row upwards at any position
Ctrl alt enter
20. View project view
alt 1
21. View the structure view
alt 7
22. Get into code quickly
Ctrl left click
23. Quickly view history
alt left (return)/right key (forward)
24. Quickly view different methods
alt up/down
25. Switch views
Ctrl Tab
26. View resource files
shift twice
27. See where the method is called
Ctrl alt H Double click to determine position
28.View parent class
Ctrl U
29. View inheritance relationship
Ctrl H
7.pycharm page
0.Menu bar
1.View window
1.Navigation Bar navigation bar
2.Refactor Refactoring
3.Tools
subtopic
4.VCS version control
1. Debug the code
1. Click in front of the code to insert a breakpoint, click on the crawler to debug, and click on the box next to the crawler to end debugging.
2. Click the ↘ icon to jump to the next breakpoint, and you can continuously observe the value of the variable.
2.settings settings
1.appearance & Behavior interface and behavior
1.appearance overall style
1.Theme theme
1.Darcula black theme
2.High contrast high contrast theme
3.Intellij bright theme
2.custom font custom font
2.system settingssystem settings
1.update
Automatically detecting updates can be turned off
2.keymap shortcut keys
1. According to the system view, you can search directly (comment note)
3.editor only edits the area
1.Font code font (adjustable size)
2.Color Scheme code area color scheme
3.Code Style code style
0.Python can make changes to every detail
1.Python
1.Indent number
2.Space space setting
3.Wrapping and Braces
1.Hard wrap at the maximum number of codes in one line
4.Blank lines blank lines
4.Inspections
1.PEP 8 is a code standard, not a grammatical error. Try to keep it as standard as possible.
2. You can set what content is checked, and you can also set the strictness of the check in Severity.
5.File and Code Templates File and Code Templates
1. Add information in Python.Script that will be displayed every time a new file is created, Find out what information you can add online
6.File Encodings file encoding
1.Default UTF-8
7.Live Templates dynamic templates (easy to use and master)
0. You can click the plus sign to add the template yourself, and be sure to set the usage location.
1.for loop
Write iter to select a loop and keep pressing Enter to write code
2. Use a for loop to write a list
Just write compl
4.Plugins
5.Project
1.Project Interpret project interpreter
1. You can manage and add third-party libraries (click the plus sign and search)
2. Different interpreters can be set for the current project
3.Project project management
1. Right-click the file and select Show in Explorer to directly open the file location.
2.New
1.File
It can be various other files, not necessarily Python files.
2.New Scratch File
Create temporary files, equivalent to scratch paper, used to test part of the code
3.Directory directory
Create a new lower-level folder
4.Python File
Compared with ordinary folders, there are more empty initialization Python files.
5. When creating a new HTML file, you can right-click it and open it in a browser to see the display effect.
4. Code results page
1.Terminal terminal
1. It is the same as the system CMD. You can install the package directly here and use the dos command.
2.Python Console console
You can directly write Python code, run it interactively, and edit it line by line.
3.TODO
It is equivalent to a memo. Write TODO ('') at the code point, so you can quickly find it and continue working. It can be used for mutual cooperation.
4. The avatar on the far right
The strictness of code checking can be adjusted. The power saving mode is equivalent to placing the arrow to the far left. All grammatical errors will not be checked.
5.Virtual environment
1.
2. The virtual environment is created because in actual development, different versions of python interpreters and different versions of the same library need to be used at the same time. Therefore, it is necessary to create a virtual environment to isolate the project environment from other environments (system environment, other virtual environments)
3. There are three ways to create a virtual environment in PyCharm, virtualen, conda and pipen
4. Virtualen can be imagined as creating an isolated copy of the current system environment. The interpreter used is the same one you installed (copy)
5. Conda selects a specific python version based on your needs, then downloads the relevant version from the Internet, and creates a new environment that is different from the system environment. The interpreter used is also different from the one you installed.
6. Pipen is similar to virtualen. It also creates a copy based on the existing system environment, but pipen uses Pipfile instead of virtualen’s requirements.txt for dependency management, which is more convenient.
6.Interrelationship
7.SVN
1. When downloading from the official website, select the English version 1.10, which is different from the Chinese version.
2. When installing TortoiseSVN.msi, be sure to check command line tools
3. A series of svn.exe files will appear in the installed bin directory.
4. Configure in pycharm and find the svn.exe file in the bin directory in setting/version control/subversion
5. Right-click the modified file in the folder to view the modifications
6. Right-click the project and a new subversion shortcut will appear. You can submit commits and undo all revert changes.
8. Easy-to-use plug-ins
1.Key Promoter X
Teach you, which shortcut operation should you use to improve efficiency in this operation? Remind you, you haven't set a shortcut key for this operation at the moment. How about setting one up quickly?
2.Regex Tester
You can test regular expressions. Click the small rectangle button at the bottom left of the PyCharm interface to find the Regex Tester option.
3.Auto PEP8
pip install autopep8, and then import this tool in PyCharm (Setting-Tools-External Tools). The specific settings are as follows
4.CodeGlance
Scroll bar for code preview function
5.Profile in PyCharm
Click Run -> Profile 'Program' to perform code performance analysis Click the Call Graph interface to intuitively display the direct calling relationship, running time and time percentage of each function.
6.Json Parser
I often check whether a JSON string is legal. In the past, my approach was to open the online website https://tool.lu/json/ and directly beautify it to verify. Only the JSON format is correct and legal. To beautify, there is a plug-in in PyCharm specifically to do this
7.HighlightBracketPair
Bracket color highlighted in editor
8.Nested Brackets Colorer
Show colors for different bracket pairs
9.Inspect Code in PyCharm
Click on the project folder, then right-click and select Inspect Code to turn on static inspection
5. Common code snippets
1.Input
1. Obtain user input of variable length
def getNum(): #Get user input of variable length nums = [] iNumStr = input("Please enter a number (press enter to exit): ") while iNumStr != "": nums.append(eval(iNumStr)) iNumStr = input("Please enter a number (press enter to exit): ") return nums
2.Text
1. English text denoising and normalization
def getText(): txt = open("hamlet.txt", "r").read() txt = txt.lower() #Convert all to lowercase letters for ch in '!"#$%&()* ,-./:;<=>?@[\\]^_‘{|}~': txt = txt.replace(ch, " ") #Replace special characters in text with spaces return txt
2. Count the frequency of words in English text
hamletTxt = getText() words = hamletTxt.split() #Separate text with spaces and convert to a list counts = {} #Create a new dictionary for word in words: #Count the frequency of each word, the default value is 0 counts[word] = counts.get(word,0) 1 items = list(counts.items()) #Convert dictionary to list items.sort(key=lambda x:x[1], reverse=True)# Sort the second element in reverse order for i in range(20): word, count = items[i] print ("{0:<10}{1:>5}".format(word, count))
3. Statistics of Chinese text word frequency
import jieba txt = open("threekingdoms.txt", "r", encoding='utf-8').read() words = jieba.lcut(txt) counts = {} for word in words: if len(word) == 1: continue else: counts[word] = counts.get(word,0) 1 items = list(counts.items()) #Convert to list to sort items.sort(key=lambda x:x[1], reverse=True) for i in range(15): word, count = items[i] print ("{0:<10}{1:>5}".format(word, count))
3.Array
1. Find the maximum value in the array
1. Elements are repeated
max_v, m_i = float(-inf), 0 #Combine a traversable data object (such as a list, tuple or string) into an index sequence, List both data subscripts and data for i, v in enumerate(nums): if v > max_v: max_v = v m_i = i
2. No repeated elements
max_v = max(nums) m_i = nums.index(max_v)
2. Dichotomy
class Solution: def searchInsert(self, nums: List[int], target: int) -> int: left, right = 0, len(nums) #Use the left closed and right open interval [left, right) while left < right: # Open to the right, so there cannot be =, the interval does not exist mid = left (right - left)//2 # Prevent overflow, //Indicates integer division if nums[mid] < target: # The midpoint is smaller than the target value. On the right side, equal positions can be obtained left = mid 1 # Left closed, so 1 else: right = mid # Open right, the real right endpoint is mid-1 return left # When this algorithm ends, it is guaranteed that left = right, and the return will be the same for everyone.
4.Matplotlib
1. Move the coordinates to (0,0)
ax = plt.gca() ax.spines['right'].set_color('none') ax.spines['top'].set_color('none') ax.xaxis.set_ticks_position('bottom') ax.spines['bottom'].set_position(('data', 0)) ax.yaxis.set_ticks_position('left') ax.spines['left'].set_position(('data', 0))
math
1. Calculate the average
def mean(numbers): #Calculate the average s = 0.0 for num in numbers: s = s num return s/len(numbers)
2. Calculate the variance
def dev(numbers, mean): #Calculate variance sdev = 0.0 for num in numbers: sdev = sdev (num - mean)**2 return pow(sdev / (len(numbers)-1), 0.5)
3. Calculate the median
def median(numbers): #Calculate the median sorted(numbers) size = len(numbers) if size % 2 == 0: med = (numbers[size//2-1] numbers[size//2])/2 else: med = numbers[size//2] return med
6. Code runs locally
In fact, just define a main function, construct an input use case, then define a solution variable, and call the minCostClimbingStairs function.
0. Code writing skills
1.Format
1. Want to write several lines in one line
Add a semicolon at the end of each line;
2. A line is too long and I want to wrap it in a new line.
Just add a right slash\ at the end of this line
Algorithm related
classic thought
0.Frequently encountered problems
1. Prevent overflow
1. When calculating the product of many numbers, in order to prevent overflow, you can take the logarithm of the product and change it into the form of addition.
1.Data structure
1.Array
0.Basic
1. As long as you see that the array given in the interview question is an ordered array, you can think about whether you can use the dichotomy method
2. The elements of the array are continuous in the memory address. An element in the array cannot be deleted individually, but can only be overwritten.
1. The same element in the array cannot be traversed twice.
for (int i = 0; i < nums.length; i ) { for (int j = i 1; j < nums.length; j ) {
2. Circular array problem
When there is a constraint that the head and tail cannot be at the same time, decompose the circular array into several ordinary array problems and find the maximum value
3. Dichotomy interval problem
1. Left closed and right closed [left, right]
int middle = left ((right - left) / 2);// prevent overflow, equivalent to (left right)/2
while (left <= right) { // When left==right, the interval [left, right] is still valid
if (nums[middle] > target) { right = middle - 1; // target is in the left range, so [left, middle - 1]
} else if (nums[middle] < target) { left = middle 1; // target is in the right range, so [middle 1, right]
2. Close left and open right [left, right)
while (left < right) { // Because when left == right, [left, right) is an invalid space
if (nums[middle] > target) { right = middle; // target is in the left interval, in [left, middle)
} else if (nums[middle] < target) { left = middle 1; // target is in the right interval, in [middle 1, right)
2. Hash table
0. As long as it involves counting the number of occurrences of a certain number/value, use a hash table
1. The hash table contains the corresponding number of a certain number, and is not itself
for (int i = 0; i < nums.length; i ) { int complement = target - nums[i]; if (map.containsKey(complement) && map.get(complement) != i)
3. Linked list
1. Add the numbers in two linked lists
1. When reversing the order: the possible carry issue of the last addition must be considered separately.
2. In forward sequence: reverse the linked list/use the data structure of the stack to achieve reversal
2. Find the median of an ordered singly linked list (left closed and right open)
Assume that the left endpoint of the current linked list is left, the right endpoint is right, and the inclusion relationship is "left closed, right open". The given linked list is a one-way linked list. It is very easy to access subsequent elements, but it cannot directly access the predecessor elements. Therefore, after finding the median node mid of the linked list, if you set the relationship of "left closed, right open", you can directly use (left, mid) and (mid.next, right) to represent the list corresponding to the left and right subtrees No need for mid.pre, and the initial list can also be conveniently represented by (head, null)
4.Characters
1. Record whether each character appears
Hash set: Set<Character> occ = new HashSet<Character>(); occ.contains(a)
5. Numbers
1. Carry acquisition for adding two single-digit numbers
int sum = carry x y; int carry = sum/10;
2. Integer reversal
To "pop" and "push" numbers without the help of a auxiliary stack/array, we can use math, take out the last digit first, then divide by 10 to remove the last digit, invert the number and keep multiplying itself After 10, add the last digit taken out, and first determine whether it will overflow.
6.Tree
1. Find precursor nodes
Take one step to the left, then keep walking to the right until you can't go any further
predecessor = root.left; while (predecessor.right != null && predecessor.right != root) { predecessor = predecessor.right; }
7. Collection
1. Remove duplicate elements from the list
s = set(ls); lt = list(s)
8. Tuple
1. If you do not want the data to be changed by the program, convert it to a tuple type
lt = tuple(ls)
2. Classic algorithm
1.Double pointer
0. Commonly used in arrays and linked lists
1. When you need to enumerate two elements in an array, if you find that as the first element increases, the second element decreases, then you can use the double pointer method to move the second pointer from the end of the array Start traversing while ensuring that the second pointer is greater than the first pointer, reducing the time complexity of the enumeration from O(N^2) to O(N)
2. When the search result is a certain range, the double pointers are used to continuously change, similar to the sliding window mechanism.
2. Fast and slow pointer method
Initially, both the fast pointer fast and the slow pointer slow point to the left endpoint left of the linked list. While we move the fast pointer fast to the right twice, we move the slow pointer to the right once until the fast pointer reaches the boundary (that is, the fast pointer reaches the right endpoint or the next node of the fast pointer is the right endpoint). At this time, the element corresponding to the slow pointer is the median
3. Dynamic programming
1. The required number can be obtained through some operation through the previous required number, and the dynamic transfer equation can be found
2.Conditions
If a problem has many overlapping sub-problems, dynamic programming is most effective.
3. Five Steps
1. Determine the meaning of dp array (dp table) and subscripts 2. Determine the recursion formula 3.How to initialize the dp array 4. Determine the traversal order 5. Derivation of dp array with examples
Why determine the recursion formula first and then consider initialization? Because in some cases the recursive formula determines how to initialize the dp array
4.How to debug
1. Print out the dp array and see if it is deduced according to your own ideas.
2. Before writing code, be sure to simulate the specific situation of the state transfer on the dp array, and be sure that the final result is the desired result.
5.Scroll array
When the recursive equation is only related to a few adjacent numbers, a rolling array can be used to optimize the space complexity to O(1)
4. Recursion
0. Recursive Trilogy
Recursion termination condition, what this recursion does, and what it returns
3. Commonly used techniques
1. Clever use of array subscripts
1.Application
The subscript of an array is an implicitly useful array, especially when counting some numbers (treating the corresponding array value as the subscript temp[arr[i]] of the new array), or determining whether some integers appear. time passed
2.Examples
1. When we give you a string of letters and ask you to determine the number of times these letters appear, we can use these letters as subscripts. When traversing, if the letter a is traversed, then arr[a] can be increased by 1. That is, arr[a]. Through this clever use of subscripts, we don’t need to judge letter by letter.
2. You are given n unordered int integer arrays arr, and the value range of these integers is between 0-20. You are required to sort these n numbers from small to large in O(n) time complexity. Print it out in large order, and use the corresponding value as the array subscript. If this number has appeared before, add 1 to the corresponding array.
2. Use the remainder skillfully
1.Application
When traversing the array, out-of-bounds judgment will be performed. If the subscript is almost out of bounds, we will set it to 0 and traverse again. Especially in some ring-shaped arrays, such as queues implemented with arrays pos = (pos 1) % N
3. Use double pointers skillfully
1.Application
For double pointers, it is particularly useful when doing questions about singly linked lists.
2.Examples
1. Determine whether a singly linked list has a cycle
Set up a slow pointer and a fast pointer to traverse the linked list. The slow pointer moves one node at a time, while the fast pointer moves two nodes at a time. If the linked list does not have a cycle, the fast pointer will traverse the list first. If there is a cycle, the fast pointer will meet the slow pointer during the second traversal.
2. How to find the middle node of the linked list in one traversal
The same is to set a fast pointer and a slow pointer. The slow one moves one node at a time, while the fast one moves two. When traversing the linked list, when the fast pointer traverse is completed, the slow pointer just reaches the midpoint
3. The k-th node from the last in a singly linked list
Set two pointers, one of which moves k nodes first. After that both pointers move at the same speed. When the first moved pointer completes the traversal, the second pointer is exactly at the kth node from the bottom.
subtopic
4. Use shift operations skillfully
1.Application
1. Sometimes when we perform division or multiplication operations, such as n / 2, n / 4, n / 8, we can use the shift method to perform the operation. Through the shift operation, The execution speed will be faster
2. There are also some operations such as & (and) and | (or), which can also speed up the operation.
2.Examples
1. To determine whether a number is odd, it will be much faster to use the AND operation.
5. Set the sentinel position
1.Application
1. In related issues of linked lists, we often set a head pointer, and this head pointer does not store any valid data. Just for the convenience of operation, we can call this head pointer the sentinel bit.
2. When operating an array, you can also set a sentinel, using arr[0] as the sentinel.
2.Examples
1. When we want to delete the first node, if a sentinel bit is not set, the operation will be different from the operation of deleting the second node. But we have set up a sentinel, so deleting the first node and deleting the second node are the same in operation, without making additional judgments. Of course, the same is true when inserting nodes
2. When you want to judge whether two adjacent elements are equal, setting a sentinel will not worry about cross-border problems. You can directly arr[i] == arr[i-1]. Don’t be afraid of crossing the boundary when i = 0
6. Some optimizations related to recursion
1. Consider state preservation for problems that can be recursive.
1. When we use recursion to solve a problem, it is easy to repeatedly calculate the same sub-problem. At this time, we must consider state preservation to prevent repeated calculations.
2.Examples
0. A frog can jump up 1 step or 2 steps at a time. Find out how many ways the frog can jump up an n-level staircase.
1. This problem can be easily solved using recursion. Assume that f(n) represents the total number of steps for n steps, then f(n) = f(n-1) f(n - 2)
2. The end condition of recursion is when 0 <= n <= 2, f(n) = n, it is easy to write recursive code
3. However, for problems that can be solved using recursion, we must consider whether there are many repeated calculations. Obviously, for the recursion of f(n) = f(n-1) f(n-2), there are many repeated calculations.
4. This time we have to consider state preservation. For example, you can use hashMap to save. Of course, you can also use an array. At this time, you can use array subscripts as we said above. When arr[n] = 0, it means that n has not been calculated. When arr[n] != 0, it means that f(n) has been calculated. At this time, the calculated value can be returned directly.
5. In this way, the efficiency of the algorithm can be greatly improved. Some people also call this kind of state preservation the memo method.
2. Think bottom-up
1. For recursive problems, we usually recurse from top to bottom until the recursion reaches the bottom, and then return the value layer by layer.
2. However, sometimes when n is relatively large, such as when n = 10000, then it is necessary to recurse down 10000 levels until n <= 2 before slowly returning the result. If n is too large, the stack space may be not enough
3. For this situation, we can actually consider a bottom-up approach.
4. This bottom-up approach is also called recursion
3. Advantages over other languages
1.Array
1. The parameters are partial arrays
In Python, parameters can directly return part of the array nums[:i]. There is no need to redesign a method to intercept the array based on the subscript like in Java.
2. Get elements and subscripts at the same time
for i, v in enumerate(nums):
Commonly used data structures/algorithms
1. Linked list
2.Stack
1. Monotone stack
1.Definition
A stack in which the elements in the stack are monotonically increasing or decreasing. A monotonic stack can only be operated on the top of the stack.
2. Nature
1. The elements in the monotonic stack are monotonic.
2. Before elements are added to the stack, all elements that destroy the monotonicity of the stack will be deleted from the top of the stack.
3. Use the monotonic stack to find the element and traverse to the left to the first element that is smaller than it (incremental stack)/find the element and traverse to the left to the first element that is larger than it.
3. Queue
4.Tree
1.BST: Binary Search Tree Binary sorting tree
1.Definition
1. The keys of all nodes on the left subtree are less than the root node
2. The keywords of all nodes on the right subtree are greater than the root node
3. The left and right subtrees are each a binary sorting tree.
Inorder traversal can obtain increasing ordered sequence
2.AVL: Balanced Binary Tree
1.Definition
1. Balanced binary tree: The absolute value of the height difference between the left and right subtrees of any node does not exceed 1
2. Balance factor: The height difference between the left and right subtrees of the node -1,0,1
3.mct: Minimum spanning tree
1.Definition
Any tree that consists only of the edges of G and contains all the vertices of G is called a spanning tree of G.
5. Figure
1. Terminology
1. Cluster (complete subgraph)
Set of points: There is an edge connecting any two points.
1.1 Point independent set
Set of points: There is no edge between any two points
2. Hamilton graph Hamilton
An undirected graph goes from a specified starting point to a specified end point, passing through all other nodes only once. A closed Hamiltonian path is called a Hamiltonian cycle, and a path containing all vertices in the graph is called a Hamiltonian path.
6. Algorithm
1.BFS: Breadth First Search
1.Definition
A hierarchical traversal algorithm similar to a binary tree, giving priority to the earliest discovered node.
2.DFS: Depth First Search
1.Definition
Similar to pre-order traversal of a tree, the last discovered node is given priority.
common sense
1.How many operations per second?
2. Time complexity of recursive algorithm
Number of recursions * Number of operations in each recursion
3. Space complexity of recursive algorithm
Depth of recursion * space complexity of each recursion
Data structure focus
Tree
1.BST: Binary Search Tree
1. Searching for nodes starting from the root node is actually a binary search process, so BST is also called a binary search tree.
2. In-order traversal of BST can obtain an ordered sequence
3. The insertion position must be the leaf position, which will not cause the tree structure to be adjusted.
4. CurrentNode=null cannot be used to delete nodes in Java.
currentNode is passed in through method parameters, so currentNode=null will not cause the object to be destroyed, because the object is still held by a reference outside the method and should be destroyed through the parent node.
5. Ask about BST’s intention in interview
Generally speaking, plain BST is not used in engineering practice because it has a fatal shortcoming - the tree structure is easily unbalanced. Consider an extreme case, if the key value sequence to construct the BST is ordered, Insert nodes one by one, and the resulting BST is actually a linked list, and the query operation time complexity of the linked list cannot reach o(logN), which violates the original design intention of BST. Even if this extreme situation does not occur, BST The structure will also gradually become unbalanced in the process of continuously inserting and deleting nodes, and the tree structure will become more and more tilted. This imbalance of the structure will become more and more unfavorable for query operations.
Solution: Use BST with self-balancing properties, such as AVL and red-black tree
subtopic
subtopic
subtopic
2. Red-black tree RBT
1.Definition
①Each node has a color, black or red ②The root node is black ③Each leaf node (NIL empty node) is black ④If a node is red, its child nodes must be black ⑤All paths from any node to each leaf node of the node contain the same number of black nodes.
2.Illustration
3. Balance properties
Property ⑤, it ensures that among all paths starting from any node to its leaf node, the length of the longest path will not exceed twice the length of the shortest path. Therefore, the red-black tree is a relatively close to balanced binary tree.
Moreover, property ⑤ clearly points out that the number of levels of black nodes in the left and right subtrees of each node is equal, so the black nodes of the red-black tree are perfectly balanced.
6. The difference between insertion and deletion
For the inserted node, the inserted position must be a leaf node, so when adjusting, there will be no problems at this level, so adjust from the relationship between the parent node and uncle For deleting nodes, the adjustment position is not necessarily the leaf node, so when adjusting, there may be problems on this layer itself, so the relationship between the node and its brothers will be adjusted from now on.
labuladuo knowledge points
0.Must-read series
1. Framework thinking for learning algorithms and solving questions
1. Storage method of data structure
1. There are only two types: array (sequential storage) and linked list (linked storage)
2. There are also various data structures such as hash tables, stacks, queues, heaps, trees, graphs, etc., which all belong to the "superstructure", while arrays and linked lists are the "structural basis". After all, those diverse data structures Their sources are special operations on linked lists or arrays, and the APIs are just different.
3. Introduction to various structures
1. The two data structures "queue" and "stack" can be implemented using either linked lists or arrays. If you implement it with an array, you have to deal with the problem of expansion and contraction; if you implement it with a linked list, you don't have this problem, but you need more memory space to store node pointers.
2. Two representation methods of "graph", adjacency list is a linked list, and adjacency matrix is a two-dimensional array. The adjacency matrix determines connectivity quickly and can perform matrix operations to solve some problems, but it consumes space if the graph is sparse. Adjacency lists save space, but many operations are definitely not as efficient as adjacency matrices.
3. "Hash table" maps keys to a large array through a hash function. And as a method to solve hash conflicts, the zipper method requires linked list characteristics, which is simple to operate, but requires additional space to store pointers; the linear probing method requires array characteristics to facilitate continuous addressing and does not require storage space for pointers, but the operation is slightly complicated some
4. "Tree", implemented with an array is a "heap", because the "heap" is a complete binary tree. Using an array to store does not require node pointers, and the operation is relatively simple; using a linked list to implement it is a very common "tree", because It is not necessarily a complete binary tree, so it is not suitable for array storage. For this reason, various ingenious designs have been derived based on this linked list "tree" structure, such as binary search trees, AVL trees, red-black trees, interval trees, B-trees, etc., to deal with different problems.
4. Advantages and Disadvantages
1. Since arrays are compact and continuous storage, they can be accessed randomly, the corresponding elements can be found quickly through indexes, and storage space is relatively saved. But because of continuous storage, the memory space must be allocated at one time. Therefore, if the array wants to expand, it needs to reallocate a larger space and then copy all the data there. The time complexity is O(N); and if you want to When inserting and deleting in the middle of the array, all subsequent data must be moved each time to maintain continuity. The time complexity is O(N).
2. Because the elements of a linked list are not continuous, but rely on pointers to point to the location of the next element, there is no problem of array expansion; if you know the predecessor and successor of a certain element, you can delete the element or insert a new element by operating the pointer. Time complexity O(1). However, because the storage space is not continuous, you cannot calculate the address of the corresponding element based on an index, so random access is not possible; and because each element must store a pointer to the position of the previous and previous elements, it will consume relatively more storage space.
2. Basic operations of data structures
1. The basic operation is nothing more than traversal access. To be more specific, it is: add, delete, check and modify
2. From the highest level, there are only two forms of traversal and access to various data structures: linear and non-linear.
3. Linear is represented by for/while iteration, and nonlinear is represented by recursion.
4. Several typical traversals
1.Array
void traverse(int[] arr) { for (int i = 0; i < arr.length; i ) { // Iterate over arr[i] } }
2. Linked list
/* Basic singly linked list node */ class ListNode { int val; ListNode next; } void traverse(ListNode head) { for (ListNode p = head; p != null; p = p.next) { //Iteratively access p.val } } void traverse(ListNode head) { // Recursively access head.val traverse(head.next) }
3. Binary tree
/* Basic binary tree node */ classTreeNode { int val; TreeNode left, right; } void traverse(TreeNode root) { traverse(root.left) traverse(root.right) }
4.N-ary tree
/* Basic N-ary tree node */ classTreeNode { int val; TreeNode[] children; } void traverse(TreeNode root) { for (TreeNode child : root.children) traverse(child); }
5. The so-called framework is a routine. Regardless of adding, deleting, checking or modifying, these codes are a structure that can never be separated. You can use this structure as an outline and just add codes to the framework according to specific problems.
3. Algorithm question writing guide
1. Brush the binary tree first, brush the binary tree first, brush the binary tree first!
2. Binary trees are the easiest to cultivate framework thinking, and most algorithm techniques are essentially tree traversal problems.
3. Don’t underestimate these few lines of broken code. Almost all binary tree problems can be solved using this framework.
void traverse(TreeNode root) { // Preorder traversal traverse(root.left) // In-order traversal traverse(root.right) // Postorder traversal }
4. If you don’t know how to start or are afraid of the questions, you might as well start with the binary tree. The first 10 questions may be a bit uncomfortable; do another 20 questions based on the framework, and maybe you will have some understanding of your own; finish the entire topic before doing it. If you look back on the topic of dividing and conquering rules, you will find that any problem involving recursion is a tree problem.
5. What should I do if I can’t understand so many codes? By directly extracting the framework, you can see the core idea: In fact, many dynamic programming problems involve traversing a tree. If you are familiar with tree traversal operations, you will at least know how to convert ideas into code, and you will also know how to extract others. The core idea of the solution
2. Dynamic programming problem-solving routine framework
1. Basic concepts
1.Form
The general form of dynamic programming problem is to find the optimal value. Dynamic programming is actually an optimization method in operations research, but it is more commonly used in computer problems. For example, it asks you to find the longest increasing subsequence and the minimum edit distance.
2. Core issues
The core problem is exhaustion. Because we require the best value, we must exhaustively enumerate all possible answers and then find the best value among them.
3.Three elements
1. Overlapping subproblems
0. There are "overlapping sub-problems" in this type of problem. If the problem is brute force, the efficiency will be extremely inefficient. Therefore, a "memo" or "DP table" is needed to optimize the exhaustive process and avoid unnecessary calculations.
2. Optimal substructure
0. The dynamic programming problem must have an "optimal substructure", so that the optimal value of the original problem can be obtained through the optimal value of the sub-problem.
3. State transition equation
0. Problems can be ever-changing, and it is not easy to exhaustively enumerate all feasible solutions. Only by listing the correct "state transition equation" can we exhaustively enumerate correctly.
1. Thinking framework
Clear base case -> clear "state" -> clear "selection" -> define the meaning of dp array/function
#Initialize base case dp[0][0][...] = base # Perform state transfer for state 1 in all values of state 1: for state 2 in all values of state 2: for... dp[state 1][state 2][...] = find the maximum value (select 1, select 2...)
2. Fibonacci Sequence
1. Violent recursion
1.Code
int fib(int N) { if (N == 1 || N == 2) return 1; return fib(N - 1) fib(N - 2); }
2. Recursive tree
1. Whenever you encounter a problem that requires recursion, it is best to draw a recursion tree. This will be of great help to you in analyzing the complexity of the algorithm and finding the reasons for the inefficiency of the algorithm.
2.Pictures
3. Time complexity of recursive algorithm
0. Multiply the number of sub-problems by the time required to solve a sub-problem
1. First calculate the number of sub-problems, that is, the total number of nodes in the recursion tree. Obviously the total number of binary tree nodes is exponential, so the number of sub-problems is O(2^n)
2. Then calculate the time to solve a sub-problem. In this algorithm, there is no loop, only f(n - 1) f(n - 2) an addition operation, the time is O(1)
3. The time complexity of this algorithm is the multiplication of the two, that is, O(2^n), exponential level, explosion
4. Observing the recursion tree, it is obvious that the reason for the inefficiency of the algorithm is found: there are a large number of repeated calculations.
5. This is the first property of dynamic programming problems: overlapping subproblems. Next, we will try to solve this problem
2. Recursive solution with memo
1. Since the time-consuming reason is repeated calculations, we can create a "memo". Don't rush back after calculating the answer to a certain sub-question. Write it down in the "memo" before returning; every time you encounter a sub-question Check the problem in "Memo" first. If you find that the problem has been solved before, just take out the answer and use it instead of spending time calculating.
2. Generally, an array is used as this "memo". Of course, you can also use a hash table (dictionary)
3.Code
int fib(int N) { if (N < 1) return 0; //The memo is all initialized to 0 vector<int> memo(N 1, 0); // Perform recursion with memo return helper(memo, N); } int helper(vector<int>& memo, int n) { // base case if (n == 1 || n == 2) return 1; //Already calculated if (memo[n] != 0) return memo[n]; memo[n] = helper(memo, n - 1) helper(memo, n - 2); return memo[n]; }
4. Recursive tree
In fact, the recursive algorithm with "memo" transforms a recursive tree with huge redundancy into a recursive graph without redundancy through "pruning", which greatly reduces the number of sub-problems (i.e. recursion number of nodes in the graph)
5. Complexity
There is no redundant calculation in this algorithm, the number of sub-problems is O(n), and the time complexity of this algorithm is O(n). Compared with violent algorithms, it is a dimension reduction attack
6.Comparison with dynamic programming
The efficiency of the recursive solution with memo is the same as that of the iterative dynamic programming solution. However, this method is called "top-down" and dynamic programming is called "bottom-up"
7. Top-down
The recursion tree (or picture) drawn extends from top to bottom, starting from a larger original problem such as f(20), and gradually decomposes the scale downward until f(1) and f(2) These two base cases then return the answers layer by layer.
8. Bottom-up
Just start from the bottom, the simplest, and the smallest problem size f(1) and f(2) and push upward until we reach the answer we want f(20). This is the idea of dynamic programming, and this is why dynamic programming Planning generally breaks away from recursion, and instead completes calculations by loop iteration
3.Iterative solution of dp array
1. Thoughts
With the inspiration from the "memo" in the previous step, we can separate this "memo" into a table, let's call it DP table. Wouldn't it be nice to complete "bottom-up" calculations on this table?
2.Code
int fib(int N) { vector<int> dp(N 1, 0); // base case dp[1] = dp[2] = 1; for (int i = 3; i <= N; i ) dp[i] = dp[i - 1] dp[i - 2]; return dp[N]; }
3. State transition equation
1. It is the core of problem solving. And it is easy to find that in fact, the state transition equation directly represents the brute force solution
2. Don’t look down on violent solutions. The most difficult thing about dynamic programming problems is to write this violent solution, that is, the state transition equation. As long as you write a brute force solution, the optimization method is nothing more than using a memo or DP table, there is no mystery at all.
4. State compression
1. The current state is only related to the two previous states. In fact, you don’t need such a long DP table to store all states. You just need to find a way to store the two previous states. Therefore, it can be further optimized to reduce the space complexity to O(1)
2.Code
int fib(int n) { if (n == 2 || n == 1) return 1; int prev = 1, curr = 1; for (int i = 3; i <= n; i ) { int sum = prev curr; prev = curr; curr = sum; } return curr; }
3. This technique is the so-called "state compression". If we find that each state transfer only requires a part of the DP table, then we can try to use state compression to reduce the size of the DP table and only record the necessary data. Generally speaking, it is Compress a two-dimensional DP table into one dimension, that is, compress the space complexity from O(n^2) to O(n)
3. The problem of collecting change
0.Question
You are given k coins with face values of c1, c2...ck. The quantity of each coin is unlimited. Then you are given a total amount of money. I ask you how many coins you need at least to make up this amount. If it is impossible to make up the amount, , the algorithm returns -1
1. Violent recursion
1. First of all, this problem is a dynamic programming problem because it has an "optimal substructure". To comply with the "optimal substructure", the subproblems must be independent of each other.
2. Back to the problem of collecting change, why is it said to be in line with the optimal substructure? For example, if you want to find the minimum number of coins when amount = 11 (original question), if you know the minimum number of coins when amount = 10 (sub-question), you only need to add one to the answer to the sub-question (choose another face value 1 coin) is the answer to the original question. Because the number of coins is unlimited, there is no mutual control between the sub-problems and they are independent of each other.
3. Four major steps
1. Determine the base case. This is very simple. Obviously, when the target amount is 0, the algorithm returns 0.
2. Determine the "state", that is, the variables that will change in the original problem and sub-problems. Since the number of coins is infinite and the denomination of the coin is also given by the question, only the target amount will continue to approach the base case, so the only "state" is the target amount amount
3. Determine the "choice", which is the behavior that causes the "state" to change. Why does the target amount change? Because you are choosing coins. Every time you choose a coin, it is equivalent to reducing the target amount. So the face value of all coins is your "choice"
4. Clarify the definition of dp function/array. What we are talking about here is a top-down solution, so there will be a recursive dp function. Generally speaking, the parameter of the function is the amount that will change during the state transition, which is the "state" mentioned above; the return value of the function It is the quantity that the question requires us to calculate. As far as this question is concerned, there is only one status, which is "target amount". The question requires us to calculate the minimum number of coins required to make up the target amount. So we can define the dp function like this: The definition of dp(n): input a target amount n, and return the minimum number of coins to make up the target amount n
4. Pseudo code
def coinChange(coins: List[int], amount: int): # Definition: To make up the amount n, at least dp(n) coins are needed def dp(n): # Make a choice and choose the result that requires the least coins. for coin in coins: res = min(res, 1 dp(n - coin)) return res #The final result required by the question is dp(amount) return dp(amount)
5.Code
def coinChange(coins: List[int], amount: int): def dp(n): # base case if n == 0: return 0 if n < 0: return -1 # Find the minimum value, so initialize it to positive infinity res = float('INF') for coin in coins: subproblem = dp(n - coin) # Sub-problem has no solution, skip it if subproblem == -1: continue res = min(res, 1 subproblem) return res if res != float('INF') else -1 return dp(amount)
6. State transition equation
7. Recursive tree
8. Complexity
The total number of sub-problems is the number of recursive tree nodes. This is difficult to see. It is O(n^k). In short, it is exponential. Each subproblem contains a for loop with a complexity of O(k). So the total time complexity is O(k * n^k), exponential level
2. Recursion with memo
1.Code
def coinChange(coins: List[int], amount: int): # memo memo = dict() def dp(n): # Check memo to avoid double counting if n in memo: return memo[n] # base case if n == 0: return 0 if n < 0: return -1 res = float('INF') for coin in coins: subproblem = dp(n - coin) if subproblem == -1: continue res = min(res, 1 subproblem) # Record in memo memo[n] = res if res != float('INF') else -1 return memo[n] return dp(amount)
2. Complexity
Obviously, the "memo" greatly reduces the number of sub-problems and completely eliminates the redundancy of sub-problems, so the total number of sub-problems will not exceed the amount of money n, that is, the number of sub-problems is O(n). The time to deal with a sub-problem remains unchanged and is still O(k), so the total time complexity is O(kn)
3.Iterative solution of dp array
1.Definition of dp array
The dp function is reflected in the function parameters, while the dp array is reflected in the array index: The definition of dp array: when the target amount is i, at least dp[i] coins are needed to collect it
2.Code
int coinChange(vector<int>& coins, int amount) { //The size of the array is amount 1, and the initial value is also amount 1 vector<int> dp(amount 1, amount 1); // base case dp[0] = 0; // The outer for loop traverses all values of all states for (int i = 0; i < dp.size(); i ) { //The inner for loop is finding the minimum value of all choices for (int coin : coins) { // The sub-problem has no solution, skip it if (i - coin < 0) continue; dp[i] = min(dp[i], 1 dp[i - coin]); } } return (dp[amount] == amount 1) ? -1 : dp[amount]; }
3. Process
4.Details
Why is the dp array initialized to amount 1? Because the number of coins that make up the amount can only be equal to the amount at most (all coins with a face value of 1 yuan are used), so initializing it to amount 1 is equivalent to initializing it to positive infinity, which facilitates the subsequent minimum value
4. Summary
1. There are actually no magic tricks for computers to solve problems. Its only solution is to exhaustively exhaust all possibilities. Algorithm design is nothing more than thinking about "how to exhaustively" first, and then pursuing "how to exhaustively exhaustively"
2. Listing the dynamic transfer equation is to solve the problem of "how to exhaustively". The reason why it is difficult is that firstly, many exhaustive calculations need to be implemented recursively, and secondly, because the solution space of some problems is complex and it is not easy to complete the exhaustive calculation.
3. Memos and DP tables are all about pursuing “how to exhaustively exhaustively”. The idea of exchanging space for time is the only way to reduce time complexity. In addition, let me ask, what other tricks can you do?