├── .gitattributes ├── commons ├── .gitattributes ├── README.md ├── note_style.sty ├── LICENSE ├── commons.sty ├── code_style.sty └── .gitignore ├── referenc.tex ├── figures ├── BIT.png ├── uf.png ├── heap.png ├── msort.png ├── treap.png ├── trie.jpg ├── trie2.png ├── BITget.png ├── b-tree.png ├── heapRepr.png ├── rb_left.png ├── rb_right.png ├── rbflip.png ├── rbrotate.png ├── rbtree11.png ├── 23insert1.png ├── 23insert2.png ├── diag1-grid.jpg ├── kmp_table.png ├── ll_reverse.jpg ├── 23splitting.png ├── catalan_proof.png ├── kmp_presuffix.jpg ├── mergeAndSort.png ├── monotonic_stk.jpg ├── morris_time.jpg ├── rainwatertrap.png ├── sort_summary.jpg ├── squareMatrix.png ├── stable_sort.png ├── Cartesian_tree.png ├── histogram_area.png ├── morris_inorder.jpg ├── virtual_indexes.png ├── morris_postorder.jpg ├── vertical_traversal.png ├── rb_3_left_right_btw.png ├── 500px-Inclusion-exclusion.png ├── Container-With-Most-Water.jpg └── Catalan_number_binary_tree_example.png ├── algo-quicksheet.pdf ├── samples └── sample.png ├── add_root.sh ├── style └── customized.sty ├── styles ├── svind.ist └── svindd.ist ├── README.md ├── titlepage.tex ├── dedic.tex ├── cblist.tex ├── acknow.tex ├── part.tex ├── chapterDivideAndConquer.tex ├── foreword.tex ├── chapterGeneral.tex ├── acronym.tex ├── glossary.tex ├── preface.tex ├── LICENSE ├── notation.tex ├── chapterMemoryComplexity.tex ├── chapterTimeComplexity.tex ├── algo-quicksheet.tex ├── .gitignore ├── chapterBasicDataStructures.tex ├── chapterGreedy.tex ├── chapterProbability.tex ├── chapterHeap.tex ├── chapterString.tex ├── chapterLinkedList.tex ├── chapterBalancedSearchTree.tex ├── chapterBitManipulation.tex ├── chapterInterval.tex ├── chapterArithmetic.tex ├── chapterMath.tex ├── chapterSearch.tex ├── chapterCombinatorics.tex ├── chapterBacktracking.tex └── chapterSort.tex /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /commons/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto -------------------------------------------------------------------------------- /referenc.tex: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/referenc.tex -------------------------------------------------------------------------------- /figures/BIT.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/BIT.png -------------------------------------------------------------------------------- /figures/uf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/uf.png -------------------------------------------------------------------------------- /figures/heap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/heap.png -------------------------------------------------------------------------------- /figures/msort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/msort.png -------------------------------------------------------------------------------- /figures/treap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/treap.png -------------------------------------------------------------------------------- /figures/trie.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/trie.jpg -------------------------------------------------------------------------------- /figures/trie2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/trie2.png -------------------------------------------------------------------------------- /algo-quicksheet.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/algo-quicksheet.pdf -------------------------------------------------------------------------------- /figures/BITget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/BITget.png -------------------------------------------------------------------------------- /figures/b-tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/b-tree.png -------------------------------------------------------------------------------- /figures/heapRepr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/heapRepr.png -------------------------------------------------------------------------------- /figures/rb_left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/rb_left.png -------------------------------------------------------------------------------- /figures/rb_right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/rb_right.png -------------------------------------------------------------------------------- /figures/rbflip.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/rbflip.png -------------------------------------------------------------------------------- /figures/rbrotate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/rbrotate.png -------------------------------------------------------------------------------- /figures/rbtree11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/rbtree11.png -------------------------------------------------------------------------------- /samples/sample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/samples/sample.png -------------------------------------------------------------------------------- /figures/23insert1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/23insert1.png -------------------------------------------------------------------------------- /figures/23insert2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/23insert2.png -------------------------------------------------------------------------------- /figures/diag1-grid.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/diag1-grid.jpg -------------------------------------------------------------------------------- /figures/kmp_table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/kmp_table.png -------------------------------------------------------------------------------- /figures/ll_reverse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/ll_reverse.jpg -------------------------------------------------------------------------------- /figures/23splitting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/23splitting.png -------------------------------------------------------------------------------- /figures/catalan_proof.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/catalan_proof.png -------------------------------------------------------------------------------- /figures/kmp_presuffix.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/kmp_presuffix.jpg -------------------------------------------------------------------------------- /figures/mergeAndSort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/mergeAndSort.png -------------------------------------------------------------------------------- /figures/monotonic_stk.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/monotonic_stk.jpg -------------------------------------------------------------------------------- /figures/morris_time.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/morris_time.jpg -------------------------------------------------------------------------------- /figures/rainwatertrap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/rainwatertrap.png -------------------------------------------------------------------------------- /figures/sort_summary.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/sort_summary.jpg -------------------------------------------------------------------------------- /figures/squareMatrix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/squareMatrix.png -------------------------------------------------------------------------------- /figures/stable_sort.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/stable_sort.png -------------------------------------------------------------------------------- /figures/Cartesian_tree.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/Cartesian_tree.png -------------------------------------------------------------------------------- /figures/histogram_area.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/histogram_area.png -------------------------------------------------------------------------------- /figures/morris_inorder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/morris_inorder.jpg -------------------------------------------------------------------------------- /figures/virtual_indexes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/virtual_indexes.png -------------------------------------------------------------------------------- /figures/morris_postorder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/morris_postorder.jpg -------------------------------------------------------------------------------- /figures/vertical_traversal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/vertical_traversal.png -------------------------------------------------------------------------------- /figures/rb_3_left_right_btw.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/rb_3_left_right_btw.png -------------------------------------------------------------------------------- /figures/500px-Inclusion-exclusion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/500px-Inclusion-exclusion.png -------------------------------------------------------------------------------- /figures/Container-With-Most-Water.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/Container-With-Most-Water.jpg -------------------------------------------------------------------------------- /figures/Catalan_number_binary_tree_example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/algorhythms/Algo-Quicksheet/HEAD/figures/Catalan_number_binary_tree_example.png -------------------------------------------------------------------------------- /add_root.sh: -------------------------------------------------------------------------------- 1 | for file in *.tex 2 | do 3 | [ -e "$file" ] || continue 4 | { 5 | echo "% !TEX root = algo-quicksheet.tex" 6 | cat "$file" 7 | } > tmpfile && mv tmpfile "$file" 8 | done -------------------------------------------------------------------------------- /style/customized.sty: -------------------------------------------------------------------------------- 1 | \ProvidesPackage{customized}[2015/07/19 v1.0 customized macros] 2 | \usepackage{titlesec} 3 | \titleformat{\section} 4 | {\normalfont\scshape\secsize\secstyle\centering}{\thesection}{1em}{} -------------------------------------------------------------------------------- /styles/svind.ist: -------------------------------------------------------------------------------- 1 | headings_flag 1 2 | heading_prefix "{\\bf " 3 | heading_suffix "}\\nopagebreak%\n \\indexspace\\nopagebreak%" 4 | delim_0 "\\idxquad " 5 | delim_1 "\\idxquad " 6 | delim_2 "\\idxquad " 7 | delim_n ",\\," 8 | -------------------------------------------------------------------------------- /styles/svindd.ist: -------------------------------------------------------------------------------- 1 | quote '+' 2 | headings_flag 1 3 | heading_prefix "{\\bf " 4 | heading_suffix "}\\nopagebreak%\n \\indexspace\\nopagebreak%" 5 | delim_0 "\\idxquad " 6 | delim_1 "\\idxquad " 7 | delim_2 "\\idxquad " 8 | delim_n ",\\," 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Algo-Quicksheet ![License](https://img.shields.io/badge/license-BSD--3-red.svg) 2 | Quicksheet for Algorithms 3 | 4 | ## Sample Page 5 | ![](/samples/sample.png) 6 | 7 | ## PDF Download 8 | [algo-quicksheet.pdf](https://github.com/algorhythms/Algo-Quicksheet/releases) 9 | -------------------------------------------------------------------------------- /commons/README.md: -------------------------------------------------------------------------------- 1 | # commons-latex 2 | Common Commands for LaTeX 3 | 4 | # Usage 5 | Use as subtree 6 | ```bash 7 | git remote add -f commons git@github.com:idf/commons-latex.git 8 | git subtree add --prefix commons commons master --squash 9 | git subtree pull --prefix commons commons master --squash 10 | git subtree push --prefix commons commons master 11 | ``` 12 | -------------------------------------------------------------------------------- /titlepage.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \onecolumn 3 | \begin{titlepage} 4 | \small{\urld{github.com/algorhythms/Algo-Quicksheet}}\\ 5 | \end{titlepage} 6 | 7 | \newpage 8 | \noindent \textcopyright 2015 github.com/idf \\ 9 | Except where otherwise noted, this document is licensed under a BSD 3.0 10 | license (\urld{opensource.org/licenses/BSD-3-Clause}). 11 | -------------------------------------------------------------------------------- /dedic.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | %%%%%%%%%%%%%%%%%%%%%%% dedic.tex %%%%%%%%%%%%%%%%%%%%%%%%%% 3 | % 4 | % sample dedication 5 | % 6 | % Use this file as a template for your own input. 7 | % 8 | %%%%%%%%%%%%%%%%%%%%%%%% Springer %%%%%%%%%%%%%%%%%%%%%%%%%% 9 | 10 | \begin{dedication} 11 | This book is dedicated to all Software Engineers. 12 | \end{dedication} 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /cblist.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | %%%%%%%%%%%%%%%%%%%%clist.tex %%%%%%%%%%%%%%%%%%%%%%%% 3 | % 4 | % sample list of contributors and their addresses 5 | % 6 | % Use this file as a template for your own input. 7 | % 8 | %%%%%%%%%%%%%%%%%%%%%%%% Springer %%%%%%%%%%%%%%%%%%%% 9 | \contributors 10 | 11 | \begin{thecontriblist} 12 | Daniel D. Zhang (\urld{github.com/idf}) 13 | \end{thecontriblist} 14 | -------------------------------------------------------------------------------- /acknow.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | %%%%%%%%%%%%%%%%%%%%%%acknow.tex%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | % sample acknowledgement chapter 4 | % 5 | % Use this file as a template for your own input. 6 | % 7 | %%%%%%%%%%%%%%%%%%%%%%%% Springer %%%%%%%%%%%%%%%%%%%%%%%%%% 8 | 9 | \extrachap{Acknowledgements} 10 | 11 | Use the template \emph{acknow.tex} together with the Springer document class SVMono (monograph-type books) or SVMult (edited books) if you prefer to set your acknowledgement section as a separate chapter instead of including it as last part of your preface. 12 | 13 | -------------------------------------------------------------------------------- /part.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | %%%%%%%%%%%%%%%%%%%%%part.tex%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | % 4 | % sample part title 5 | % 6 | % Use this file as a template for your own input. 7 | % 8 | %%%%%%%%%%%%%%%%%%%%%%%% Springer %%%%%%%%%%%%%%%%%%%%%%%%%% 9 | 10 | \begin{partbacktext} 11 | \part{Part Title} 12 | \noindent Use the template \emph{part.tex} together with the Springer document class SVMono (monograph-type books) or SVMult (edited books) to style your part title page and, if desired, a short introductory text (maximum one page) on its verso page in the Springer layout. 13 | 14 | \end{partbacktext} -------------------------------------------------------------------------------- /commons/note_style.sty: -------------------------------------------------------------------------------- 1 | \ProvidesPackage{note_style}[2015/09/24 v1.0 common note style macros] 2 | \usepackage[top=1in, bottom=1in, left=1.25in, right=1.25in]{geometry} 3 | \usepackage{color,soul} % provides \hl{#1} 4 | \usepackage[usenames,dvipsnames]{xcolor} 5 | \usepackage{graphicx} 6 | \usepackage{amssymb} 7 | \usepackage{float} 8 | \usepackage{listings} 9 | 10 | \newcommand\note[1]{% 11 | \textcolor{Maroon}{#1}% 12 | } 13 | 14 | \renewcommand\labelitemi{{\boldmath$\cdot$}} 15 | 16 | \newcommand\run[1]{% 17 | 18 | \rih{#1}% 19 | } 20 | 21 | % uniform size box 22 | \newcommand*{\unibox}[1]{\framebox{\strut #1}} 23 | 24 | \lstnewenvironment{pseudo}[1][]{% 25 | \lstset{mathescape, columns=flexible}% 26 | \lstset{#1}% 27 | }{} 28 | -------------------------------------------------------------------------------- /chapterDivideAndConquer.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Divide \& Conquer} 3 | 4 | \section{Principles} 5 | \runinhead{Divide.}\runinhead{Reduce \# sub-problems.} After dividing, we have $a$ subproblems. Now need to identify the redundancy in the $a$ sub-problems. Find the common shared calculations among sub-problems and thus try to reduce $a$ to $a-1$. Identify the \textbf{commonality}. 6 | \runinhead{Sub-problem dimension.} Reduce the dimensionality of the original problem; thus consider the simpler version of the problem. 7 | \runinhead{Input dimension.} Increase the representation dimensionality of the input. For example, in FFT (Fast Fourier Transform) augment the input with complex space. 8 | \begin{align*} 9 | w_{j, k} = e^{j2\pi i/k} 10 | \end{align*} 11 | -------------------------------------------------------------------------------- /foreword.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | %%%%%%%%%%%%%%%%%%%%%%foreword.tex%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | % sample foreword 4 | % 5 | % Use this file as a template for your own input. 6 | % 7 | %%%%%%%%%%%%%%%%%%%%%%%% Springer %%%%%%%%%%%%%%%%%%%%%%%%%% 8 | 9 | \foreword 10 | 11 | Use the template \textit{foreword.tex} together with the Springer document class SVMono (monograph-type books) or SVMult (edited books) to style your foreword\index{foreword} in the Springer layout. 12 | 13 | The foreword covers introductory remarks preceding the text of a book that are written by a \textit{person other than the author or editor} of the book. If applicable, the foreword precedes the preface which is written by the author or editor of the book. 14 | 15 | 16 | \vspace{\baselineskip} 17 | \begin{flushright}\noindent 18 | Place, month year\hfill {\it Firstname Surname}\\ 19 | \end{flushright} 20 | 21 | 22 | -------------------------------------------------------------------------------- /chapterGeneral.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{General} 3 | \section{General Tips} 4 | \runinhead{Information Source.} Keep the source information rather than derived information (e.g. keep the array index rather than array element). 5 | \runinhead{Information Transformation.} Need you keep the raw information to avoid information loss (e.g. after converting \pyinline{str} to \pyinline{list}, you should keep \pyinline{str}). 6 | \runinhead{Element Data Structure} When working with ADT, you should use a more intelligence data structure as type to avoid allocating another ADT to maintain the state (e.g. \javainline{java.util.PriorityQueue}). 7 | \runinhead{Solving unseen problems.} Solving unseen problems is like a search problems. You need to explore different options, either with dfs or bfs. 8 | \runinhead{Small samples.} Try out with some small input sample. 9 | \runinhead{Corner cases.} Atypical input. 10 | -------------------------------------------------------------------------------- /acronym.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | %%%%%%%%%%%%%%%%%%%%%%acronym.tex%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | % sample list of acronyms 4 | % 5 | % Use this file as a template for your own input. 6 | % 7 | %%%%%%%%%%%%%%%%%%%%%%%% Springer %%%%%%%%%%%%%%%%%%%%%%%%%% 8 | 9 | \Extrachap{Abbreviations} 10 | 11 | \runinhead{A} Array 12 | \runinhead{idx} Index 13 | \runinhead{TLE} Time Limit Exceeded 14 | \runinhead{MLE} Memory Limit Exceeded 15 | \runinhead{dp} Dynamic programming 16 | \runinhead{def} Definition 17 | \runinhead{ptr} Pointer 18 | \runinhead{len} Length 19 | \runinhead{asc} Ascending 20 | \runinhead{desc} Descending 21 | \runinhead{pred} Predecessor 22 | \runinhead{succ} Successor 23 | \runinhead{$\pi$/pi} The parent of a child 24 | \runinhead{bfs} Breadth-first search 25 | \runinhead{dfs} Depth-first search 26 | \runinhead{mat} Matrix 27 | \runinhead{ADT} Abstract Data Type 28 | \runinhead{aka} Also known as 29 | -------------------------------------------------------------------------------- /glossary.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | %%%%%%%%%%%%%%%%%%%%%%acronym.tex%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | % sample list of acronyms 4 | % 5 | % Use this file as a template for your own input. 6 | % 7 | %%%%%%%%%%%%%%%%%%%%%%%% Springer %%%%%%%%%%%%%%%%%%%%%%%%%% 8 | 9 | \Extrachap{Glossary} 10 | 11 | \runinhead{in-place} The algorithm takes $\leq c \lg N$ extra space 12 | \runinhead{partially sorted} Number of inversion in the array $\leq cN$ 13 | \runinhead{non-degeneracy} Distinct properties without total overlapping 14 | \runinhead{underflow} Degenerated, empty, or null case 15 | \runinhead{loitering} Holding a reference to an object when it is no longer needed thus hindering garbage collection. 16 | \runinhead{subsarray} Continuous subarray $A[i:j]$ 17 | \runinhead{subsequence} Non-continuous ordered subsequence that $S\subset A[i:j]$. 18 | \runinhead{invariant} An invariant is a condition that can be relied upon to be true during execution of a program. A loop invariant is a condition that is true at the beginning and end of every execution of a loop. 19 | -------------------------------------------------------------------------------- /preface.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | %%%%%%%%%%%%%%%%%%%%%%preface.tex%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 3 | % sample preface 4 | % 5 | % Use this file as a template for your own input. 6 | % 7 | %%%%%%%%%%%%%%%%%%%%%%%% Springer %%%%%%%%%%%%%%%%%%%%%%%%%% 8 | 9 | \preface 10 | \section*{Introduction} 11 | This quicksheet contains many classical equations and diagrams for algorithm, which helps you quickly recall knowledge and ideas in algorithm.\\ 12 | 13 | This quicksheet has three significant advantages: 14 | \begin{enumerate} 15 | \item Non-essential knowledge points omitted 16 | \item Compact knowledge representation 17 | \item Quick recall 18 | \end{enumerate} 19 | \section*{How to Use This Quicksheet} 20 | High-level abstraction is the key. You should not attempt to remember the details of an algorithm. Instead, you should know: 21 | \begin{enumerate} 22 | \item What problems this algorithm solves. 23 | \item The benefits of using this algorithm compared to others. 24 | \item The important clues of this algorithm so that you can derive the details of the algorithm from them. 25 | \end{enumerate} 26 | The codes are just the details of implementation. Remembering them is simply unproductive and non-scalable. Only dive into the codes when you are unable to reconstruct the algorithm from the hits and clues. 27 | 28 | \vspace{\baselineskip} 29 | \begin{flushright}\noindent 30 | At GitHub, June 2015\hfill {\it github.com/idf} \\ 31 | \end{flushright} 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, algorhythms 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of Algo-Quicksheet nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /commons/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Daniel D. Zhang 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of commons-latex nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | 29 | -------------------------------------------------------------------------------- /notation.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \Extrachap{Notations}\label{sec:Notation} 3 | 4 | \section*{General Math Notations} 5 | 6 | \begin{longtable}{cl} 7 | \hline\noalign{\smallskip} 8 | \textbf{Symbol} & \textbf{Meaning} \\ 9 | \noalign{\smallskip}\hline\noalign{\smallskip} 10 | $\lfloor x \rfloor$ & Floor of $x$, i.e. round down to nearest integer\\ 11 | $\lceil x \rceil$ & Ceiling of $x$, i.e. round up to nearest integer\\ 12 | % floor(key) & The largest key $\leq$ the given key \\ 13 | % ceil(key) & The smallest key $\geq$ the given key \\ 14 | $\log x$ & The base of logarithm is 2 unless otherwise stated\\ 15 | $a \wedge b$ & Logical AND\\ 16 | $a \vee b$ & Logical OR\\ 17 | $\neg a $ & Logical NOT\\ 18 | $a\AND b$ & Bit AND\\ 19 | $a \OR b$ & Bit OR\\ 20 | $a \XOR b$ & Bit XOR\\ 21 | $\NOT a$ & Bit NOT\\ 22 | $\SHIFTL a$ & Bit shift left\\ 23 | $\SHIFTR a$ & Bit shift right\\ 24 | $\infty$ & Infinity\\ 25 | $\rightarrow$ & Tends towards / Approaches, e.g., $n \rightarrow \infty$\\ 26 | $\propto$ &Proportional to; $y = ax$ can be written as $y \propto x$\\ 27 | $\abs{x}$ & Absolute value\\ 28 | $||\vec{a}||$ & $L_2$ distance (Euclidean distance) of a vector; norm-2 \\ 29 | $\abs{\mathcal{S}}$ & Size (cardinality) of a set\\ 30 | $n!$ & Factorial function\\ 31 | $\triangleq$ & Defined as\\ 32 | $O(\cdot)$ & Big-O notation, complexity upper bound\\ 33 | $\mathbb{R}$ & The real numbers\\ 34 | $0:n$ & Range (Python convention): $0:n = {0, 1, 2,...,n-1}$\\ 35 | $\approx$ & Approximately equal to\\ 36 | $\sim$ & Tilde, approximated value \\ 37 | $\arg\max\limits_x f(x)$ & Argmax: the value $x$ that maximizes $f$\\ 38 | $\binom{n}{k}$ & $n$ choose $k$ , equal to $\frac{n!}{k!(n-k)!}$\\ 39 | $\text{range}(i,j)$ & Range of number from i (inclusive) to j (exclusive) \\ 40 | $A[i:j]$ & Subarray consist of $A_i, A_{i+1}, ..., A_{j-1}$.\\ 41 | \noalign{\smallskip}\hline\noalign{\smallskip} 42 | \end{longtable} 43 | 44 | 45 | \twocolumn 46 | -------------------------------------------------------------------------------- /commons/commons.sty: -------------------------------------------------------------------------------- 1 | \ProvidesPackage{commons}[2015/07/22 v1.0 common commands macros] 2 | \usepackage{ifthen} 3 | \usepackage[bookmarksnumbered=true, 4 | bookmarksopen=true, 5 | colorlinks=true, 6 | linkcolor=blue, 7 | anchorcolor=blue, 8 | citecolor=blue 9 | ]{hyperref} 10 | \usepackage{amssymb,amsmath,bm} 11 | \usepackage{verbatim} 12 | \usepackage{tikz} 13 | \usetikzlibrary{shadows} 14 | 15 | \usepackage{enumitem} 16 | \setlist[itemize]{noitemsep, topsep=6pt} 17 | \setlist[enumerate]{noitemsep, topsep=6pt} 18 | 19 | %- short cuts 20 | \newcommand{\ra}{\rightarrow} 21 | \newcommand{\la}{\leftarrow} 22 | \newcommand{\Ra}{\Rightarrow} 23 | \newcommand{\La}{\Leftarrow} 24 | \newcommand{\Lra}{\Leftrightarrow} 25 | \newcommand{\R}{\mathbb{R}} 26 | %\newcommand{\E}{\mathbb{E}} 27 | \newcommand{\trieq}{\triangleq} 28 | \newcommand\mat[1]{\mathcal{#1}} 29 | \newcommand{\pinkhl}[1]{\sethlcolor{pink}\hl{#1}\sethlcolor{yellow}} 30 | \newcommand{\sumstep}[3]{\sum_{\substack{#1 \\ \Delta: #2}}^{#3}} 31 | % % after { to avoid extra space 32 | \newcommand\rih[1]{% 33 | \textbf{#1}% 34 | } 35 | 36 | \newcommand\emaillink[1]{% 37 | \href{mailto:#1}{\nolinkurl{#1}}% 38 | } 39 | 40 | \newcommand\display[2]{% 41 | \ifthenelse{\equal{#1}{true}}{#2}{}% 42 | } 43 | 44 | % url directly starts with domain, without protocol 45 | \newcommand\urld[1]{% 46 | \href{http://#1}{\nolinkurl{#1}}% 47 | } 48 | 49 | \newcommand{\hp}{\text{-}} 50 | 51 | % make bmatrix display vertical bar 52 | \makeatletter 53 | \renewcommand*\env@matrix[1][*\c@MaxMatrixCols c]{% 54 | \hskip -\arraycolsep 55 | \let\@ifnextchar\new@ifnextchar 56 | \array{#1}} 57 | \makeatother 58 | 59 | % example: 60 | \begin{comment} 61 | \begin{bmatrix}[cccc|c] 62 | 1 & 0 & 3 & -1 & 0 \\ 63 | 0 & 1 & 1 & -1 & 0 \\ 64 | 0 & 0 & 0 & 0 & 0 \\ 65 | \end{bmatrix} 66 | \end{comment} 67 | 68 | % itemize list tree; for single-line item 69 | \newcommand{\treelist}{\tikz[overlay]\draw(-.2,--.15)--(-.2,.6) [path fading=east](-.2,.15)--(.1,.15);} 70 | \newcommand{\treeitem}{\item[\treelist]} 71 | 72 | % integral 73 | \newcommand*\diff{\mathop{}\!\mathrm{d}} 74 | \newcommand*\Diff[1]{\mathop{}\!\mathrm{d^#1}} % \Diff3\mathbf{y} -------------------------------------------------------------------------------- /chapterMemoryComplexity.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Memory Complexity} 3 | 4 | \section{Introduction} 5 | When discussing memory complexity, need to consider both 6 | \begin{enumerate} 7 | \item \textbf{Heap}: the declared variables' size. 8 | \item \textbf{Stack}: the recursive functions' call stack. 9 | \end{enumerate} 10 | \subsection{Memory for Data Type} 11 | The memory usage is based on Java.\\ 12 | 13 | \begin{table} 14 | \begin{tabular}{ll} 15 | \hline\noalign{\smallskip} 16 | \textbf{Type} & \textbf{Bytes} \\ 17 | \noalign{\smallskip}\hline\noalign{\smallskip} 18 | 19 | boolean & 1 \\ 20 | byte & 1 \\ 21 | char & 2 \\ 22 | int & 4 \\ 23 | float & 4 \\ 24 | long & 8 \\ 25 | double & 8\\ 26 | 27 | \noalign{\smallskip}\hline\noalign{\smallskip} 28 | \end{tabular} 29 | \caption{for primitive types} 30 | \end{table} 31 | 32 | \begin{table} 33 | \begin{tabular}{ll} 34 | \hline\noalign{\smallskip} 35 | \textbf{Type} & \textbf{Bytes} \\ 36 | \noalign{\smallskip}\hline\noalign{\smallskip} 37 | 38 | char[] & 2N+24 \\ 39 | int[] & 4N+24 \\ 40 | double[] & 8N+24 \\ 41 | T[] & 8N+24 \\ 42 | 43 | \noalign{\smallskip}\hline\noalign{\smallskip} 44 | \end{tabular} 45 | \caption{for one-dimensional arrays} 46 | \end{table} 47 | 48 | \begin{table} 49 | \begin{tabular}{ll} 50 | \hline\noalign{\smallskip} 51 | \textbf{Type} & \textbf{Bytes} \\ 52 | \noalign{\smallskip}\hline\noalign{\smallskip} 53 | 54 | char[][] & 2MN \\ 55 | int[][] & 4MN \\ 56 | double[][] & 8MN \\ 57 | 58 | \noalign{\smallskip}\hline\noalign{\smallskip} 59 | \end{tabular} 60 | \caption{for two-dimensional arrays} 61 | \end{table} 62 | 63 | \begin{table} 64 | \begin{tabular}{ll} 65 | \hline\noalign{\smallskip} 66 | \textbf{Type} & \textbf{Bytes} \\ 67 | \noalign{\smallskip}\hline\noalign{\smallskip} 68 | 69 | Object overhead & 16 \\ 70 | Reference & 8 \\ 71 | Padding & 8x \\ 72 | 73 | \noalign{\smallskip}\hline\noalign{\smallskip} 74 | \end{tabular} 75 | \caption{for objects} 76 | \end{table} 77 | 78 | Notice: 79 | \begin{enumerate} 80 | \item The reference takes memory of 8 bytes. 81 | \item Reference includes object reference and innner class reference. 82 | \item T[] only considers reference; if consider underlying data structure, the memory is 8N+24+xN, where $x$ is the underlying data structure memory for each element. 83 | \item Padding is to make the object memory size as a 8's multiple. 84 | \end{enumerate} 85 | 86 | \subsection{Example} 87 | The generics is passed as Boolean: 88 | \begin{java} 89 | public class Box { // 16 (object overhead) 90 | private in N; // 4 (int) 91 | private T[] items; // 8 (reference to array) 92 | // 8N+24 (array of Boolean references) 93 | // 24N (underlying Boolean objects) 94 | // 4 (padding to round up to a multiple) 95 | } 96 | \end{java} 97 | 98 | Notice the multiple levels of references. 99 | -------------------------------------------------------------------------------- /chapterTimeComplexity.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Time Complexity} 3 | 4 | \section{Basic Counts} 5 | \rih{Double for-loops} 6 | $$ 7 | \sum_{i=1}^N{\sum_{j=i}^N{1}} = {N \choose 2} \sim \frac{1}{2} N^2 8 | $$ 9 | $$ 10 | \sum_{i=1}^N{\sum_{j=i}^N{1}} \sim \int_{x=1}^N \int_{y=x}^N \mathrm{d}y\, \mathrm{d}x 11 | $$ 12 | \rih{Triple for-loops} 13 | $$ 14 | \sum_{i=1}^N{\sum_{j=i}^N{\sum_{k=1}^N{1}}} = {N \choose 3} \sim \frac{1}{6} N^3 15 | $$ 16 | $$ 17 | \sum_{i=1}^N{\sum_{j=i}^N{\sum_{k=1}^N{1}}} \sim \int_{x=1}^N \int_{y=x}^N \int_{z=y}^N \mathrm{d}z\, 18 | \mathrm{d}y\, \mathrm{d}x 19 | $$ 20 | 21 | \section{Solving Recurrence Equations} 22 | Basic recurrence equation solving techniques: 23 | \begin{enumerate} 24 | \item Guessing and validation 25 | \item Telescoping 26 | \item Recursion tree 27 | \item Master Theorem 28 | \end{enumerate} 29 | 30 | \subsection{Master Theorem} 31 | Recurrence relations: 32 | $$T(n) = a \; T\!\left(\frac{n}{b}\right) + f(n)\mbox{, where }a \geq 1 \mbox{, } b > 1$$ 33 | 34 | Notice that $b>1$ rather than $b\geq1$. 35 | 36 | \subsubsection*{Case 1, dominated by the 1st term} 37 | If: 38 | $$f(n) = o(n^{\log_b a})$$ 39 | 40 | where in the condition it is $o$ rather than $O$. \\\\ 41 | Then: 42 | $$T(n) = \Theta(n^{\log_b a})$$ 43 | \subsubsection*{Case 2, non-dominance} 44 | If: 45 | $$f(n) = \Theta(n^{\log_b a} \log^{k} n)$$ 46 | 47 | for some constant $k \geq 0$\\\\ 48 | Then: 49 | $$ 50 | T(n) = \Theta(n^{\log_b a} \log^{k+1} n) 51 | $$ 52 | 53 | typically $k=0$ in most cases. 54 | 55 | If $a=b$, then it reduces to 56 | $$ 57 | T(n) = \Theta(n \log n) 58 | $$ 59 | \subsubsection*{Case 3, dominated by the 2nd term} 60 | If: 61 | $$f(n) = \omega(n^{\log_b a})$$ 62 | 63 | where in the condition it is $\omega$ rather than $\Omega$. \\\\ 64 | And with regularity condition: 65 | $$f\left(\frac{n}{b}\right) \le k f(n)$$ 66 | 67 | for some constant $k < 1$ and sufficiently large $n$\\\\ 68 | Then: 69 | $$T\left(n \right) = \Theta\left(f(n) \right)$$ 70 | \section{Useful Math Equations} 71 | \runinhead{Logarithm.} 72 | \begin{align*} 73 | \log_b mn &= \log_b m + \log_b n \\ 74 | \log_b m^n &= n \log_b m \\ 75 | \log_b a &= \frac{\ln a}{\ln b} 76 | \end{align*} 77 | 78 | \runinhead{Euler.} 79 | $$ 80 | \frac{1}{2}+\frac{1}{3}+\frac{1}{4} + ... + \frac{1}{n} = \ln{n} 81 | $$ 82 | \runinhead{Logarithm power.} 83 | $$ 84 | a^{\log_b n} = n^{\log_b a} 85 | $$ 86 | 87 | Proof: 88 | \begin{align*} 89 | a^{\log_b n} &= n^{\log_b a} \\ 90 | \Leftarrow \ln{a^{\log_b n}} &= \ln{n^{\log_b a}}\\ 91 | \Leftarrow \frac{\ln n}{\ln b}\ln a &=\frac{\ln a}{\ln b}\ln n 92 | \end{align*} 93 | 94 | \runinhead{Discrete to continuous.} 95 | 96 | if $f(x)$ is monotonously decreasing, then 97 | $$ 98 | \int_1^{+\infty}f(x) \diff x \leq \sum_{i=1}^{+\infty} f(i) \leq f(1) + \int_1^{+\infty}f(x) \diff x 99 | $$ 100 | -------------------------------------------------------------------------------- /commons/code_style.sty: -------------------------------------------------------------------------------- 1 | \ProvidesPackage{code_style}[2015/07/07 v1.0 code style macros] 2 | \usepackage[utf8]{inputenc} 3 | \usepackage{setspace} 4 | 5 | 6 | % Default fixed font does not support bold face 7 | \DeclareFixedFont{\ttb}{T1}{txtt}{bx}{n}{8} % for bold 8 | \DeclareFixedFont{\ttm}{T1}{txtt}{m}{n}{8} % for normal 9 | 10 | % Custom colors 11 | \usepackage{color} 12 | \definecolor{deepblue}{rgb}{0,0,0.5} 13 | \definecolor{deepred}{rgb}{0.6,0,0} 14 | \definecolor{deepgreen}{rgb}{0,0.5,0} 15 | \definecolor{lightgray}{rgb}{0.7,0.7,0.7} 16 | 17 | \usepackage{listings} 18 | % Python style for highlighting 19 | \newcommand\pythonstyle{\lstset{ 20 | language=Python, 21 | sensitive=true, 22 | columns=flexible, 23 | alsoletter={_}, 24 | basicstyle=\setstretch{0.8}\ttb, 25 | morekeywords={super, assert, self, yield, as, True, False}, % Add keywords here 26 | deletekeywords={is}, 27 | deletekeywords=[2]{min, max, next, sum}, 28 | keywordstyle=\ttm\color{deepblue}, 29 | literate={\_}{\ttb{\char`_}}1{-}{\ttb{-}}1{<}{\ttb{<}}1{>}{\ttb{>}}1{\\}{\ttb{\char`\\}}1{\{}{\ttb{\char`\{}}1{\}}{\ttb{\char`\}}}1{\\}{{\ttb\color{gray!20}{\char`\\}}}1, 30 | keepspaces=true, 31 | commentstyle=\ttm\color{lightgray}, 32 | emph={}, % Custom highlighting 33 | emphstyle=\ttm\color{deepred}, % Custom highlighting style 34 | stringstyle=\color{deepgreen}, 35 | % frame=tb, % Any extra options here 36 | showstringspaces=false 37 | }} 38 | 39 | % Python environment 40 | \lstnewenvironment{python}[1][]{ 41 | \pythonstyle 42 | \lstset{#1} 43 | }{} 44 | 45 | % Python for external files 46 | \newcommand\pythonexternal[2][]{ 47 | { 48 | \pythonstyle 49 | \lstinputlisting[#1]{#2} 50 | } 51 | } 52 | 53 | % Python for inline 54 | %% avoid white space in \newcommand 55 | \newcommand\pythoninline[1]{\pythonstyle\lstinline!#1!} 56 | 57 | % alias 58 | \newcommand{\pyinline}{\pythoninline} 59 | \newcommand{\pyexternal}{\pythonexternal} 60 | 61 | \newcommand\javastyle{\lstset{ 62 | language=Java, 63 | basicstyle=\setstretch{0.8}\ttb, 64 | morekeywords={}, % Add keywords here 65 | keywordstyle=\ttm\color{deepblue}, 66 | columns=flexible, 67 | literate={\_}{\ttb{\char`_}}1{-}{\ttb{-}}1{<}{\ttb{<}}1{>}{\ttb{>}}1{\\}{\ttb{\char`\\}}1{\{}{\ttb{\char`\{}}1{\}}{\ttb{\char`\}}}1, 68 | keepspaces=true, 69 | commentstyle=\ttm, 70 | emph={}, % Custom highlighting 71 | emphstyle=\ttm\color{deepred}, % Custom highlighting style 72 | stringstyle=\color{deepgreen}, 73 | % frame=tb, % Any extra options here 74 | showstringspaces=false 75 | }} 76 | 77 | % Java environment 78 | \lstnewenvironment{java}[1][]{ 79 | \javastyle 80 | \lstset{#1} 81 | }{} 82 | 83 | % Java for external files 84 | \newcommand\javaexternal[2][]{ 85 | { 86 | \javastyle 87 | \lstinputlisting[#1]{#2} 88 | } 89 | } 90 | 91 | % Java for inline 92 | %% avoid white space in \newcommand 93 | \newcommand\javainline[1]{\javastyle\lstinline!#1!} -------------------------------------------------------------------------------- /algo-quicksheet.tex: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%% editor.tex %%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % 3 | % sample root file for the contributions of a "contributed volume" 4 | % 5 | % Use this file as a template for your own input. 6 | % 7 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%% Springer %%%%%%%%%%%%%%%%%%%%%%%%%% 8 | 9 | 10 | % RECOMMENDED %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 11 | \documentclass[graybox, envcountchap, twocolum]{styles/svmult} 12 | 13 | % general metadata: 14 | \author{Author: idf@github} 15 | \title{Algorithm Quicksheet} 16 | \subtitle{Classical equations, diagrams and patterns in algorithms} 17 | % choose options for [] as required from the list 18 | % in the Reference Guide 19 | 20 | \usepackage{amssymb,amsmath,bm} 21 | \DeclareMathAlphabet{\mathcal}{OMS}{cmsy}{m}{n} 22 | \usepackage{textcomp} 23 | \newcommand\abs[1]{\left\lvert#1\right\rvert} 24 | \usepackage{longtable} 25 | \usepackage{algorithm2e} 26 | \usepackage{tocbibind} 27 | \usepackage[toc]{multitoc} 28 | \usepackage{commons/commons} 29 | \usepackage{commons/code_style} 30 | \usepackage{style/customized} 31 | \usepackage{listings} 32 | \usepackage{soul} 33 | \usepackage{tikz} 34 | \usetikzlibrary{automata,positioning,arrows} 35 | \usepackage{booktabs} 36 | \usetikzlibrary{patterns, positioning, arrows.meta} 37 | \lstset{columns=fullflexible, commentstyle=\rm, basicstyle=\footnotesize} % remove columns=fullflexible for monospace 38 | 39 | \usepackage{titlesec} 40 | \titlespacing*{\section}{0pt}{5\baselineskip}{\baselineskip} 41 | 42 | \renewcommand{\bibname}{References} 43 | \usepackage{mathptmx} % selects Times Roman as basic font 44 | \usepackage{helvet} % selects Helvetica as sans-serif font 45 | \usepackage{courier} % selects Courier as typewriter font 46 | %\usepackage{type1cm} % activate if the above 3 fonts are 47 | % not available on your system 48 | 49 | \usepackage{makeidx} % allows index generation 50 | \usepackage{graphicx} % standard LaTeX graphics tool 51 | % when including figure files 52 | \usepackage[justification=centering]{caption} 53 | \usepackage{subfig} 54 | \usepackage{multicol} % used for the two-column index 55 | \usepackage{multirow} 56 | \usepackage[bottom]{footmisc}% places footnotes at page bottom 57 | \usepackage{comment} 58 | \usepackage{slashed} 59 | \synctex=1 60 | %\excludecomment{figure} 61 | \graphicspath{{figures/}} 62 | \newcommand\AND{\ \&\ } 63 | \newcommand\OR{\ |\ } 64 | \newcommand\XOR{\wedge} 65 | \newcommand\NOT{\ensuremath{\mathord{\sim}}} 66 | \newcommand\SHIFTL{\ll} 67 | \newcommand\SHIFTR{\gg} 68 | \newcommand{\dd}{\mathrm{d}} 69 | % see the list of further useful packages in the Reference Guide 70 | 71 | \makeindex % used for the subject index 72 | % please use the style svind.ist with 73 | % your makeindex program 74 | 75 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 76 | \usepackage[normalem]{ulem} 77 | \usepackage{color} 78 | 79 | \begin{document} 80 | 81 | \frontmatter%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 82 | 83 | \include{titlepage} 84 | \include{dedic} 85 | %\include{foreword} 86 | \include{preface} 87 | %\include{acknow} 88 | 89 | \tableofcontents 90 | %\include{cblist} 91 | %\include{acronym} 92 | \include{notation} 93 | 94 | 95 | \mainmatter %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 96 | %\include{part} 97 | % complexity 98 | \include{chapterTimeComplexity} 99 | \include{chapterMemoryComplexity} 100 | 101 | % data structure 102 | \include{chapterBasicDataStructures} 103 | \include{chapterLinkedList} 104 | \include{chapterHeap} 105 | \include{chapterTree} 106 | 107 | % algorithm 108 | \include{chapterSort} 109 | \include{chapterSearch} 110 | \include{chapterArray} 111 | \include{chapterString} 112 | \include{chapterMath} 113 | \include{chapterArithmetic} 114 | \include{chapterCombinatorics} 115 | \include{chapterProbability} 116 | \include{chapterBitManipulation} 117 | \include{chapterGreedy} 118 | \include{chapterBacktracking} 119 | \include{chapterGraph} 120 | \include{chapterDynamicProgramming} 121 | \include{chapterInterval} 122 | 123 | 124 | \backmatter%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 125 | \appendix 126 | \include{chapterBalancedSearchTree} 127 | \include{chapterGeneral} 128 | \include{chapterDivideAndConquer} 129 | \include{glossary} 130 | \include{acronym} 131 | \printindex 132 | 133 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 134 | 135 | \end{document} 136 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | # External tool builders 19 | .externalToolBuilders/ 20 | 21 | # Locally stored "Eclipse launch configurations" 22 | *.launch 23 | 24 | # CDT-specific 25 | .cproject 26 | 27 | # PDT-specific 28 | .buildpath 29 | 30 | 31 | ################# 32 | ## Visual Studio 33 | ################# 34 | 35 | ## Ignore Visual Studio temporary files, build results, and 36 | ## files generated by popular Visual Studio add-ons. 37 | 38 | # User-specific files 39 | *.suo 40 | *.user 41 | *.sln.docstates 42 | 43 | # Build results 44 | 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | x64/ 48 | build/ 49 | [Bb]in/ 50 | [Oo]bj/ 51 | 52 | # MSTest test Results 53 | [Tt]est[Rr]esult*/ 54 | [Bb]uild[Ll]og.* 55 | 56 | *_i.c 57 | *_p.c 58 | *.ilk 59 | *.meta 60 | *.obj 61 | *.pch 62 | *.pdb 63 | *.pgc 64 | *.pgd 65 | *.rsp 66 | *.sbr 67 | *.tlb 68 | *.tli 69 | *.tlh 70 | *.tmp 71 | *.tmp_proj 72 | *.log 73 | *.vspscc 74 | *.vssscc 75 | .builds 76 | *.pidb 77 | *.log 78 | *.scc 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | *.ncrunch* 108 | .*crunch*.local.xml 109 | 110 | # Installshield output folder 111 | [Ee]xpress/ 112 | 113 | # DocProject is a documentation generator add-in 114 | DocProject/buildhelp/ 115 | DocProject/Help/*.HxT 116 | DocProject/Help/*.HxC 117 | DocProject/Help/*.hhc 118 | DocProject/Help/*.hhk 119 | DocProject/Help/*.hhp 120 | DocProject/Help/Html2 121 | DocProject/Help/html 122 | 123 | # Click-Once directory 124 | publish/ 125 | 126 | # Publish Web Output 127 | *.Publish.xml 128 | *.pubxml 129 | 130 | # NuGet Packages Directory 131 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 132 | #packages/ 133 | 134 | # Windows Azure Build Output 135 | csx 136 | *.build.csdef 137 | 138 | # Windows Store app package directory 139 | AppPackages/ 140 | 141 | # Others 142 | sql/ 143 | *.Cache 144 | ClientBin/ 145 | [Ss]tyle[Cc]op.* 146 | ~$* 147 | *~ 148 | *.dbmdl 149 | *.[Pp]ublish.xml 150 | *.pfx 151 | *.publishsettings 152 | 153 | # RIA/Silverlight projects 154 | Generated_Code/ 155 | 156 | # Backup & report files from converting an old project file to a newer 157 | # Visual Studio version. Backup files are not needed, because we have git ;-) 158 | _UpgradeReport_Files/ 159 | Backup*/ 160 | UpgradeLog*.XML 161 | UpgradeLog*.htm 162 | 163 | # SQL Server files 164 | App_Data/*.mdf 165 | App_Data/*.ldf 166 | 167 | ############# 168 | ## Windows detritus 169 | ############# 170 | 171 | # Windows image file caches 172 | Thumbs.db 173 | ehthumbs.db 174 | 175 | # Folder config file 176 | Desktop.ini 177 | 178 | # Recycle Bin used on file shares 179 | $RECYCLE.BIN/ 180 | 181 | # Mac crap 182 | .DS_Store 183 | 184 | 185 | ############# 186 | ## Python 187 | ############# 188 | 189 | *.py[co] 190 | 191 | # Packages 192 | *.egg 193 | *.egg-info 194 | dist/ 195 | build/ 196 | eggs/ 197 | parts/ 198 | var/ 199 | sdist/ 200 | develop-eggs/ 201 | .installed.cfg 202 | 203 | # Installer logs 204 | pip-log.txt 205 | 206 | # Unit test / coverage reports 207 | .coverage 208 | .tox 209 | 210 | #Translations 211 | *.mo 212 | 213 | #Mr Developer 214 | .mr.developer.cfg 215 | log/ 216 | data/ 217 | ############# 218 | ## Python 219 | ############# 220 | venv 221 | *.pyc 222 | staticfiles 223 | .env 224 | *.exe 225 | 226 | 227 | ############# 228 | ## Sublime 229 | ############# 230 | # Ignore production server environment files 231 | uwsgi_params 232 | # sublime text 233 | *.sublime-workspace 234 | 235 | 236 | ############# 237 | ## Idea 238 | ############# 239 | .idea 240 | .idea/ 241 | *.eml 242 | # IntelliJ docs require that *.iml shall be included in the source control. 243 | target/ 244 | 245 | ############# 246 | ## Bower 247 | ############# 248 | bower_components/ 249 | 250 | ############# 251 | ## MATLAB 252 | ############# 253 | *.asv 254 | 255 | ############# 256 | ## BaKoMa 257 | ############# 258 | *.ddf 259 | *.idx 260 | *.ilg 261 | *.ind 262 | *.Backup 263 | *.aux 264 | *.toc 265 | *.out 266 | *.bb 267 | *.dvi 268 | *.dvv -------------------------------------------------------------------------------- /chapterBasicDataStructures.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Basic Data Structures} 3 | 4 | 5 | \section{Introduction} 6 | Abstract Data Types (ADT): 7 | \begin{enumerate} 8 | \item Queue 9 | \item Stack 10 | \item HashMap 11 | \end{enumerate} 12 | Implementation (for both queue and stack): 13 | \begin{enumerate} 14 | \item Linked List 15 | \item Resizing Array: 16 | \begin{enumerate} 17 | \item Doubling: when full (100\%). 18 | \item Halfing: when one-quarter full (25\%). 19 | \end{enumerate} 20 | \end{enumerate} 21 | Python Library: 22 | \begin{enumerate} 23 | \item \pythoninline{collections.deque} \footnote{The lowercase and uppercase naming in Python collections are awkward: \href{http://stackoverflow.com/questions/18953681/naming-convention-in-collections-why-are-some-lowercase-and-others-capwords}{discussion}.} 24 | \item \pythoninline{list} 25 | \item \pythoninline{dict, OrderedDict, defaultdict} 26 | \end{enumerate} 27 | Java Library: 28 | \begin{enumerate} 29 | \item \javainline{java.util.Stack} 30 | \item \javainline{java.util.LinkedList} 31 | \item \javainline{java.util.HashMap; java.util.TreeMap} 32 | \end{enumerate} 33 | 34 | \section{Python Library} 35 | \begin{python} 36 | from collections import defaultdict 37 | 38 | # defaultdict take constructor func as param 39 | counter = defaultdict(int) 40 | G = defaultdict(list) # graph of v -> neighbors 41 | G = defaultdict(dict) # G[s][e] -> weight 42 | G = defaultdict(lambda: defaultdict(int)) # G[s][e] -> weight 43 | 44 | 45 | from collections import deque 46 | path = deque() 47 | path.appendleft("a") 48 | path.append("b") 49 | path.popleft() 50 | path.pop() 51 | path = deque(sorted(path)) 52 | 53 | 54 | import heapq 55 | l = [] # list 56 | heapq.heappush(l, "a") 57 | heapq.heappop(l) 58 | heapq.heapify(l) 59 | heapq.heappushpop(l, "b") 60 | heapq.nsmallest(3, l) 61 | \end{python} 62 | 63 | \section{Stack} 64 | \subsection{Stack and Recursion} 65 | How a compiler implements a function: 66 | \begin{enumerate} 67 | \item Function call: push local environment and return address 68 | \item Return: pop return address and local environment. 69 | \end{enumerate} 70 | 71 | Recursive function: function calls itself. It can always be implemented by using an explicit stack to remove recursion. 72 | 73 | Stack can convert recursive DFS to iterative. 74 | 75 | \subsection{Usage} 76 | The core philosophy of using stack is to maintain a relationship invariant among stack element. 77 | 78 | The \textbf{relationship invariants} can be: 79 | \begin{enumerate} 80 | \item strictly asc/ strictly desc 81 | \item non-desc/ non-asc 82 | \end{enumerate} 83 | 84 | \subsection{Unpaired Parentheses} 85 | \runinhead{Longest Valid Parentheses.} 86 | Given a string containing just the characters `(' and `)', find the length of the longest valid (well-formed) parentheses substring. Core clues: 87 | \begin{enumerate} 88 | \item \textbf{Invariant}: Stack holds the INDEX of UNPAIRED brackets, either ( or ). \footnote{The stack should always hold indices, rather than values.} 89 | \item Thus, \pyinline{stk[-1]} stores the last unpaired bracket. 90 | \item The length of the well-formed parentheses is: if currently \textbf{valid}, current scanning index \pyinline{idx} minus the last \textbf{invalid} index of bracket \pyinline{stk[-1]} 91 | \end{enumerate} 92 | \begin{python} 93 | def longestValidParentheses(self, s): 94 | stk = [] 95 | maxa = 0 96 | for idx, val in enumerate(s): 97 | if val == ")" and stk and s[stk[-1]] == "(": 98 | stk.pop() 99 | if not stk: 100 | maxa = max(maxa, idx+1) 101 | else: 102 | maxa = max(maxa, idx-stk[-1]) 103 | else: 104 | stk.append(idx) 105 | 106 | return maxa 107 | 108 | \end{python} 109 | 110 | \section{Map} 111 | \subsection{Math relations} 112 | \textbf{1-1 Map}. Mathematically, full projection. One map, dual entries. 113 | \begin{python} 114 | class OneToOneMap: 115 | def __init__(self): 116 | self.m = {} # keep a single map 117 | 118 | def set(self, a, b): 119 | self.m[a] = b 120 | self.m[b] = a 121 | 122 | def get(self, a): 123 | return self.m.get(a) 124 | \end{python} 125 | \subsection{Operations} 126 | \runinhead{Sorting by value.} Sort the map entries by values \pyinline{itemgetter}. 127 | \begin{python} 128 | from operators import itemgetter 129 | sorted(hm.items(), key=itemgetter(1), reverse=True) 130 | sorted(hm.items(), key=lambda x: x[1], reverse=True) 131 | \end{python} 132 | \section{Monotonic Stack} 133 | Ref array \ref{monostack} 134 | \section{Monotonic Queue} 135 | Ref array \ref{monoqueue} 136 | -------------------------------------------------------------------------------- /commons/.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | # External tool builders 19 | .externalToolBuilders/ 20 | 21 | # Locally stored "Eclipse launch configurations" 22 | *.launch 23 | 24 | # CDT-specific 25 | .cproject 26 | 27 | # PDT-specific 28 | .buildpath 29 | 30 | 31 | ################# 32 | ## Visual Studio 33 | ################# 34 | 35 | ## Ignore Visual Studio temporary files, build results, and 36 | ## files generated by popular Visual Studio add-ons. 37 | 38 | # User-specific files 39 | *.suo 40 | *.user 41 | *.sln.docstates 42 | 43 | # Build results 44 | 45 | [Dd]ebug/ 46 | [Rr]elease/ 47 | x64/ 48 | build/ 49 | [Bb]in/ 50 | [Oo]bj/ 51 | 52 | # MSTest test Results 53 | [Tt]est[Rr]esult*/ 54 | [Bb]uild[Ll]og.* 55 | 56 | *_i.c 57 | *_p.c 58 | *.ilk 59 | *.meta 60 | *.obj 61 | *.pch 62 | *.pdb 63 | *.pgc 64 | *.pgd 65 | *.rsp 66 | *.sbr 67 | *.tlb 68 | *.tli 69 | *.tlh 70 | *.tmp 71 | *.tmp_proj 72 | *.log 73 | *.vspscc 74 | *.vssscc 75 | .builds 76 | *.pidb 77 | *.log 78 | *.scc 79 | 80 | # Visual C++ cache files 81 | ipch/ 82 | *.aps 83 | *.ncb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | 88 | # Visual Studio profiler 89 | *.psess 90 | *.vsp 91 | *.vspx 92 | 93 | # Guidance Automation Toolkit 94 | *.gpState 95 | 96 | # ReSharper is a .NET coding add-in 97 | _ReSharper*/ 98 | *.[Rr]e[Ss]harper 99 | 100 | # TeamCity is a build add-in 101 | _TeamCity* 102 | 103 | # DotCover is a Code Coverage Tool 104 | *.dotCover 105 | 106 | # NCrunch 107 | *.ncrunch* 108 | .*crunch*.local.xml 109 | 110 | # Installshield output folder 111 | [Ee]xpress/ 112 | 113 | # DocProject is a documentation generator add-in 114 | DocProject/buildhelp/ 115 | DocProject/Help/*.HxT 116 | DocProject/Help/*.HxC 117 | DocProject/Help/*.hhc 118 | DocProject/Help/*.hhk 119 | DocProject/Help/*.hhp 120 | DocProject/Help/Html2 121 | DocProject/Help/html 122 | 123 | # Click-Once directory 124 | publish/ 125 | 126 | # Publish Web Output 127 | *.Publish.xml 128 | *.pubxml 129 | 130 | # NuGet Packages Directory 131 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 132 | #packages/ 133 | 134 | # Windows Azure Build Output 135 | csx 136 | *.build.csdef 137 | 138 | # Windows Store app package directory 139 | AppPackages/ 140 | 141 | # Others 142 | sql/ 143 | *.Cache 144 | ClientBin/ 145 | [Ss]tyle[Cc]op.* 146 | ~$* 147 | *~ 148 | *.dbmdl 149 | *.[Pp]ublish.xml 150 | *.pfx 151 | *.publishsettings 152 | 153 | # RIA/Silverlight projects 154 | Generated_Code/ 155 | 156 | # Backup & report files from converting an old project file to a newer 157 | # Visual Studio version. Backup files are not needed, because we have git ;-) 158 | _UpgradeReport_Files/ 159 | Backup*/ 160 | UpgradeLog*.XML 161 | UpgradeLog*.htm 162 | 163 | # SQL Server files 164 | App_Data/*.mdf 165 | App_Data/*.ldf 166 | 167 | ############# 168 | ## Java 169 | ############# 170 | *.class 171 | 172 | 173 | ############# 174 | ## Windows detritus 175 | ############# 176 | 177 | # Windows image file caches 178 | Thumbs.db 179 | ehthumbs.db 180 | 181 | # Folder config file 182 | Desktop.ini 183 | 184 | # Recycle Bin used on file shares 185 | $RECYCLE.BIN/ 186 | 187 | # Mac crap 188 | .DS_Store 189 | 190 | 191 | ############# 192 | ## Python 193 | ############# 194 | 195 | *.py[co] 196 | 197 | # Packages 198 | *.egg 199 | *.egg-info 200 | dist/ 201 | build/ 202 | eggs/ 203 | parts/ 204 | var/ 205 | sdist/ 206 | develop-eggs/ 207 | .installed.cfg 208 | 209 | # Installer logs 210 | pip-log.txt 211 | 212 | # Unit test / coverage reports 213 | .coverage 214 | .tox 215 | 216 | #Translations 217 | *.mo 218 | 219 | #Mr Developer 220 | .mr.developer.cfg 221 | log/ 222 | data/ 223 | ############# 224 | ## Python 225 | ############# 226 | venv 227 | *.pyc 228 | staticfiles 229 | .env 230 | *.exe 231 | 232 | 233 | ############# 234 | ## Sublime 235 | ############# 236 | # Ignore production server environment files 237 | uwsgi_params 238 | # sublime text 239 | *.sublime-workspace 240 | 241 | 242 | ############# 243 | ## Idea 244 | ############# 245 | .idea 246 | .idea/ 247 | *.eml 248 | # IntelliJ docs require that *.iml shall be included in the source control. 249 | target/ 250 | 251 | ############# 252 | ## Bower 253 | ############# 254 | bower_components/ 255 | 256 | ############# 257 | ## MATLAB 258 | ############# 259 | *.asv 260 | 261 | ############# 262 | ## BaKoMa 263 | ############# 264 | *.ddf 265 | *.idx 266 | *.ilg 267 | *.ind 268 | *.Backup 269 | *.aux 270 | *.toc 271 | *.out 272 | *.bb -------------------------------------------------------------------------------- /chapterGreedy.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Greedy} 3 | 4 | \section{Introduction} 5 | Philosophy: choose the best options at the current state without backtracking/reverting the choice in the future. 6 | 7 | A greedy algorithm is an algorithm that follows the problem solving heuristic of making the \textbf{local optimal} choice at each stage with the hope of finding a global optimum. 8 | 9 | Greedy algorithm is a degraded DP since the the past substructure is not remembered. 10 | 11 | \subsection{Proof} 12 | The proof technique for the correctness of the greedy method. 13 | 14 | Proof by contradiction, the solution of greedy algorithm is $\mat{G}$ and the optimal solution is $\mat{O}$, $\mat{O}\neq \mat{G}$ (or relaxed to $|\mat{O}|\neq |\mat{G}|$). 15 | 16 | Two general technique it is impossible to have $\mat{O}\neq \mat{G}$: 17 | \begin{enumerate} 18 | \item \rih{Exchange method}: Greedy-Choice Property. Any optimal solution can be transformed into the solution produced by the greedy algorithm without worsening its quality. 19 | \item \rih{Stays-ahead method}: Optimal Substructure. the solution constructed by the greedy algorithm is consistently as good as or better than any other solution at every step or position 20 | \end{enumerate} 21 | 22 | \section{Extreme First} 23 | \runinhead{Schedule jobs with duration and deadlines.} Given a list of jobs $A$ and each $A_i$ has a (duration, deadline). Determine whether we can finish all the jobs. 24 | 25 | Greedily choose the earliest deadline. Why earliest-deadline-first is the only schedule we need to test? 26 | \begin{enumerate} 27 | \item Deadline inversions are harmless to fix. Call an \textbf{inversion} two consecutive jobs $A_i, A_j$ where the first one’s $A_i$ deadline is later than the second one’s $A_j$ . \textbf{Swap} such a pair. 28 | \item $A_j$ with the earlier deadline now finishes \textbf{sooner}, so it meets its deadline. 29 | \item $A_i$ \textbf{inherits} $A_j$'s old completion time ($A_i.t + A_j.t = A_j.t + A_i.t$) and $A_i$ has a later deadline; thus $A_i$ can finish. 30 | \item $\Ra$ Result: a swap never turns a feasible schedule into an infeasible one. 31 | \end{enumerate} 32 | 33 | 34 | \runinhead{Rearranging String $k$ distance apart.} Given a non-empty string $s$ and an integer $k$, rearrange the string such that the same characters are at least distance 35 | $k$ from each other. 36 | 37 | \textbf{Core clues.} 38 | \begin{enumerate} 39 | \item The char with the most count put to the result first - greedy. 40 | \item Fill every $k$ slots as cycle - greedily fill high-count char as many as possible. 41 | \end{enumerate} 42 | 43 | \textbf{Implementations.} 44 | \begin{enumerate} 45 | \item Use a heap as a way to get the char of the most count. 46 | \item \pyinline{while} loop till exhaust the heap 47 | \end{enumerate} 48 | 49 | \begin{python} 50 | def rearrangeString(self, s, k): 51 | if not s or k == 0: return s 52 | 53 | d = defaultdict(int) 54 | for c in s: 55 | d[c] += 1 56 | 57 | h = [] 58 | for char, cnt in d.items(): 59 | heapq.heappush(h, Val(cnt, char)) 60 | 61 | ret = [] 62 | while h: 63 | cur = [] 64 | for _ in range(k): 65 | if not h: 66 | return "".join(ret) if len(ret) == len(s) else "" 67 | 68 | e = heapq.heappop(h) 69 | ret.append(e.val) 70 | e.cnt -= 1 71 | if e.cnt > 0: 72 | cur.append(e) 73 | 74 | for e in cur: 75 | heapq.heappush(h, e) 76 | 77 | return "".join(ret) 78 | 79 | \end{python} 80 | \subsection{Coverage} 81 | \runinhead{Minimum to cover.} Given $n$ representing the garden starts at the point 0 and ends at the point $n$. An array $ranges$ of length $n + 1$ where $ranges_i$ means the i-th tap can water the area $[i - ranges_i, i + ranges_i]$ 82 | 83 | Find the minimum number of taps that should be open to water the whole garden. 84 | 85 | \rih{Core clues:} 86 | \begin{enumerate} 87 | \item Each ranges cover $[start, end]$ $\Ra$ need a map either by start or end. 88 | \item Minimum taps to cover is maximum covering range $\Ra$ greedy 89 | \end{enumerate} 90 | \begin{python} 91 | def minTaps(self, n, ranges): 92 | # reaches: max end by start 93 | reaches = [0 for _ in range(n + 1)] 94 | for i, r in enumerate(ranges): 95 | s = max(0, i - r) 96 | e = min(n+1, i + r + 1) # [i-r, i+r+1) 97 | reaches[s] = max(reaches[s], e) 98 | 99 | ret = 0 100 | lo, hi = 0, 1 # [lo, hi) 101 | maxa = hi 102 | # Greedily extend coverage until we reach n 103 | while maxa <= n: 104 | # search for one tap that reaches farthest 105 | for i in range(lo, hi): 106 | maxa = max(maxa, reaches[i]) 107 | 108 | # cannot extend coverage 109 | if maxa <= hi: 110 | return -1 111 | ret += 1 112 | 113 | lo = hi 114 | hi = maxa 115 | 116 | return ret 117 | \end{python} 118 | 119 | Note that the dictionary name can be short form by keys as \pyinline{starts} or values as \pyinline{reaches} and the latter is preferred. 120 | 121 | -------------------------------------------------------------------------------- /chapterProbability.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Probability} 3 | 4 | 5 | \section{Shuffle} 6 | Equal probability shuffle algorithm. 7 | 8 | \subsection{Incorrect naive solution} 9 | Swap current card $A_i$ with a random card from the entire deck $A$. 10 | 11 | \begin{python} 12 | def shuffle(A): 13 | n = len(A) 14 | for i in range(n): 15 | j = random.randrange(n) 16 | A[i], A[j] = A[j], A[i] 17 | \end{python} 18 | 19 | Consider 3 cards, the easiest proof that this algorithm does not produce a uniformly random permutation is that it generates $3^3=27$ possible plans (consider steps in plans, duplicated result included), but there are only 3! = 6 permutations. Since $27\%6 \neq 0$, there must be some permutation is that is picked too much, and some that is picked too little. 20 | 21 | In general, it generates $n^n$ possible plans, and 22 | $$ 23 | n^n \% n! \neq 0 24 | $$ 25 | \subsection{Uniform Shuffle - Knuth Shuffle} 26 | $$ 27 | |..closed..|i..........| 28 | $$ 29 | Knuth shuffling algorithm guarantees to rearrange the elements in uniformly random order. 30 | 31 | \rih{Intuition}. Similar to drawing lots, the order of drawing lots does not affect the probability of a given outcome. 32 | 33 | Core clues: 34 | \begin{enumerate} 35 | \item choose index uniformly $\in [i, N)$. 36 | \item just like shuffling the poker card. 37 | \end{enumerate} 38 | 39 | \begin{python} 40 | def shuffle(A): 41 | n = len(A) 42 | for i in range(n): 43 | j = random.randrange(i, n) 44 | A[i], A[j] = A[j], A[i] 45 | \end{python} 46 | 47 | \runinhead{Proof:} 48 | 49 | $n!$ permutations. 50 | 51 | \subsection{Random Maximum} 52 | Find the index of maximum number in an array with a probability of $\frac{1}{\#maxima}$, instead of the first seen index. 53 | 54 | \rih{Intuition}. Consider the maximums array of length $L=k$, the \pyinline{current} $max$ has the probability $\frac{1}{k}$ of being selected. When moving to $L=k+1$, the \pyinline{current} $max$ has the probability $\frac{1}{k+1}$ of being selected, the \pyinline{previous} $max$ has the probility of $\frac{k}{k+1}$ to stay. Then the \pyinline{previous} $max$ has the new updated probability of being selected as: 55 | $$ 56 | P(max_k) = \frac{1}{k} \cdot \frac{k}{k+1} = \frac{1}{k+1} 57 | $$ 58 | \begin{python} 59 | def random_max(A): 60 | # k is the counter 61 | maxa, maxa_idx, k = A[0], 0, 1 62 | for i in range(1, len(A)): 63 | if maxa < A[i]: 64 | maxa, maxa_idx, k = A[i], i, 1 65 | elif maxa == A[i]: 66 | k += 1 67 | if random.random() < float(1)/k: 68 | maxa_idx = i 69 | 70 | return maxa_idx 71 | 72 | \end{python} 73 | 74 | 75 | \runinhead{Proof.} Prove via mathematical induction. 76 | 77 | That is, assuming it works for any array of size $N$, prove it works for any array of size $N+1$. 78 | 79 | So, given an array of size $N+1$, think of it as a sub-array of size $N$ followed a new element at the end. By assumption, your algorithm uniformly selects one of the max elements of the sub-array... And then it behaves as follows: 80 | 81 | If the new element is larger than the max of the sub-array, return that element. This is obviously correct. 82 | 83 | If the new element is less than the max of the sub-array, return the result of the algorithm on the sub-array. Also obviously correct. 84 | 85 | The only slightly tricky part is when the new element equals the max element of the sub-array. In this case, let the number of max elements in the sub-array be $k$. Then, by hypothesis, your algorithm selected one of them with probability $\frac{1}{k}$. By keeping that same element with probability $\frac{k}{k+1}$, you make the overall probability of selecting that same element equal $$\frac{1}{k} \cdot \frac{k}{k+1} = \frac{1}{k+1}$$, as desired. You also select the last element with the same probability. 86 | 87 | To complete the inductive proof, just verify the algorithm works on an array of size 1. 88 | 89 | \runinhead{Random pick index.} Similar question - given an array of integers with possible duplicates, randomly output the index of a given target number. 90 | \begin{python} 91 | def pick(self, target): 92 | sz = 0 93 | ret = None 94 | for idx, val in enumerate(A): 95 | if val == target: 96 | sz += 1 97 | rv = random.randrange(0, sz) 98 | if rv == 0: # or any number < sz 99 | ret = idx 100 | 101 | return ret 102 | \end{python} 103 | 104 | \section{Sampling} 105 | \subsection{Reservoir Sampling} 106 | Sample $k$ from $A$, where the length of $A$ is either very large or unknown or dynamic. 107 | \begin{python} 108 | def reservoir_sample(A, k): 109 | R = A[:k] # k for size 110 | 111 | for i in range(k, len(A)): 112 | # rv for random variable 113 | rv = random.randrange(0, i+1) 114 | if rv < k: 115 | R[rv] = A[i] 116 | \end{python} 117 | \rih{Intuition}. 118 | When processing index $i$ and $A_i$, we have the elements of length $i$ already been scanned. Every element $\in [A_0, A_1, ..., A_i]$ has a probability in the reservoir of: 119 | $$ 120 | P_{select} = \frac{k}{i+1} 121 | $$ 122 | 123 | \section{Distribution} 124 | \subsection{Geometric Distr} 125 | $$ 126 | P(X=k) = (1-p)^{k-1}p 127 | $$ 128 | 129 | Expected number of trials of get a specific outcome: 130 | $$ 131 | E[T] = \frac{1}{p} 132 | $$ 133 | , which is the mean of the Geometric Distr. 134 | \subsection{Binomial Distr} 135 | Notations: 136 | $$ 137 | B(n, p) 138 | $$ 139 | pmf: 140 | $$ 141 | {n \choose k}\,p^{k}(1-p)^{n-k} 142 | $$ 143 | 144 | \section{Expected Value} 145 | \subsection{Dice value} 146 | Expected value of rolling dice until getting a 3 147 | \subsection{Coupon collector's problem} 148 | Given n coupons, how many coupons do you expect to draw with replacement before having drawn each coupon at least once? 149 | 150 | $$ 151 | E[T] = \Theta(n \lg n) 152 | $$ 153 | , where $T$ is number of trial (i.e. time). 154 | 155 | Let $T$ be the time to collect all. $t_i$ be the time to collect the $i$-th new different coupon. $p_i$ be the probability of collecting the $i$-th coupon after $i-1$ coupons have been collected. Observe that: 156 | \begin{align*} 157 | p_1 &= \frac{n}{n} \\ 158 | p_2 &= \frac{n-1}{n} \\ 159 | p_i &= \frac{n-i+1}{n} 160 | \end{align*} 161 | Thus, 162 | 163 | \begin{align*} 164 | E[T] &= \sum_{i=1}^n E[t_i] \\ 165 | &= \sum \frac{1}{p_i} \\ 166 | &= n(\frac{1}{1}+\frac{1}{2}+...+\frac{1}{n}) 167 | \end{align*} 168 | 169 | \runinhead{Dice.} How many times must you roll a die until each side has appeared? 170 | -------------------------------------------------------------------------------- /chapterHeap.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Heap} 3 | 4 | \section{Introduction} 5 | Heap-ordered. Binary heap is one of the implementations of Priority Queue (ADT). The core relationship of elements in the heap: 6 | $A_{2i} \leq A_{i} \geq A_{2i+1}$. 7 | 8 | 9 | \begin{figure}[hbtp] 10 | \centering 11 | \subfloat{\includegraphics[width=0.7\linewidth]{heap}} 12 | \caption{Heap} 13 | \label{fig:heap} 14 | \end{figure} 15 | 16 | \section{Applications} 17 | \subsection{Python Heapq} 18 | Python only has built in min-heap. To use max-heap, you can: 19 | \begin{enumerate} 20 | \item Invert the number: 1 becomes -1. 21 | (usually the best solution)\item Wrap the data into another class and override \textbf{comparators}: \_\_cmp\_\_ or \_\_lt\_\_ 22 | \end{enumerate} 23 | 24 | The following code presents the wrapping method: 25 | \begin{python} 26 | from dataclasses import dataclass 27 | 28 | @dataclass 29 | class HeapValue: 30 | val: int 31 | deleted: bool = False # lazy delete 32 | 33 | def __lt__(self, other): # reverse for max-heap 34 | return not self.val < other.val 35 | \end{python} 36 | 37 | Normally the deletion by value in Python is $O(n)$, to achieve $O(\lg n)$ we can use \textbf{lazy deletion}. Before take the top of the heap, we do the following: 38 | \begin{python} 39 | while heap and heap[0].deleted: 40 | heapq.heappop(heap) 41 | \end{python} 42 | \subsection{Java Priority Queue} 43 | \begin{java} 44 | // min-heap 45 | PriorityQueue pq = new PriorityQueue<>( 46 | (o1, o2) -> o1-o2 47 | ); 48 | 49 | // max-heap 50 | PriorityQueue pq = new PriorityQueue<>( 51 | (o1, o2) -> o2-o1 52 | ); 53 | \end{java} 54 | \subsection{Stale-Checking Heap for Update} 55 | If introduceing \textbf{update} to the values in the heap, we can do stale checking as lazy validation. 56 | 57 | Consider the following: $freq_i$ represents the frequency of $A_i$ at step $i$. We want to keep track of the most frequent number at each step $i$. $A$ contains duplicagtes. $freq_i$ can be negative. 58 | \begin{python} 59 | def most_frequent_num(self, A, freq): 60 | counter = defaultdict(int) 61 | h = [] 62 | ret = [] 63 | for n, cnt in zip(A, freq): 64 | counter[n] += cnt 65 | heapq.heappush(h, (-counter[n], n)) 66 | while True: 67 | c, maxa = h[0] 68 | if c != -counter[maxa]: # stale check 69 | heapq.heappop(h) 70 | else: 71 | ret.append(maxa) 72 | break 73 | 74 | return ret 75 | \end{python} 76 | 77 | \subsection{Maintain min max in Sliding Window} 78 | Count number of continuous subarrays in $A$ s.t. within the subarray $|A_i - A_j| \leq 2, \forall i, j$. 79 | \rih{Core clues} 80 | \begin{enumerate} 81 | \item Need to maintain max and min in the sliding window $\Ra$ heap 82 | \item In sliding window $i, j$, count of all valid subarrays ending AT j $\Ra j - i + 1$ 83 | \end{enumerate} 84 | \begin{python} 85 | def continuousSubarrays(self, A): 86 | ret = 0 87 | i = 0 # starting index of the window 88 | j = 0 # ending index of the window 89 | h_min = [] # (a, idx) 90 | h_max = [] # (-a, idx) 91 | while j < len(A): 92 | heapq.heappush(h_min, (A[j], j)) 93 | heapq.heappush(h_max, (-A[j], j)) 94 | while -h_max[0][0] - h_min[0][0] > 2: 95 | # shrink 96 | i += 1 97 | while h_min[0][1] < i: 98 | heapq.heappop(h_min) 99 | while h_max[0][1] < i: 100 | heapq.heappop(h_max) 101 | 102 | ret += j-i+1 103 | j += 1 104 | 105 | return ret 106 | \end{python} 107 | Note: 3-layred nested \pyinline{while} loop. 108 | 109 | \subsection{Find $k$ smallest/largest} 110 | Partial Quicksort \ref{find-k-th} 111 | 112 | \section{Derivatives} 113 | \subsection{Heap of Linked Lists} 114 | Maintain a heap of linked lists, pop the min head, and push the head's next back to the heap. 115 | 116 | \section{Inside the Library} 117 | Assume the root \textbf{starts} at $a[1]$ rather than $a[0]$. 118 | \\ 119 | Basic operations: 120 | \begin{enumerate} 121 | \item sink()/ sift\_down() - recursive 122 | \item swim()/ sift\_up() - recursive 123 | \item build()/ heapify() - bottom-up sink() 124 | \end{enumerate} 125 | \subsection{General} 126 | The self-implemented binary heap's index usually starts at 1 rather than 0. 127 | 128 | The array representation of heap is in \textbf{level-order}. 129 | 130 | The main reason that we can use an array to represent the heap-ordered tree in a binary heap is because the tree is \textbf{complete}. 131 | 132 | Suppose that we represent a BST containing N keys using an array, with $a[0]$ empty, the root at $a[1]$. The two children of $a[k]$ will be at $a[2k]$ and $a[2k+1]$. Then, the length of the array might need to be as large as $2^N$. 133 | 134 | It is possible to have 3-heap. A 3-heap is an array representation (using 1-based indexing) of a complete 3-way tree. 135 | The children of $a[k]$ are $a[3k-1]$, $a[3k]$, and $a[3k+1]$. 136 | \begin{figure}[hbtp] 137 | \centering 138 | \subfloat{\includegraphics[width=0.9\linewidth]{heapRepr}} 139 | \caption{Heap representation} 140 | \label{fig:heap} 141 | \end{figure} 142 | 143 | \subsection{Sink (sift\_down)} 144 | Core clue: compare parent to the \textit{larger} child (because we want to maintain the heap invariant). 145 | \begin{python} 146 | def sink(self, idx): 147 | while 2*idx <= self.N: 148 | c = 2*idx 149 | if c+1 <= self.N and self.less(c, c+1): 150 | c += 1 151 | if not self.less(idx, c): 152 | return 153 | 154 | self.swap(idx, c) 155 | idx = c 156 | \end{python} 157 | We can return the \pyinline{idx} at the end to report the final index of the element. 158 | \subsection{Swim (sift\_up)} 159 | Core clue: compare child to its parent. 160 | \begin{python} 161 | def swim(self, idx): 162 | while idx > 1 and self.less(idx/2, idx): 163 | pi = idx/2 164 | self.swap(pi, idx) 165 | idx = pi 166 | \end{python} 167 | \subsection{Heapify} 168 | Core clue: bottom-up sink(). 169 | \begin{python} 170 | def heapify(self): 171 | for i in range(self.N/2, 0, -1): 172 | self.sink(i); 173 | \end{python} 174 | \runinhead{Complexity.} Heapifying \textbf{a sorted array} is the worst case for heap construction, because the root of each subheap considered sinks all the way to the bottom. The worst case complexity $\sim 2N$. 175 | 176 | Building a heap is $O(N)$ rather than $O(N \lg N)$. Intuitively, the deeper the level, the more the nodes, but the less the level to sink down. 177 | 178 | At most $\big\lceil\frac{n}{2^{h+1}}\big\rceil$ nodes of any height $h$. 179 | 180 | Proof: 181 | \begin{align*} 182 | \because \sum_{i=0}^{+\infty} {ix^i} =\frac{x}{(1-x)^2} \\ 183 | \therefore \sum_{h=0}^{\lfloor\lg n\rfloor}{\Big\lceil\frac{n}{2^{h+1}}\Big\rceil 184 | O(h)} &= O\Bigg(n\sum_{h=0}^{\lfloor\lg n\rfloor}{\frac{h}{2^h}}\Bigg) \\ 185 | &= O(n) 186 | \end{align*} 187 | -------------------------------------------------------------------------------- /chapterString.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{String} 3 | 4 | \section{Palindrome} 5 | \subsection{Palindrome anagram} 6 | \begin{itemize} 7 | \item \rih{Test palindrome anagram.} Char counter, number of odd count should $\leq 0$. 8 | \item \rih{Count palindrome anagram.} See Section-\ref{N_objects_K_types}. 9 | \item \rih{Construct palindrome anagram.} Construct all palindrome anagrams given a string \pyinline{s}. 10 | \end{itemize} 11 | 12 | \runinhead{Core Clues:} 13 | \begin{enumerate} 14 | \item Different choices of char $\Ra$ backtracking, choose the next from the char counters of \pyinline{s}. 15 | \item To avoid loop $\Ra$ jump parent char 16 | \end{enumerate} 17 | 18 | \begin{python} 19 | def backtrack(self, s, counters, pi, cur, ret): 20 | if len(cur) == len(s): 21 | ret.append(cur) 22 | return 23 | 24 | for k in counters.keys(): 25 | # jump the parent 26 | if k != pi and counters[k] > 0: 27 | for n in range(1, counters[k]/2+1): 28 | counters[k] -= n*2 29 | self.backtrack(s, counters, k, k*n+cur+k*n, ret) 30 | counters[k] += n*2 31 | \end{python} 32 | 33 | Jump within the iterations of choice to avoid dead loop. 34 | 35 | How to handle odd counter? Check outside the backtrack, and potentially raise error. 36 | 37 | \subsection{Number} 38 | \runinhead{Next Permutation.} Given a string $n$ representing an int, return the closest int (not including itself), which is a palindrome. For example $n = `123'$, return $`121'$. 39 | 40 | \begin{enumerate} 41 | \item Palindrome $\Ra$ Mirror the left half of $n$. 42 | \item 19997 has two candidates 19991 and 20002 $\Ra$ for the half, $+/-1$ for carry/borrow 43 | \item 1000 has two candidates 999 and 1001 $\Ra$ More/less digits of $n$ $\Ra$ checking numbers $10..0, 9..9$ 44 | \end{enumerate} 45 | \begin{python} 46 | def nearestPalindromic(self, s: str) -> str: 47 | l = len(s) 48 | odd_l = l & 1 49 | if odd_l: 50 | half = s[:l//2] + s[l//2] 51 | else: 52 | half = s[:l//2] 53 | 54 | def mir(half: str) -> str: 55 | if not odd_l: 56 | return half + half[::-1] 57 | else: 58 | return half + half[::-1][1:] 59 | 60 | # candidates 61 | cands = { 62 | mir(str(int(half) - 1)), 63 | mir(half), 64 | mir(str(int(half) + 1)), 65 | } 66 | cands |= { 67 | str(10 ** l + 1), 68 | str(10 ** (l - 1) - 1) 69 | } 70 | cands.discard(s) 71 | return min( 72 | cands, 73 | key=lambda e: (abs(int(s) - int(e)), int(e)) 74 | ) 75 | \end{python} 76 | \section{Anagram} 77 | \runinhead{Group of strings by anagram.} 78 | 79 | Using a frequency vector 80 | \begin{python} 81 | class Solution: 82 | def groupAnagrams(self, strs): 83 | # key: 26-tuple counts; value: list of words 84 | d = defaultdict(list) 85 | for s in strs: 86 | cnt = [0] * 26 87 | for ch in s: 88 | cnt[ord(ch) - ord('a')] += 1 89 | d[tuple(cnt)].append(s) 90 | return list(d.values()) 91 | \end{python} 92 | 93 | \section{KMP} 94 | Find the pattern $p$ in string $s$ within complexity of $O(|P|+|S|)$. 95 | 96 | \subsection{Prefix matching suffix table} 97 | Intuition: when a mismatch happens at $p_i$ vs $s_i$, don't restart from $p_0$; instead jump to the next best candidate $i$ right after the matched prefix \& suffix, keeping the already verified prefix. 98 | 99 | Let $L_i$ be the length of the longest proper prefix that matches a suffix ending at $p_i$. 100 | \begin{enumerate} 101 | \item A proper prefix is a prefix that is not equal to the whole string itself. Proper ensures that $L_i < i+1$. 102 | \item Need to maintain the longest proper prefix of a substring that is also a suffix of it. 103 | \end{enumerate} 104 | 105 | \begin{table}[h!] 106 | \centering 107 | \begin{tabular}{c|ccccccc} 108 | \toprule 109 | \textbf{i} & 0 & 1 & 2 & 3 & 4 & 5 & 6 \\ 110 | \midrule 111 | \textbf{$p_i$} & A & B & C & D & A & B & D \\ 112 | \textbf{$L_i$} & 0 & 0 & 0 & 0 & 1 & 2 & 0 \\ 113 | \bottomrule 114 | \end{tabular} 115 | \end{table} 116 | \begin{python} 117 | def kmp_lps(p): 118 | L = [0] * len(p) 119 | i = 1 120 | pre = 0 121 | while i < len(p): 122 | if p[i] == p[pre]: 123 | L[i] = pre + 1 124 | pre += 1 125 | i += 1 126 | elif pre: # fallback 127 | pre = L[pre - 1] 128 | else: # no fallback 129 | L[i] = 0 130 | i += 1 131 | 132 | return L 133 | \end{python} 134 | Time complexity: 135 | \begin{itemize} 136 | \item From code itself it appears to be $O(|P|^2)$. 137 | \item Define $\Delta = i - pre$. 138 | \item $i$ never goes backward - $i$ either forwards of stays. 139 | \item Any time $i$ doesn’t move forward, $\Delta$ must rise 140 | \item $j$ is bounded by $O(|P|)$ and $\Delta$ is bounded by $O(|P|)$ 141 | \end{itemize} 142 | \subsection{Searching algorithm} 143 | \begin{figure}[] 144 | \centering 145 | \subfloat{\includegraphics[width=0.8\linewidth]{kmp_presuffix}} 146 | \caption{KMP example} 147 | \label{fig:kmp_presuffix} 148 | \end{figure} 149 | 150 | 151 | \begin{python} 152 | def kmp_search(p, s): 153 | L = kmp_lps(p) 154 | ret = [] 155 | i = 0 156 | j = 0 157 | while j < len(s): 158 | if p[i] == s[j]: 159 | i += 1 160 | j += 1 161 | if i == len(p): 162 | ret.append(j - i) 163 | i = L[i - 1] 164 | elif i: 165 | i = L[i - 1] 166 | else: 167 | j += 1 168 | 169 | return ret 170 | \end{python} 171 | Time compelxity: 172 | \begin{itemize} 173 | \item Define $\Delta = j - i$. 174 | \item $j$ never goes backward 175 | \item Any time $j$ doesn’t move forward, $\Delta$ must rise 176 | \item $j$ is bounded by $O(|S|)$ and $\Delta$ is bounded by $O(|P|+|S|)$ 177 | \end{itemize} 178 | \subsection{Applications} 179 | \begin{enumerate} 180 | \item Find needle in haystack. 181 | \item Shortest palindrome 182 | \end{enumerate} 183 | \subsection{Subsequence} 184 | Given a string $s$ and an array of strings $words$, return the number of $words_i$ that is a subsequence of $s$. For example, input: \pyinline{s = "abcde"}, \pyinline{words = ["a","bb","acd","ace"]} 185 | 186 | \rih{Core clues:} 187 | \begin{enumerate} 188 | \item Test whether one string is another's subsequence is straightforwad, how to test multiple strings is one's subsequence? $\Ra$ consume the multiple strings in one iteartion. 189 | \item Each character can be a candidate $\Ra$ char map. 190 | \item Each $words_i$ is only match once $\Ra$ iterator 191 | \end{enumerate} 192 | \begin{python} 193 | def numMatchingSubseq(self, S, words) -> int: 194 | """ 195 | Linear O(|S| + sum(|word|)) 196 | no need to if-check with HashMap + Iterator 197 | """ 198 | itr_lsts = defaultdict(list) 199 | for w in words: 200 | itr_lsts[w[0]].append(iter(w[1:])) 201 | 202 | for c in S: 203 | itrs = itr_lsts.pop(c, []) 204 | for itr in itrs: 205 | ch = next(itr, None) 206 | itr_lsts[ch].append(itr) 207 | 208 | return len(itr_lsts[None]) 209 | \end{python} 210 | Note \pyinline{itr_lsts} can be short formed as \pyinline{itrss} 211 | 212 | -------------------------------------------------------------------------------- /chapterLinkedList.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Linked List} 3 | 4 | 5 | \section{Operations} 6 | \subsection{Fundamentals} 7 | Get the $pre$ reference: 8 | \begin{python} 9 | dummy = Node(0) 10 | dummy.next = head 11 | pre = dummy 12 | cur = pre.next 13 | \end{python} 14 | 15 | In majority case, we need a reference to pre. 16 | 17 | \subsection{Basic Operations} 18 | \begin{enumerate} 19 | \item Get the length 20 | \item Get the $i$-th object 21 | \item Delete a node 22 | \item Reverse 23 | \begin{figure}[] 24 | \centering 25 | \subfloat{\includegraphics[width=\linewidth]{ll_reverse}} 26 | \caption{Reverse the linked list} 27 | \label{fig:LABEL} 28 | \end{figure} 29 | \begin{python} 30 | def reverseList(self, head): 31 | dummy = ListNode(0) 32 | dummy.next = head 33 | 34 | pre = dummy 35 | cur = head # ... = dummy.next, not preferred 36 | while pre and cur: 37 | assert pre.next == cur 38 | nxt = cur.next 39 | # op 40 | cur.next = pre 41 | 42 | pre = cur 43 | cur = nxt 44 | 45 | # original head pointing to dummy 46 | head.next = None # dummy.next.next = ..., not preferred 47 | return pre # new head 48 | \end{python} 49 | Notice: the evaluation order for the swapping the nodes and links. 50 | \end{enumerate} 51 | 52 | \subsection{Combined Operations} 53 | In $O(n)$ without extra space: 54 | \begin{enumerate} 55 | \item Determine whether two lists intersects 56 | \item Determine whether the list is palindrome 57 | \item Determine whether the list is acyclic 58 | \end{enumerate} 59 | 60 | \section{Combinations} 61 | \subsection{Merge K Linked List} 62 | Given an array of k linked-lists lists, each linked-list is sorted in ascending order. Merge all the linked-lists into one sorted linked-list and return it. 63 | \runinhead{Core Clues:} 64 | \begin{enumerate} 65 | \item Relax the problem: if only two lists, just compare and merge like merge sort 66 | \item $k$ lists $\Ra$ Heap 67 | \begin{python} 68 | use heap 69 | 70 | 71 | ------------------- 72 | | | | | | | 73 | | | | | | | 74 | | | | | | | 75 | | | | | | | 76 | \end{python} 77 | \end{enumerate} 78 | \begin{python} 79 | def mergeKLists(self, lists): 80 | h = [] 81 | for node in lists: 82 | if node: 83 | heapq.heappush(h, (node.val, node)) 84 | 85 | dummy = ListNode(0) 86 | current = dummy # verbose name for return 87 | while h: 88 | val, mini = heapq.heappop(h) 89 | current.next = mini 90 | nxt = mini.next 91 | if nxt: 92 | heapq.heappush(h, (nxt.val, nxt)) 93 | 94 | current = current.next 95 | 96 | return dummy.next 97 | \end{python} 98 | \subsection{LRU} 99 | Core clues: 100 | \begin{enumerate} 101 | \item Ensure $O(1)$ find $O(1)$ deletion. 102 | \item Doubly linked list + map. 103 | \item Keep both \pyinline{head} and \pyinline{tail} pointer. 104 | \item Operations on doubly linked list are case by case. 105 | \end{enumerate} 106 | \begin{python} 107 | class Node: 108 | def __init__(self, key, val): 109 | self.key = key 110 | self.val = val 111 | self.pre = None 112 | self.next = None 113 | 114 | 115 | class LRUCache: 116 | def __init__(self, capacity): 117 | self.cap = capacity 118 | self.map = {} # key to Node(val) 119 | self.head = None 120 | self.tail = None 121 | 122 | def get(self, key): 123 | if key in self.map: 124 | cur = self.map[key] 125 | self._elevate(cur) 126 | return cur.val 127 | 128 | return -1 129 | 130 | def set(self, key, value): 131 | if key in self.map: 132 | cur = self.map[key] 133 | cur.val = value 134 | self._elevate(cur) 135 | else: 136 | cur = Node(key, value) 137 | self.map[key] = cur 138 | self._appendleft(cur) 139 | 140 | if len(self.map) > self.cap: 141 | last = self._pop() 142 | del self.map[last.key] 143 | 144 | # doubly linked-list operations only 145 | def _appendleft(self, cur): 146 | """Normal or initially empty""" 147 | if not self.head and not self.tail: 148 | self.head = cur 149 | self.tail = cur 150 | return 151 | 152 | head = self.head 153 | cur.next, cur.pre = head, None 154 | head.pre = cur 155 | self.head = cur 156 | 157 | def _pop(self): 158 | """Normal or resulting empty""" 159 | last = self.tail 160 | if self.head == self.tail: 161 | self.head, self.tail = None, None 162 | return last 163 | 164 | pre = last.pre 165 | pre.next = None 166 | self.tail = pre 167 | 168 | return last 169 | 170 | def _elevate(self, cur): 171 | """Head, Tail, Middle""" 172 | pre, nxt = cur.pre, cur.next 173 | if not pre: 174 | return 175 | elif not nxt: 176 | assert self.tail == cur 177 | self._pop() 178 | else: 179 | pre.next, nxt.pre = nxt, pre 180 | 181 | self._appendleft(cur) 182 | \end{python} 183 | 184 | Use \pyinline{OrderedDict}: 185 | \begin{python} 186 | from collections import OrderedDict 187 | 188 | class LRUCache: 189 | def __init__(self, capacity: int) -> None: 190 | self.capacity = capacity 191 | self.kv = OrderedDict() # key -> value 192 | 193 | def get(self, key: int) -> int | None: 194 | if key not in self.kv: 195 | return None # or raise KeyError 196 | # Move to MRU position 197 | self.kv.move_to_end(key, last=False) 198 | return self.data[key] 199 | 200 | def put(self, key: int, value: int) -> None: 201 | # If key exists, update value and move to MRU 202 | if key in self.kv: 203 | self.kv.move_to_end(key, last=False) 204 | self.kv[key] = value 205 | 206 | # Evict LRU if over capacity 207 | if len(self.kv) > self.capacity: 208 | self.kv.popitem(last=True) # pop LRU (right side) 209 | \end{python} 210 | 211 | \runinhead{First Unique Number in the stream.} 212 | 213 | Naive: 214 | \begin{python} 215 | class FirstUnique: 216 | def __init__(self, A): 217 | self.cnt = Counter() 218 | self.q = deque() 219 | for a in A: 220 | self.add(a) 221 | 222 | def showFirstUnique(self) -> int: 223 | while self.q and self.cnt[self.q[0]] > 1: 224 | # no need to dec counter 225 | self.q.popleft() 226 | return self.q[0] if self.q else -1 227 | 228 | def add(self, value: int) -> None: 229 | self.cnt[value] += 1 230 | self.q.append(value) 231 | \end{python} 232 | 233 | Using Double Linked List: 234 | \begin{python} 235 | from collections import OrderedDict 236 | 237 | class FirstUnique: 238 | def __init__(self, A): 239 | self.cnt = defaultdict(int) 240 | self.uniques = OrderedDict() # Ordered List 241 | for x in A: 242 | self.add(x) 243 | 244 | def showFirstUnique(self) -> int: 245 | return next(iter(self.uniques)) if self.uniques else -1 246 | 247 | def add(self, value): 248 | self.cnt[value] += 1 249 | 250 | if self.cnt[value] == 1: 251 | # first time: becomes unique; append to end 252 | self.uniques[value] = None 253 | elif self.cnt[value] == 2: 254 | self.uniques.pop(value, None) 255 | \end{python} 256 | 257 | Using Double Linked List without OrderedDict: 258 | \begin{enumerate} 259 | \item Maintain a map: val $\ra$ node if seen once; None if unseen; DUP if seen $\ge$ 2; 260 | \end{enumerate} 261 | \begin{python} 262 | @dataclass 263 | class Node: 264 | val: int 265 | prev: Node | None = None 266 | next: Node | None = None 267 | 268 | class DoubleLinkedList: 269 | def __init__(self): 270 | self.head = Node(0) 271 | self.tail = Node(0) 272 | self.head.next = self.tail 273 | self.tail.prev = self.head 274 | 275 | def append(self, node): 276 | last = self.tail.prev 277 | 278 | node.prev = last 279 | node.next = self.tail 280 | 281 | last.next = node 282 | self.tail.prev = node 283 | return node 284 | 285 | def remove(self, node): 286 | prev = node.prev 287 | nxt = node.next 288 | 289 | prev.next = nxt 290 | nxt.prev = prev 291 | 292 | def first(self): 293 | return self.head.next.val \ 294 | if self.head.next is not self.tail else None 295 | 296 | DUP = object() 297 | 298 | class FirstUnique: 299 | def __init__(self, A): 300 | self.dll = DoubleLinkedList() 301 | # val -> node if seen once; None if unseen; DUP if seen >= 2; 302 | self.nodes = {} 303 | for x in A: 304 | self.add(x) 305 | 306 | def showFirstUnique(self) -> int: 307 | v = self.dll.first() 308 | return v if v is not None else -1 309 | 310 | def add(self, value: int): 311 | if value not in self.nodes: 312 | node = self.dll.append(Node(value)) 313 | self.nodes[value] = node 314 | elif self.nodes[value] is not DUP: 315 | # seen once before 316 | self.dll.remove(self.nodes[value]) 317 | self.nodes[value] = DUP 318 | else: 319 | # DUP 320 | pass 321 | \end{python} 322 | -------------------------------------------------------------------------------- /chapterBalancedSearchTree.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Balanced Search Tree} 3 | \section{2-3 Search Tree} 4 | \subsection{Insertion} 5 | Insertion into a 3-node at bottom: 6 | \begin{enumerate} 7 | \item Add new key to the 3-node to create a temporary 4-node. 8 | \item Move middle key of the 4-node into the parent (including root's parent). 9 | \item Split the modified 4-node. 10 | \item Repeat recursively up the trees as necessary. 11 | \end{enumerate} 12 | \begin{figure}[hbtp] 13 | \centering 14 | \subfloat{\includegraphics[width=\linewidth]{23insert1}} 15 | \caption{Insertion 1} 16 | \label{fig:LABEL} 17 | \end{figure} 18 | 19 | \begin{figure}[hbtp] 20 | \centering 21 | \subfloat{\includegraphics[width=0.8\linewidth]{23insert2}} 22 | \caption{insert 2} 23 | \label{fig:LABEL} 24 | \end{figure} 25 | 26 | \subsection{Splitting} 27 | Summary of splitting the tree. 28 | \begin{figure}[hbtp] 29 | \centering 30 | \subfloat{\includegraphics[width=\linewidth]{23splitting}} 31 | \caption{Splitting temporary 4-ndoe summary} 32 | \label{fig:splitting} 33 | \end{figure} 34 | 35 | \subsection{Properties} 36 | When inserting a new key into a 2-3 tree, under which one of the following scenarios must the height of the 2-3 tree increase by one? When every node on the search path from the root is a 3-node 37 | 38 | \section{Red-Black Tree}\label{rbtree} 39 | \subsection{Properties} 40 | Red-black tree is an implementation of 2-3 tree using \textbf{leaning-left red link}. \begin{figure}[hbtp] 41 | \centering 42 | \subfloat{\includegraphics[width=\linewidth]{rbtree11}} 43 | \caption{RB-tree and 2-3 tree} 44 | \label{fig:LABEL} 45 | \end{figure} 46 | The height of the RB-tree is at most $2\lg N$ where alternating red and black links. Red is the special link while black is the default link. 47 | 48 | \runinhead{Perfect black balance.}Every path from root to null link has the same number of black links. 49 | \subsection{Operations} 50 | \runinhead{Elementary operations:} 51 | \begin{enumerate} 52 | \item Left rotation: orient a (temporarily) right-leaning red link to lean left. Rotate leftward. 53 | \item Right rotation: orient a (temporarily) left-leaning red link to lean right. 54 | \item Color flip: Recolor to split a (temporary) 4-node. Rotate rightward. 55 | \end{enumerate} 56 | \begin{figure}[hbtp] 57 | \centering 58 | \subfloat{\includegraphics[width=\linewidth]{rbrotate}} 59 | \caption{Rotate left/right} 60 | \label{fig:LABEL} 61 | \end{figure} 62 | 63 | \begin{figure}[hbtp] 64 | \centering 65 | \subfloat{\includegraphics[width=0.75\linewidth]{rbflip}} 66 | \caption{Flip colors} 67 | \label{fig:LABEL} 68 | \end{figure} 69 | 70 | \runinhead{Insertion.} When doing insertion, from the child's perspective, need to have the information of current leaning direction and parent's color. Or from the parent's perspective - need to have the information of children's and grandchildren's color and directions. 71 | 72 | For every new insertion, the node is always attached with red links. 73 | 74 | The following code is the simplest version of RB-tree insertion: 75 | \newpage 76 | \begin{java} 77 | Node put(Node h, Key key, Value val) { 78 | if (h == null) // std red insert (link to parent). 79 | return new Node(key, val, 1, RED); 80 | int cmp = key.compareTo(h.key); 81 | if (cmp < 0) h.left = put(h.left, key, val); 82 | else if (cmp > 0) h.right = put(h.right, key, val); 83 | else h.val = val; // pass 84 | 85 | if (isRed(h.right) && !isRed(h.left)) 86 | h = rotateLeft(h); 87 | if (isRed(h.left) && isRed(h.left.left)) 88 | h = rotateRight(h); 89 | if (isRed(h.left) && isRed(h.right)) 90 | flipColors(h); 91 | 92 | h.N = 1+size(h.left)+size(h.right); 93 | return h; 94 | } 95 | \end{java} 96 | 97 | Rotate left, rotate right, then flip colors. 98 | 99 | \runinhead{Illustration of cases.} Insert into a single 2-node: Figure-\ref{fig:rb_2}. Insert into a single 3-node: Figure-\ref{fig:rb_3} 100 | \begin{figure}[hbtp] 101 | \begin{tabular}{cc} 102 | \includegraphics[height = 1.7in]{rb_left} & 103 | \includegraphics[height = 1.7in]{rb_right}\\ 104 | \end{tabular} 105 | \caption{(a) smaller than 2-node (b) larger than 2-nod} 106 | \label{fig:rb_2} 107 | \end{figure} 108 | 109 | \begin{figure}[hbtp] 110 | \centerline{\includegraphics[width=\linewidth]{rb_3_left_right_btw}} 111 | \caption{(a) larger than 3-node (b) smaller than 3-node (c) between 3-node.} 112 | \label{fig:rb_3} 113 | \end{figure} 114 | 115 | \runinhead{Deletion.} Deletion is more complicated. 116 | 117 | \section{B-Tree} 118 | B-tree is the generalization of 2-3 tree. 119 | \begin{figure*}[hbtp] 120 | \centering 121 | \subfloat{\includegraphics[width=\linewidth]{b-tree}} 122 | \caption{B-Tree} 123 | \label{fig:b-tree} 124 | \end{figure*} 125 | \subsection{Basics} 126 | Half-full principle: 127 | 128 | \begin{table} 129 | \begin{tabular}{lll} 130 | \hline\noalign{\smallskip} 131 | \textbf{Attrs} & \textbf{Non-leaf} & \textbf{Leaf} \\ 132 | \noalign{\smallskip}\hline\noalign{\smallskip} 133 | Ptrs & $\lceil\frac{n+1}{2}\rceil$ & $\lfloor\frac{n+1}{2}\rfloor$ \\ 134 | \noalign{\smallskip}\hline\noalign{\smallskip} 135 | \end{tabular} 136 | \caption{Nodes at least half-full} 137 | \end{table} 138 | 139 | \subsection{Operations} 140 | \subsubsection{Insertion} 141 | Core clues 142 | \begin{enumerate} 143 | \item \textbf{Invariant}: children balanced or left-leaning 144 | \item \textbf{Split}: split half, thus invariant. 145 | \item \textbf{Leaf-Up}: no delete, recursively move up the right node's first child; 146 | thus invariant. 147 | \item \textbf{Nonleaf-Up}: delete and recursively move up the left's last if left-leaning 148 | or right's first if balanced; thus invariant. 149 | \end{enumerate} 150 | \subsubsection{Deletion} 151 | Core clues 152 | \begin{enumerate} 153 | \item \textbf{Invariant}: children $\lceil\frac{n+1}{2}\rceil, \lfloor\frac{n+1}{2}\rfloor$ 154 | \item \textbf{Fuse}: fuse remaining to left sibling, if left not full. \textit{Delete} 155 | upper level. 156 | \item \textbf{Redistribute}: Extract the last key of left sibling, if left full. \textit{Adjust} 157 | upper level. 158 | \item \textbf{Non-leaf fuse}: fuse remaining to left sibling, if left not full. \textit{Move 159 | down} the upper level. 160 | \end{enumerate} 161 | \section{AVL Tree} 162 | TODO 163 | 164 | RB-Tree is preferred since shorter implementation code. 165 | 166 | \section{Cartesian Tree} 167 | \subsection{Basics} 168 | Also known as max tree (or min tree). The root is the maximum number in the array. The left subtree and right subtree are the max trees of the subarray divided by the root number. 169 | \begin{figure}[hbtp] 170 | \centering 171 | \subfloat{\includegraphics[width=\linewidth]{Cartesian_tree}} 172 | \caption{Cartesian Tree} 173 | \label{fig:cartesianTree} 174 | \end{figure} 175 | \begin{java} 176 | Given [2, 5, 6, 0, 3, 1], the max tree is 177 | 6 178 | / \ 179 | 5 3 180 | / / \ 181 | 2 0 1 182 | \end{java} 183 | \runinhead{Construction algorithm.} Similar to all nearest smaller (or larger) values problem - Section \ref{allNearestSmaller} Mono Stack. 184 | 185 | Core clues: 186 | \begin{enumerate} 187 | \item Use stack to maintain a \textit{strictly decreasing} stack, similar to find the all nearest large elements. 188 | \item Maintain the tree for currently scanning $A_i$ with the subarray $A[:i]$. 189 | \begin{enumerate} 190 | \item \rih{Left tree.} For each currently scanning node $A_i$, if ${stk}_{-1} \leq A_i$, then ${stk}_{-1}$ is the left subtree of $A_i$. Then pop the stack and iteratively look at ${stk}_{-1}$ again (previously ${stk}_{-2}$). Notice that the original left subtree of $A_i$ should become the right subtree of ${stk}_{-1} $, because the original left subtree appears later and satisfies the decreasing relationship. 191 | \item \rih{Right tree.} In this stack, ${stk}_{-1} < {stk}_{-2}$ and ${stk}_{-1}$ appears later than ${stk}_{-2}$; thus ${stk}_{-1}$ is the right subtree of ${stk}_{-2}$. The strictly decreasing relationship of stack will be processed when popping the stack. 192 | \end{enumerate} 193 | \end{enumerate} 194 | 195 | $O(n)$ since each node on the tree is pushed and popped out from stack once. 196 | 197 | 198 | \begin{python} 199 | def maxTree(self, A): 200 | stk = [] 201 | for a in A: 202 | cur = TreeNode(a) 203 | while stk and stk[-1].val <= cur.val: 204 | pre = stk.pop() 205 | pre.right = cur.left 206 | cur.left = pre 207 | 208 | stk.append(cur) 209 | 210 | pre = None 211 | while stk: 212 | cur = stk.pop() 213 | cur.right = pre 214 | pre = cur 215 | 216 | return pre 217 | \end{python} 218 | 219 | Usually, min tree is more common. 220 | \subsection{Treap} 221 | \rih{Randomized Cartesian tree}. Heap-like tree. It is a Cartesian tree in which each key is given a (randomly chosen) numeric priority. As with any binary search tree, the inorder traversal order of the nodes is the same as the sorted order of the keys. 222 | 223 | \begin{figure}[hbtp] 224 | \centering 225 | \subfloat{\includegraphics[width=\linewidth]{treap}} 226 | \caption{Treap. Each node x is labeled with x.key: x.priority.} 227 | \label{fig:treap} 228 | \end{figure} 229 | 230 | Construct a Treap for an array $A$ with index as the $x.key$ randomly chosen priority $x.priority$ $O(n)$. Thus support search, insert, delete into array (i.e. Treap) $O(\log n)$ on average. 231 | 232 | Insertion and deletion - need to perform \textit{rotations} to maintain the min-treap property. 233 | -------------------------------------------------------------------------------- /chapterBitManipulation.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Bit Manipulation} 3 | \section{Concepts} 4 | \subsection{Basics} 5 | \begin{enumerate} 6 | \item Bit value: bit0, bit1. 7 | \item BitSet/Bits 8 | \item Bit position (bit interchangeably) 9 | \item 32-bit signed range: $[-2^{31}, 2^{31}-1]$. $0$ is like positive number without complement. 10 | \end{enumerate} 11 | \begin{python} 12 | MAX = 0x7FFFFFFF # 0111,1... 13 | MIN = 0x80000000 # 1000,0... 14 | MSK = 0xFFFFFFFF # 1111,1... 15 | # hex is 4 bit 16 | \end{python} 17 | \subsection{Operations} 18 | \runinhead{Basics} 19 | \begin{enumerate} 20 | \item Masking to 1: to mask a single bit position, $bit\OR 1$ 21 | \item Masking to 0: to mask a single bit position, $bit\AND 0$ 22 | \item Querying a bit position value: to query a single bit position, $bit\AND 0010$ 23 | \item Toggling bit values: to toggle a single bit position, $bit\XOR 1$ 24 | \end{enumerate} 25 | This can be extended to do masking operations on multiple bits. 26 | \runinhead{Shift.} 27 | Shift operator $<<$ and $>>$ has lower precedence than arithmetic operators. 28 | \runinhead{2's complement} 29 | $$ 30 | -x = \NOT x+1 31 | $$ 32 | 33 | Negative numbers: \pyinline{0b10000000} to \pyinline{0b11111111} (-128 to -1 in decimal). This process essentially "wraps around" the positive binary sequence to start from the most negative number. 34 | 35 | \begin{python} 36 | # 4 overflow 37 | 3: 0b011 38 | 2: 0b010 39 | 1: 0b001 40 | 0: 0b000 41 | ######### 42 | -1: 0b111 43 | -2: 0b110 44 | -3: 0b101 45 | -4: 0b100 46 | \end{python} 47 | Since 0 takes a position in the binary sequence $\Ra$ 48 | \begin{itemize} 49 | \item $\NOT0$ is $-1$ 50 | \item $\NOT1$ is $-2$ 51 | \item $\NOT3$ is $-4$. 52 | \end{itemize} 53 | 54 | \runinhead{Negation and index} We can use tilde notation for the index accessing a string or an array 55 | \begin{lstlisting} 56 | i ~i 57 | ##### 58 | 0 -1 59 | 1 -2 60 | 2 -3 61 | 3 -4 62 | 4 -5 63 | 5 -6 64 | \end{lstlisting} 65 | $$ 66 | \NOT x = -x-1 67 | $$ 68 | To determine whether a string is palindrome: 69 | \begin{python} 70 | def is_palindrome(s): 71 | return all(s[i] == s[~i] for i in range(len(s)/2)) 72 | \end{python} 73 | 74 | \runinhead{Check 2's power} 75 | 2's power is in the form of \pyinline{0b01000}. 76 | $$x\AND(x-1) == 0$$ 77 | 78 | \runinhead{Divide by 2.} To divide a number by 2, it should be \pyinline{x >> 1} rather than \pyinline{x >> 2}. 79 | 80 | \runinhead{Rightmost bit set. LSB} To get the rightmost bit, with the help of 2's complement as followed: 81 | \begin{align*} 82 | x &= \pyinline{0bhijk1000} \\ 83 | \NOT x &= \pyinline{0bHIJK0111} \\ 84 | -x &= \pyinline{0bHIJK1000} 85 | \end{align*} 86 | where \pyinline{HIJK} represent bits that are the negation of \pyinline{hijk}. Thus, 87 | 88 | $$LSB = x \AND -x$$ 89 | 90 | The LSB above is left extended with 0's. To left extended with 1's. 91 | 92 | \begin{align*} 93 | lsb &= \pyinline{0b0001,0000} \\ 94 | lsb' &= \pyinline{0b1111,0000} \\ 95 | lsb - 1 &= \pyinline{0b0000,1111} 96 | \end{align*} 97 | 98 | Thus, 99 | $$ 100 | LSB_{1extended} = \NOT (lsb - 1) 101 | $$ 102 | 103 | \runinhead{Leftmost bit set. MSB.} Find the most significant bit. 104 | \begin{python} 105 | # msb_idx 106 | msb = 0 107 | while num >> msb: 108 | msb += 1 109 | return msb # index 110 | \end{python} 111 | If zero-indexed, the MSB is \pyinline{msb - 1}. 112 | 113 | Alternatively, 114 | \begin{python} 115 | msb = 1 116 | while msb <= x: 117 | msb <<= 1 118 | return msb >> 1 119 | \end{python} 120 | 121 | \subsection{Python} 122 | Python int is larger than 32 bit. 123 | If 32bit signed int, in python, we may need to mask the int: 124 | \begin{enumerate} 125 | \item Mask to 32bit: \pyinline{x & MSK}, 126 | \item Left extended with 1's: \pyinline{\~(x ^ MSK)} 127 | \end{enumerate} 128 | 129 | , where \pyinline{MSK = 0xFFFFFFFF}, 8 F's for 32 bits. 130 | 131 | \section{Radix} 132 | \runinhead{Convert to hexadecimal, but with 2's complement.} Easy to convert positive number of hex, but need to pay more attention to negative number when thinking in the decimal representation. 133 | 134 | Everything easy to convert the number even under 2's complement if thinking in the binary representation. 135 | 136 | Core proccess: 137 | \begin{enumerate} 138 | \item current digit we need: \pyinline{num & 0xF} 139 | \item next significant number: \pyinline{num >>= 4} 140 | \end{enumerate} 141 | \section{Circuit} 142 | It is under 32-bit assumption, for Python, we need additional masking in the previous section. 143 | \subsection{Full-adder} 144 | \rih{Plus.} Handle carry: only 1 + 1 needs carry, thus \pyinline{a & b} determines carry. 145 | \begin{python} 146 | def plus(a, b): 147 | carry = (a & b) << 1 148 | out = a ^ b 149 | if carry != 0: 150 | return plus(out, carry) 151 | else: 152 | return out 153 | \end{python} 154 | \rih{Half Adder}. One bit \pyinline{a, b}: 155 | \begin{python} 156 | def half_add(a, b): 157 | carry = a & b 158 | out = a ^ b 159 | return out, carry 160 | \end{python} 161 | \rih{Full Adder}. One bit \pyinline{a, b, cin}. \pyinline{out = a ^ b ^ cin}. and \pyinline{cout = a & b | cin & a ^ b} 162 | \begin{python} 163 | def full_add(a, b, cin): 164 | out, c1 = half_add(a, b) 165 | out, c2 = half_add(out, cin) 166 | cout = c1 | c2 # ^ possible 167 | return out, cout 168 | \end{python} 169 | 170 | \subsection{Full-substractor} 171 | \rih{Substract.} Handle borrow: only 0 - 1 needs borrow, thus \pyinline{\~a & b} determines borrow. 172 | \begin{python} 173 | def sub(a, b): 174 | borrow = (~a & b) << 1 175 | out = a ^ b 176 | if borrow != 0: 177 | return sub(out, borrow) 178 | else: 179 | return out 180 | \end{python} 181 | \rih{Half Substractor.} One bit \pyinline{a, b}: 182 | \begin{python} 183 | def half_sub(a, b): 184 | borrow = (~a & b) 185 | out = a ^ b 186 | return out, borrow 187 | \end{python} 188 | Notice negation can be done in xor. 189 | \begin{python} 190 | ~a == 1 ^ a 191 | \end{python} 192 | \rih{Full Substractor}. One bit \pyinline{a, b, bin}. \pyinline{out = a ^ b ^ bin}. 193 | \begin{python} 194 | def full_sub(a, b, bin): 195 | out, b1 = half_sub(a, b) 196 | out, b2 = half_sub(out, bin) 197 | bout = b1 | b2 198 | return out, bout 199 | \end{python} 200 | 201 | \subsection{Multipler} 202 | 203 | 204 | \section{Single Number} 205 | \subsection{Three-time appearance} 206 | Given an array of integers, every element appears three times except for one. Find that single one. 207 | 208 | \rih{Using list.} Consider 4-bit numbers: 209 | \begin{eqnarray*} 210 | && 0000 \\ 211 | && 0001 \\ 212 | && 0010 \\ 213 | && ... \\ 214 | && 1111 215 | \end{eqnarray*} 216 | 217 | Add (not $\&$) the bit values \textbf{vertically}, then result would be $abcd$ where $a, b, c, d$ can be any number, not just binary. $a, b, c, d$ can be divided by 3 if the all element appears three times. Until here, you can use a list to hold $a, b, c, d$. By mod 3, the single one that does not appear 3 times is found. 218 | 219 | To generalize to 32-bit \pythoninline{int}, use a list of length 32. 220 | 221 | \rih{Using bits.} 222 | To further optimize the space, use bits (bit set) instead of list. 223 | \begin{itemize} 224 | \item Since all except one appears 3 times, we are only interested in $0, 1, 2$ (mod 3) count of bit1 appearances in a bit position. 225 | \item We create 3 bit sets to represent $0, 1, 2$ appearances of all positions of bits. 226 | \item For a bit, there is one and only one bit set containing bit1 in that bit position. 227 | \item Transition among the 3 bit sets for every number: 228 | $$ 229 | bitSet^{(i)} = (bitSet^{(i-1)}\AND num)\OR(bitSet^{(i)}\AND \NOT num) 230 | $$ 231 | \end{itemize} 232 | 233 | For $i$ appearances, the first part is the bit set \textbf{transited from} $(i-1)$ appearances, and the second part is the bit set \textbf{transited out} from itself. 234 | 235 | Consider each single bit separately. For the $j$-th bit in $num$, if $num_j=1$, the first part indicates $bitSet^{(i-1)}$ will transit in (since transition); the 2nd part is always 0 (since transition out or initially 0). If $num_j=0$, the 1st part is always 0 (since no transition); the 2nd part indicates $bitSet^{(i)}$ will remain the same (since no transition). 236 | 237 | 238 | 239 | \subsection{Two Numbers} 240 | Given an array of numbers nums, in which exactly \textbf{two} elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once. 241 | 242 | \begin{itemize} 243 | \item Easily get: $x = a \XOR b$. 244 | \item $a \neq b$; thus there are at least one 1-bit in $x$ is different. 245 | \item Take an arbitrary 1 bit set in $x$, and such bit set can classify the elements in the array into two separate groups. 246 | \item Then do $a_i \XOR a_j$ in two groups respectively to find out $a$ and $b$ from each group. 247 | \end{itemize} 248 | 249 | \section{Bitwise operators} 250 | \runinhead{Comparison.} Write a method which finds the maximum of two numbers $a, b$. You should not use if- else or any other comparison operator 251 | \\ 252 | Clues: 253 | \begin{enumerate} 254 | \item check the sign bit $s$ of $a-b$. 255 | \item return $a-s*(a-b)$ 256 | \end{enumerate} 257 | Codes: 258 | \begin{java} 259 | int getMax(int a, int b) { 260 | int c = a - b; 261 | int k = (c >> 31) & 0x1; 262 | int max = a - k * c; 263 | return max; 264 | } 265 | 266 | \end{java} 267 | If consider overflow, it raises another level of difficulty. 268 | 269 | \runinhead{ Maximum XOR} Maximum XOR of Two Numbers in an Array. To achieve $O(N)$, check bit by bit rather than number by number. 270 | -------------------------------------------------------------------------------- /chapterInterval.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Interval} 3 | 4 | 5 | \section{Interval Merger} 6 | \runinhead{Merge intervals.} Given a collection of intervals, merge all overlapping intervals. 7 | 8 | \textbf{Core clues}: 9 | \begin{enumerate} 10 | \item Sort the intervals 11 | \item When does the overlapping happens? 12 | [0, 5) vs. [2, 6); [0, 5) vs. [2, 4) 13 | \end{enumerate} 14 | \begin{python} 15 | def merge(self, itvls): 16 | itvls.sort(key=lambda x: x.start) 17 | ret = [itvls[0]] 18 | for cur in itvls[1:]: 19 | pre = ret[-1] 20 | if cur.start <= pre.end: # overlap 21 | pre.end = max(pre.end, cur.end) 22 | else: 23 | ret.append(cur) 24 | 25 | return ret 26 | \end{python} 27 | 28 | \runinhead{Insert \& merge intervals.} Given a set of non-overlapping intervals, insert a new interval into the intervals (merge if necessary). Assume that the intervals were initially sorted according to their start times. 29 | 30 | \textbf{Core clues} 31 | \begin{enumerate} 32 | \item Partition the original list of intervals to left-side intervals and right-side intervals according to the new interval. 33 | \item Merge the intermediate intervals with the new interval. Need to mathematically prove it works as expected. 34 | \end{enumerate} 35 | 36 | \begin{python} 37 | def insert(self, itvls, newItvl): 38 | s, e = newItvl.start, newItvl.end 39 | left = list(filter(lambda x: x.end < s, itvls)) 40 | right = list(filter(lambda x: x.start > e, itvls)) 41 | if len(left) + len(right) != len(itvls): 42 | s = min(s, itvls[len(left)].start) 43 | e = max(e, itvls[-len(right)-1].end) 44 | 45 | return left + [Interval(s, e)] + right 46 | \end{python} 47 | \section{Meeting Rooms} 48 | Each meeting has a start and an end $[start, end)$. 49 | 50 | \runinhead{Meeting room requried.} Given an array of meeting time $itvls$. 51 | 52 | \rih{Core Clues:} 53 | \begin{enumerate} 54 | \item Sort by $start$ 55 | \item Put $end$ into heap 56 | \item Record the max heap size 57 | \end{enumerate} 58 | \begin{python} 59 | def minMeetingRooms(self, itvls) -> int: 60 | itvls.sort(key=lambda x: x.start) 61 | 62 | ret = 0 63 | end_h = [] 64 | 65 | for s, e in itvls: 66 | if end_h and end_h[0] <= s: 67 | heapq.heappop(end_h) 68 | 69 | heapq.heappush(end_h, e) 70 | ret = max(ret, len(end_h)) 71 | 72 | return ret 73 | \end{python} 74 | 75 | \runinhead{Delayed meetings.} Given $n$ as number of rooms, and a list of meetings $itvls$. Return the number of the room that held the most meetings. 76 | \begin{enumerate} 77 | \item Each meeting will take place in the unused room with the lowest number. 78 | \item If there are no available rooms, the meeting will be delayed until a room becomes free. The meeting duration unchange. 79 | \item When a room becomes unused, meetings that have an earlier original start time should be given the room. 80 | \end{enumerate} 81 | \runinhead{Core Clues:} 82 | \begin{enumerate} 83 | \item Intervals $\Ra$ sort intervals by $start$ and use a heap of busy rooms (processing) to hold their ending time $end$. 84 | \item $n$ rooms $\Ra$ need to keep tracks of currently available rooms, and number of ever hosted meetings per room. 85 | \item We don't limit busy heap size by $n$. 86 | \item How to handle overflow $\Ra$ Overflow are scheduled ahead into the busy heap with delayed time. 87 | \item Get the smallest room index to schedule a meeting $\Ra$ use another heap to maintain the indices. 88 | \end{enumerate} 89 | \begin{python} 90 | def mostBooked(self, n, itvls) -> int: 91 | itvls.sort() # by start time 92 | avail_h = [i for i in range(n)] 93 | heapq.heapify(avail_h) # rooms sort by index 94 | busy_h = [] # [(end, room)] 95 | counters = [0 for _ in range(n)] # meeting count per room 96 | 97 | for s, e in itvls: 98 | # process start 99 | while busy_h and busy_h[0][0] <= s: 100 | end, room = heapq.heappop(busy_h) 101 | heapq.heappush(avail_h, room) 102 | 103 | # process end 104 | if avail_h: 105 | room = heapq.heappop(avail_h) 106 | heapq.heappush(busy_h, (e, room)) 107 | counters[room] += 1 108 | else: 109 | end_earliest, room = heapq.heappop(busy_h) 110 | delayed_e = end_earliest + (e-s) # delay meeting 111 | heapq.heappush(busy_h, (delayed_e, room)) 112 | counters[room] += 1 113 | 114 | return counters.index(max(counters)) # argmax 115 | 116 | \end{python} 117 | Alternatively: 118 | \begin{enumerate} 119 | \item Intervals $\Ra$ sort by $start$ and use a heap to hold $end$. 120 | \item $n$ rooms $\Ra$ limit the heap size to $n$, then need to handle overflow. 121 | \item Need to know the room that hold the max $\Ra$ we need to maintain room indices. 122 | \item Overflow $\Ra$ a pending heap to hold meetings waiting for rooms, sort by start time 123 | \item How to get new start time $\Ra$ earliest end time of a room, and the new end time is naturally known by the duration. 124 | \item This is complicated since it requires heaps, time pointers and meeting indices. 125 | \end{enumerate} 126 | 127 | \section{Event-driven algorithms} 128 | \subsection{Introduction} 129 | The core philosophy of event-driven algorithm: 130 | \begin{enumerate} 131 | \item \textbf{Events}: define \textit{events}; the events are sorted by time of appearance. 132 | \item \textbf{Accumulator}: define \textit{accumulator} as the accumulated impacts of the event. 133 | \item \textbf{Transition}: define \textit{transition functions} among events impacting the accumulator. 134 | \end{enumerate} 135 | 136 | \subsection{Line Sweeping} 137 | \runinhead{Maximal Overlaps.} Given a list of intervals, find the max number of overlapping intervals. This is the same as number of meeting rooms required. 138 | 139 | \rih{Core clues:} 140 | \begin{enumerate} 141 | \item \textbf{Events}: Every new start of an interval is an event. Sort intervals by \textit{start}. 142 | \item Need to maintain a list of \textit{ends} that are covered by the current \textit{start}, i.e. $end_i < start, \forall i$. 143 | \item \textbf{Accumulator}: When iterating the current element, maintain the maximum number of overlaps. The smallest \textit{end} should be covered by the current \textit{start}; $\Ra$ sort the \textit{ends} . 144 | \item \textbf{Transition}: Sorted by a heap, stores the \textit{ends} of the intervals. Put the \textit{end} into heap, and pop the ending time earlier than the new start time from heap. And we need min-heap to pop the early ones. 145 | \end{enumerate} 146 | 147 | \begin{python} 148 | def max_overlapping(intervals): 149 | maxa = 0 150 | intervals.sort(key=lambda x: x.start) 151 | h_end = [] 152 | for itvl in intervals: 153 | heapq.heappush(h_end, itvl.end) 154 | 155 | while h_end and h_end[0] <= itvl.start: 156 | heapq.heappop(h_end) 157 | 158 | maxa = max(maxa, len(h_end)) 159 | 160 | return maxa 161 | \end{python} 162 | 163 | \runinhead{The horizontal line balancing above and below.} Given a 2D integer array $squares$. Each $squares_i = [x_i, y_i, l_i]$, representing the coordinates of the bottom-left point and the side length of a square parallel to the x-axis. 164 | 165 | Find the minimum y-coordinate value of a horizontal line such that the total area of the squares above the line equals to that below the line. Note: Squares may overlap. Overlapping areas should be counted multiple times. 166 | 167 | \rih{Core clues:} 168 | \begin{enumerate} 169 | \item \textbf{Events}: Every $y_i$ repreents a new square, with a begining and an end. 170 | \item \textbf{Accumulator}: Define a quantity $q(y) = areaBelow(y)$, by the horizontal line $y$. 171 | \item \textbf{Transition}: The rate of change of $q(y)$ is $rate$. The $rate$ is the accumulated squares intersecting at $y$. Each event contains a $\Delta rate$ that impacts the accumulated rate. More formally, 172 | \begin{align*} 173 | \frac{\dd}{\dd y}q(y) &= rate(y) \\ 174 | rate(y) &= \sum_{\substack{\text{all squares }i \\ y_i \le y < y_i + l_i}} l_i \\ 175 | rate(y) &\geq 0 \\ 176 | \frac{\dd}{\dd y}rate(y) &= \Delta {rate}_y \text{ from } events 177 | \end{align*} 178 | \end{enumerate} 179 | \begin{python} 180 | def separateSquares(self, squares): 181 | """ 182 | Event driven. Line Sweep. 183 | q(y) = areaBelow(y) 184 | dq/dy = slope, the rate of change of q(y) 185 | q(y) is monotonically increasing 186 | """ 187 | events = [] 188 | total_area = 0 189 | for x, y, l in squares: 190 | # (y-coordiate, delta_rate) 191 | # x not relevant 192 | events.append((y, l)) 193 | events.append((y+l, -l)) 194 | total_area += l*l 195 | 196 | events.sort() 197 | target = total_area / 2 198 | 199 | q = 0 200 | rate = 0 201 | prev_y, _ = events[0] 202 | for y, d_rate in events: 203 | q += (y - prev_y) * rate 204 | if q >= target: 205 | return y - (q - target) / rate 206 | # eager, won't fall below prev_y 207 | 208 | rate += d_rate 209 | prev_y = y 210 | 211 | return prev_y 212 | \end{python} 213 | 214 | \section{Range by Pivot} 215 | \rih{Two-way range.} The current scanning node as the pivot, need to scan its left neighbors and right neighbors. 216 | $$ 217 | |\leftarrow p \rightarrow | 218 | $$ 219 | 220 | \rih{Dedup.} If the relationship between the pivot and its neighbors is symmetric, since scanning range is $[i-k, i+k]$ and iterating from left to right, only consider $[i-k, i]$ to avoid duplication. 221 | $$ 222 | |\leftarrow p 223 | $$ 224 | 225 | -------------------------------------------------------------------------------- /chapterArithmetic.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Arithmetic} 3 | 4 | 5 | \section{Big Number} 6 | \rih{Plus One.} Given a non-negative number represented as an array of digits, plus one to the number. 7 | \begin{python} 8 | def plusOne(self, digits): 9 | for i in range(len(digits)-1, -1, -1): 10 | digits[i] += 1 11 | if digits[i] < 10: 12 | return digits 13 | else: 14 | digits[i] -= 10 15 | 16 | # if not return within the loop 17 | digits.insert(0, 1) 18 | return digits 19 | \end{python} 20 | 21 | \runinhead{Multiplication.} The key to big number multiplication is to break down the problem: 22 | \begin{enumerate} 23 | \item Multiply one digit by one. 24 | \item Add one big number by one. 25 | \item Add a list of big number with increasing significance 26 | \end{enumerate} 27 | Details see \href{https://github.com/algorhythms/LeetCode/blob/master/042%20Multiply%20Strings.py}{code}. 28 | \section{Calculator} 29 | \subsection{Parsing} 30 | \runinhead{Basic calculator.} Given a string s which represents an expression, evaluate this expression and return its value. 31 | \begin{python} 32 | s = "3+2*2" 33 | s = " 3/2 " 34 | s = " 23+5 / 2 " 35 | \end{python} 36 | \runinhead{Core Clues:} 37 | \begin{enumerate} 38 | \item Parse the string char by char 39 | \item \hl{Lookback} number and lookback operator 40 | \item Sentiels: \pyinline{None}. 41 | \item Append \pyinline{"\0"} to flush 42 | \end{enumerate} 43 | \begin{python} 44 | def calculate(self, s: str) -> int: 45 | stk = [] 46 | num = None 47 | op = None 48 | 49 | for c in s + '\0': # sentinel to flush the last number 50 | if c == ' ': 51 | continue 52 | 53 | if c.isdigit(): 54 | num = (num if num else 0) * 10 + int(c) 55 | continue 56 | 57 | # c is operator, assign c to op, process op first 58 | if op == '+' or op is None: 59 | stk.append(num) 60 | elif op == '-': 61 | stk.append(-num) 62 | elif op == '*': 63 | stk[-1] = stk[-1] * num 64 | elif op == '/': 65 | # truncate toward zero (not use // for negatives) 66 | stk[-1] = int(stk[-1] / num) 67 | else: 68 | raise 69 | 70 | num = None 71 | op = c 72 | 73 | return sum(stk) 74 | \end{python} 75 | 76 | \runinhead{Add brackets ().} 77 | \begin{enumerate} 78 | \item Use the original function as a recursive function 79 | \item A \pyinline{"("} is a recurisve call stack, and A \pyinline{")"} is a return the current recurisve call. 80 | \end{enumerate} 81 | \begin{python} 82 | def calculate(self, s: str) -> int: 83 | s += '\0' 84 | val, _ = self.parse(s, 0) 85 | return val 86 | 87 | def parse(self, s, i: int) -> tuple[int, int]: 88 | stk = [] 89 | num = None 90 | op = None 91 | 92 | while i < len(s): 93 | c = s[i] 94 | if c == ' ': 95 | i += 1 96 | continue 97 | 98 | if c.isdigit(): 99 | num = (num if num else 0) * 10 + int(c) 100 | i += 1 101 | continue 102 | 103 | if c == '(': 104 | # Evaluate bracketed expression 105 | subtotal, j = self.parse(s, i + 1) 106 | num = subtotal 107 | i = j 108 | continue 109 | 110 | # at this point c is an operator or ')' 111 | # assign c to op 112 | num = num if num else 0 113 | if op == '+' or op is None: 114 | stk.append(num) 115 | elif op == '-': 116 | stk.append(-num) 117 | elif op == '*': 118 | stk[-1] = stk[-1] * num 119 | elif op == '/': 120 | # Truncate toward zero 121 | stk[-1] = int(stk[-1] / num) 122 | else: 123 | raise 124 | num = None 125 | 126 | if c == ')': 127 | # finish this level, skipping ')' 128 | return sum(stk), i + 1 129 | else: 130 | op = c # include '\0' 131 | i += 1 132 | 133 | return sum(stk), i 134 | \end{python} 135 | 136 | Alternatively, iterative Approach: note that the order inside the for-loop cannot change 137 | \begin{python} 138 | def calculate(self, s: str) -> int: 139 | stk = [] # holds signed ints, plus (, the op just before ( 140 | num = None 141 | op = None 142 | 143 | for c in s + '\0': 144 | if c == ' ': 145 | continue 146 | 147 | if c.isdigit(): 148 | num = (num if num else 0) * 10 + int(c) 149 | continue 150 | 151 | if c == '(': 152 | # Save the operator before ( 153 | stk.append(op) # +/- 154 | stk.append('(') 155 | op = None # reset 156 | continue 157 | 158 | # c is +/-/): flush 159 | if num != None: 160 | if op == '+' or op is None: 161 | stk.append(num) 162 | else: # '-' 163 | stk.append(-num) 164 | num = None 165 | 166 | if c == ')': 167 | subtotal = 0 168 | while stk and stk[-1] != '(': 169 | subtotal += stk.pop() 170 | stk.pop() # remove ( 171 | 172 | prev_op = stk.pop() # op before ( 173 | if prev_op == '+' or prev_op is None: 174 | stk.append(subtotal) 175 | else: # '-' 176 | stk.append(-subtotal) 177 | # op remains unchanged 178 | else: # only for +/- 179 | op = c 180 | 181 | return sum(stk) 182 | \end{python} 183 | Adding \pyinline{"*/"} 184 | \begin{python} 185 | def calculate(self, s: str) -> int: 186 | stk = [] 187 | num = None 188 | op = None 189 | 190 | for c in s + '\0': 191 | if c == ' ': 192 | continue 193 | 194 | if c.isdigit(): 195 | num = (num if num else 0) * 10 + int(c) 196 | continue 197 | 198 | if c == '(': 199 | stk.append(op) 200 | stk.append('(') 201 | op = None # reset 202 | continue 203 | 204 | if num != None: 205 | if op == '+' or op is None: 206 | stk.append(num) 207 | elif op == '-': 208 | stk.append(-num) 209 | elif op == '*': 210 | stk[-1] = stk[-1] * num 211 | else: # '/' 212 | stk[-1] = int(stk[-1] / num) 213 | num = None 214 | 215 | if c == ')': 216 | subtotal = 0 217 | while stk and stk[-1] != '(': 218 | subtotal += stk.pop() 219 | stk.pop() # remove ( 220 | 221 | prev_op = stk.pop() 222 | if prev_op == '+' or prev_op is None: 223 | stk.append(subtotal) 224 | elif prev_op == '-': 225 | stk.append(-subtotal) 226 | elif prev_op == '*': 227 | stk[-1] = stk[-1] * subtotal 228 | else: # '/' 229 | stk[-1] = int(stk[-1] / subtotal) 230 | else: 231 | op = c 232 | 233 | return sum(stk) 234 | \end{python} 235 | \section{Appendix - Polish Notations} 236 | Polish Notation is in-fix while Reverse Polish Notation is post-fix. 237 | 238 | Reverse Polish notation (RPN) is a mathematical notation in which every operator follows all of its operands (i.e. operands are followed by operators). RPN should be treated as the orthogonal expression. 239 | 240 | Polish notation (PN) is a mathematical notation in which every operator is followed by its operands. 241 | \subsection{Evaluate post-fix expressions}\label{section:evaluationPostFix} 242 | Consider: 243 | 244 | In-fix 245 | \begin{python} 246 | 5 + ((1 + 2) * 4) - 3 247 | \end{python} 248 | 249 | Post-fix 250 | \begin{python} 251 | 5 1 2 + 4 * + 3 - 252 | \end{python} 253 | Straightforward: use a \textit{stack} to store the number. Iterate the input, push 254 | stack when hit numbers, pop stack when hit operators. 255 | \subsection{Convert in-fix to post-fix (RPN)} 256 | \pyinline{ret} stores the final result of reverse polish notation. \pyinline{stk} stores 257 | the temporary result in strictly increasing order. 258 | 259 | In-fix 260 | \begin{python} 261 | 5 + ((1 + 2) * 4) - 3 262 | \end{python} 263 | 264 | can be written as 265 | \begin{python} 266 | 5 1 2 + 4 * + 3 - 267 | \end{python} 268 | Core clues: 269 | \begin{enumerate} 270 | \item \rih{Stack}. The stack temporarily stores the operators of \textit{strictly increasing precedence order}, except for brackets, which are put onto stack directly. 271 | \item \rih{Precedence}. Digits have the highest precedence, followed by \pyinline{*, /, +, (}. Notice that \pyinline{(} operator itself has the \textit{lowest} precedence. 272 | \item \rih{Bracket}. \textit{Match} the brackets. 273 | \end{enumerate} 274 | Code: 275 | \begin{python} 276 | def infix2postfix(self, lst): 277 | stk = [] 278 | ret = [] # post fix result 279 | for elt in lst: 280 | if elt.isdigit(): 281 | ret.append(elt) 282 | elif elt == "(": 283 | stk.append(elt) 284 | elif elt == ")": 285 | while stk and stk[-1] != "(": 286 | ret.append(stk.pop()) 287 | stk.pop() # pop "(" 288 | else: 289 | # maintain invariant 290 | while stk and not precdn(stk[-1]) < precdn(elt): 291 | ret.append(stk.pop()) 292 | stk.append(elt) 293 | 294 | while stk: # clean up 295 | ret.append(stk.pop()) 296 | 297 | return ret 298 | \end{python} 299 | 300 | \subsection{Convert in-fix to pre-fix (PN)} 301 | PN is the \textit{reverse} of RPN, thus, scan the expression from right to left; and \pyinline{stk} stores the temporary result in \textit{non-decreasing} order, except for brackets. 302 | 303 | 304 | In-fix 305 | \begin{python} 306 | 5 + ((1 + 2) * 4) - 3 307 | \end{python} 308 | 309 | can be written as the intermediate representation (IR) 310 | \begin{python} 311 | 3 4 2 1 + * 5 + - 312 | \end{python} 313 | 314 | reverse as the pre-fix 315 | \begin{python} 316 | - + 5 * + 1 2 4 3 317 | \end{python} 318 | 319 | \begin{python} 320 | def infix2prefix(self, lst): 321 | """starting from right the left""" 322 | stk = [] 323 | pre = [] 324 | for elt in reversed(lst): 325 | if elt.isdigit(): 326 | pre.append(elt) 327 | elif elt == ")": 328 | stk.append(elt) 329 | elif elt == "(": 330 | while stk and stk[-1] != ")": 331 | pre.append(stk.pop()) 332 | stk.pop() 333 | else: 334 | # maintain invariant 335 | while stk and not precdn(stk[-1]) <= precdn(elt): 336 | pre.append(stk.pop()) 337 | stk.append(elt) 338 | 339 | while stk: 340 | pre.append(stk.pop()) 341 | 342 | pre.reverse() 343 | return pre 344 | \end{python} 345 | 346 | 347 | \subsection{Evaluate pre-fix (PN) expressions} 348 | Consider: 349 | 350 | In-fix 351 | \begin{python} 352 | 5 + ((1 + 2) * 4) - 3 353 | \end{python} 354 | 355 | Pre-fix 356 | \begin{python} 357 | - + 5 * + 1 2 4 3 358 | \end{python} 359 | 360 | reverse as the intermediate representation (IR) 361 | \begin{python} 362 | 3 4 2 1 + * 5 + - 363 | \end{python} 364 | Put into \textit{stack}, similar to evaluating post-fix \ref{section:evaluationPostFix}, but pay attention to operands order, which should be reversed when hitting a operator. 365 | -------------------------------------------------------------------------------- /chapterMath.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Math} 3 | 4 | \section{Functions} 5 | \rih{Equals.} Requirements for equals 6 | \begin{enumerate} 7 | \item Reflexive 8 | \item Symmetric 9 | \item Transitive 10 | \item Non-null 11 | \end{enumerate} 12 | \rih{Compare.} Requirements for compares (total order): 13 | \begin{enumerate} 14 | \item Antisymmetry 15 | \item Transitivity 16 | \item Totality 17 | \end{enumerate} 18 | \section{Divisor} 19 | \runinhead{gcd.} Greatest common divisor. 20 | $$ 21 | gcd(a,b) = gcd(b, r) 22 | $$ 23 | \begin{center} 24 | \begin{tikzpicture}[->, thick, >=stealth] 25 | 26 | % Nodes 27 | \node (a) at (0,2) {\(a\)}; 28 | \node (b) at (2,2) {\(b\)}; 29 | \node (mod) at (2,0) {\(a \% b\)}; 30 | \node (b2) at (0,0) {\(b\)}; 31 | 32 | % Arrows 33 | \draw (a) -- (mod); 34 | \draw (b) -- (b2); 35 | 36 | \end{tikzpicture} 37 | \end{center} 38 | 39 | \begin{python} 40 | # recursive abbr 41 | def gcd(a, b): 42 | if b == 0: 43 | return a 44 | 45 | return gcd(b, a % b) 46 | 47 | # iterative 48 | def gcd(a, b): 49 | while b != 0: 50 | a, b = b, a % b 51 | 52 | return a 53 | \end{python} 54 | 55 | \rih{Proof}. Euclidean Algorithm. The Euclidean algorithm is based on the principle that the GCD of two numbers $a$ and $b$ is the same as the GCD of $b$ and $a\%b$ until $b$ becomes zero. Prove the following recursive form: 56 | $$ 57 | gcd(a,b) = gcd(b, r) 58 | $$ 59 | \begin{enumerate} 60 | \item \rih{Divisibility}: Let $g$ be the GCD of $a$ and $b$. By definition, $g$ divides both $a$ and $b$. From the equation $a=b\cdot q+r$, it follows that $g$ must also divide the remainder $r$, because any divisor of both $a$ and $b$ divides linear combination of them: $r = a - b \cdot q$. 61 | \item \rih{Reduction}: If we replace $a$ with $b$ and $b$ with $r$, the GCD remains unchanged, and the problem size gets smaller (since $r 0 else True 78 | 79 | n = abs(n) 80 | ret = 1.0 81 | while n > 0: 82 | if n & 1 == 1: 83 | ret *= x 84 | 85 | n >>= 1 86 | x *= x 87 | 88 | if is_invert: 89 | ret = 1.0 / ret 90 | 91 | return ret 92 | \end{python} 93 | \section{Prime Numbers} 94 | \subsection{Sieve of Eratosthenes} 95 | \subsubsection{Basics} 96 | To find all the prime numbers less than or equal to a given integer n by Eratosthenes' method: 97 | \begin{enumerate} 98 | \item Create a list of consecutive integers from 2 through n: (2, 3, 4, ..., n). 99 | \item Initially, let $p$ equal 2, the first prime number. 100 | \item Starting from $p$, enumerate its multiples by counting to n in increments of $p$, and mark them in the list (these will be $2p$, $3p$, $4p$, ... ; the $p$ itself should not be marked). 101 | \item Find the first number greater than $p$ in the list that is not marked. If there was no such number, stop. Otherwise, let $p$ now equal this new number (which is the next prime), and repeat from step 3. 102 | \end{enumerate} 103 | 104 | When the algorithm terminates, the numbers remaining not marked in the list are all the primes below $n$. 105 | 106 | \subsubsection{Refinements} 107 | The main idea here is that every value for $p$ is prime, because we have already marked all the multiples of the numbers less than $p$. Note that some of the numbers being marked may have already been marked earlier (e.g., 15 will be marked both for 3 and 5). 108 | 109 | As a refinement, it is sufficient to mark the numbers in step 3 starting from $p^2$, because all the smaller multiples of $p$ will have already been marked at that point by the previous smaller prime factor other than $p$. From $p^2$, $p$ becomes the smaller prime factor of a composite number. This means that the algorithm is allowed to terminate in step 4 when $p^2$ is greater than n. 110 | 111 | For example, consider $p=5$. The first multiple of 5 that we need to mark is $5^2 = 25$, because: 112 | \begin{enumerate} 113 | \item $5 \times 2 = 10$ has already been marked when processing $p = 2$. 114 | \item $5 \times 3 = 15$ has already been marked when processing $p = 3$ 115 | \item $5 \times 4 = 20$ has already been marked when processing $p = 2$. 116 | \end{enumerate} 117 | Therefore, we only need to start marking multiples from $p^2$, since all smaller multiples of $p$ have already been handled. 118 | 119 | \begin{python} 120 | def count_primes_sieve(N): 121 | if N < 2: 122 | return 0 123 | # intialize all numbers prime candidates 124 | primes = [True for _ in range(N+1)] 125 | # 0 and 1 are not prime numbers 126 | primes[0] = primes[1] = False 127 | 128 | p = 2 129 | while (p * p <= N): 130 | if primes[p]: 131 | for i in range(p * p, N+1, p): 132 | primes[i] = False 133 | p += 1 134 | 135 | return sum(prime) 136 | \end{python} 137 | 138 | \rih{Time complexity}. Iterating \pyinline{p} is $O(N)$ and for all the prime number, each inner loop take $\frac{N}{p}$. Then it becomes 139 | 140 | $$ 141 | \sum_{p \leq N} \frac{N}{p} = N \sum_{p \leq N} \frac{1}{p} 142 | $$ 143 | 144 | It looks like $O(N \log N)$ but $p$ are prime numbers, it becomes $O(N \log \log N)$. The \rih{Prime Number Theorem} tells us that the number of primes less than or equal to $N$ is approximate 145 | $$ 146 | \frac{N}{\log N} 147 | $$ 148 | 149 | 150 | Another refinement is to initialize list odd numbers only, (3, 5, ..., n), and count in increments of $2p$ in step 3, thus marking only odd multiples of $p$. This actually appears in the original algorithm. This can be generalized with wheel factorization, forming the initial list only from numbers coprime with the first few primes and not just from odds (i.e., numbers coprime with 2), and counting in the correspondingly adjusted increments so that only such multiples of $p$ are generated that are coprime with those small primes, in the first place. 151 | 152 | 153 | 154 | To summarized, the refinements include: 155 | \begin{enumerate} 156 | \item Starting from $p^2$; thus $p$ is the smaller prime factor. 157 | \item Preprocessing even numbers and then only process odd numbers; thus the increment becomes $2p$. 158 | \end{enumerate} 159 | 160 | \begin{python} 161 | def count_primes(N): 162 | if N < 3: 163 | return 0 164 | primes = [ 165 | False if i%2 == 0 else True 166 | for i in range(n) 167 | ] 168 | primes[0], primes[1] = False, False 169 | for i in range(3, int(math.sqrt(N))+1, 2): 170 | if primes[i]: 171 | for j in range(i*i, n, 2*i): 172 | primes[j] = False 173 | 174 | return prime.count(True) 175 | \end{python} 176 | 177 | \subsection{Factorization} 178 | Backtracking: Section-\ref{factorization}. 179 | 180 | \section{Median} 181 | \subsection{Basic DualHeap} 182 | \runinhead{Sliding Window Median.} Find the list of median in the sliding window. $\Ra$ Dual heap with lazy deletion. 183 | 184 | DualHeap to keep track the median when a method to find median is called multiple times. 185 | 186 | Here we use the negation of the value as a trick to convert min-heap to max-heap. 187 | \begin{python} 188 | import heapq 189 | 190 | class DualHeap: 191 | def __init__(self): 192 | self.min_h = [] 193 | self.max_h = [] # need to negate the value 194 | 195 | def insert(self, num): 196 | if not self.min_h or num > self.min_h[0]: 197 | heapq.heappush(self.min_h, num) 198 | else: 199 | heapq.heappush(self.max_h, -num) 200 | self.balance() 201 | 202 | def balance(self): 203 | l1 = len(self.min_h) 204 | l2 = len(self.max_h) 205 | if l1-l2 > 1: 206 | heapq.heappush(self.max_h, 207 | -heapq.heappop(self.min_h)) 208 | self.balance() 209 | elif l2-l1 > 1: 210 | heapq.heappush(self.min_h, 211 | -heapq.heappop(self.max_h)) 212 | self.balance() 213 | return 214 | 215 | def get_median(self): 216 | """Straightforward""" 217 | \end{python} 218 | 219 | \subsection{DualHeap with Lazy Deletion}\label{dh_lazy_del} 220 | Clues: 221 | \begin{enumerate} 222 | \item Wrap the value and wrap the heap 223 | \item When delete a value, mark it with tombstone. 224 | \item When negate the value, only change the value, not the reference. 225 | \item When heap pop, clean the op first. 226 | \end{enumerate} 227 | \begin{python} 228 | import heapq 229 | from collections import defaultdict 230 | from dataclasses import dataclass 231 | 232 | @dataclass 233 | class Value: 234 | val: int 235 | deleted: bool 236 | 237 | 238 | class Heap: 239 | def __init__(self): 240 | self.h = [] 241 | self.len = 0 242 | 243 | def push(self, item): 244 | heapq.heappush(self.h, item) 245 | self.len += 1 246 | 247 | def pop(self): 248 | self._clean_top() 249 | self.len -= 1 250 | return heapq.heappop(self.h) 251 | 252 | def remove(self, item): 253 | """lazy delete""" 254 | item.deleted = True 255 | self.len -= 1 256 | 257 | def __len__(self): 258 | return self.len 259 | 260 | def _clean_top(self): 261 | while self.h and self.h[0].deleted: 262 | heapq.heappop(self.h) 263 | 264 | def peek(self): 265 | self._clean_top() 266 | return self.h[0] 267 | 268 | 269 | class DualHeap: 270 | def __init__(self): 271 | self.min_h = Heap() # represent right side 272 | self.max_h = Heap() # represent left side 273 | # others similar as the previous section's above DualHeap 274 | \end{python} 275 | 276 | \section{Modular} 277 | \subsection{Power of 4} 278 | To check whether a number of the power of 4, we can check whether it mod 3 equals 1. 279 | \begin{align*} 280 | 4^a &\equiv 1^a\mod 3 \\ 281 | &\equiv 1 \mod 3 282 | \end{align*} 283 | 284 | Alternatively, we can use bit manipulation based on the power of 4 in the binary form of \pyinline{repeat n 1 << 2}, and checks whether there is even number of 0's in binary form. 285 | 286 | \section{Ord} 287 | \runinhead{Number in lexical order.} Given an integer n, return 1 ~ n in lexicographical order. For example, given 13, return: [1,10,11,12,13,2,3,4,5,6,7,8,9]. 288 | 289 | Enumerate to find the pattern: 290 | \begin{python} 291 | 1 10 11 ... 19 292 | 2 21 22 ... 29 293 | 3 31 32 ... 39 294 | 4 ... 295 | . 296 | . 297 | \end{python} 298 | 299 | Using DFS. 300 | \begin{python} 301 | def dfs(cur, ret, N): 302 | ret.append(cur) 303 | for d in range(10): 304 | nxt = cur * 10 + d 305 | if nxt <= N: 306 | dfs(nxt, ret, N) 307 | else: 308 | break 309 | 310 | N = 105 311 | ret = [] 312 | for i in range(1, 10): 313 | if i <= N: 314 | dfs(i, ret, N) 315 | else: 316 | break 317 | \end{python} 318 | 319 | Optionally, using iterative appraoch. 320 | \begin{python} 321 | def gen(): 322 | i = 1 323 | for _ in range(n): 324 | yield i 325 | if i * 10 <= n: 326 | i *= 10 # * 10 327 | elif i % 10 != 9 and i + 1 <= n: 328 | i += 1 # for current digit 329 | else: 330 | while i % 10 == 9 or i + 1 > n: 331 | i //= 10 332 | i += 1 333 | \end{python} 334 | -------------------------------------------------------------------------------- /chapterSearch.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Search} 3 | 4 | \section{Binary Search} 5 | \runinhead{Variants:} 6 | Find the insertion point 7 | \begin{enumerate} 8 | \item \pyinline{bisect_left} leftmost element to insert the target 9 | \item \pyinline{bisect_right} rightmost element to insert the target 10 | \item Get the idx equal OR just lower (floor) 11 | \item Get the idx equal OR just higher (ceil) 12 | \end{enumerate} 13 | The above four have subtle differences. 14 | 15 | \begin{python} 16 | target = 5 17 | lst = [1, 3, 5, 7, 9] 18 | idx = [0, 1, 2, 3, 4] 19 | ^ ^ 20 | | | 21 | bisect_left bisect_right 22 | \end{python} 23 | 24 | \subsection{bisect\_left} 25 | Return the index where to insert item x in list A. So if t already appears in the list, 26 | A.insert(t) will insert just before the \textit{leftmost} t already there. 27 | 28 | By insertion point \pyinline{i}, it means \begin{python} 29 | all(val <= x for val in A[lo:i]) 30 | \end{python} for the left side. 31 | \begin{python} 32 | all(val > x for val in A[i:hi]) 33 | \end{python} for the right side. \pyinline{A[i]} is the first element larger than x. 34 | \runinhead{Core clues:} 35 | \begin{enumerate} 36 | \item Move \pyinline{lo} if \hl{$A_{mid} < t$} 37 | \item Move \pyinline{hi} if $A_{mid} \geq t$ 38 | \end{enumerate} 39 | 40 | \begin{python} 41 | def bisect_left(A, t, lo, hi): 42 | while lo < hi: 43 | mid = (lo+hi) // 2 44 | if A[mid] < t: 45 | lo = mid+1 46 | else: 47 | hi = mid 48 | 49 | return lo 50 | \end{python} 51 | 52 | \subsection{bisect\_right} 53 | Return the index where to insert item x in list A. So if t already appears in the list, A.insert(t) will insert just after the \textit{rightmost} x already there. 54 | \runinhead{Core clues:} 55 | \begin{enumerate} 56 | \item Move \pyinline{lo} if \hl{$A_{mid} \leq t$} 57 | \item Move \pyinline{hi} if $A_{mid} > t$ 58 | \end{enumerate} 59 | \begin{python} 60 | def bisect_right(A, t, lo, hi): 61 | while lo < hi: 62 | mid = (lo+hi) // 2 63 | if A[mid] <= t: 64 | lo = mid+1 65 | else: 66 | hi = mid 67 | 68 | return lo 69 | \end{python} 70 | \subsection{Generalized bisect} 71 | Find the smallest bound that satisfies some condition of $predicate$: 72 | \begin{python} 73 | def bisect_left(A, predicate, lo, hi): 74 | while lo < hi: 75 | mid = (lo+hi) // 2 76 | if predicate(A[mid]): # go right 77 | lo = mid + 1 78 | else: # left 79 | hi = mid 80 | 81 | return lo 82 | \end{python} 83 | 84 | \subsection{idx equal OR just lower} 85 | Binary search, get the idx of the element equal to or just lower than the target. The returned idx is the $A_{idx} \leq target$. It is possible to return $-1$. It is different from the \pyinline{bisect_lect}. 86 | 87 | \runinhead{Core clues:} 88 | \begin{enumerate} 89 | \item To get ``equal'', \pyinline{return mid}. 90 | \item To get ``just lower'', \pyinline{return lo-1}. 91 | \end{enumerate} 92 | $A_{idx} \leq target$. 93 | \begin{python} 94 | def bi_search(self, A, t, lo, hi): 95 | while lo < hi: 96 | mid = (lo+hi) // 2 97 | if A[mid] == t: 98 | return mid 99 | elif A[mid] < t: 100 | lo = mid+1 101 | else: 102 | hi = mid 103 | 104 | return lo-1 105 | \end{python} 106 | 107 | Using \pyinline{bisect_left} with multiple pre-checks to simply the find process. 108 | \begin{python} 109 | def find(A, v): 110 | # A is sorted 111 | if not A: 112 | return None 113 | if v >= A[-1]: 114 | return A[-1] 115 | if v < A[0]: 116 | return None 117 | 118 | idx = bisect_left(A, v) 119 | if A[idx] == v: 120 | return v 121 | idx -= 1 # already checked before 122 | return A[idx] 123 | \end{python} 124 | 125 | \subsection{idx equal OR just higher} 126 | $A_{idx} \geq target$. 127 | \begin{python} 128 | def bi_search(self, A, t, lo, hi): 129 | while lo < hi: 130 | mid = (lo+hi) // 2 131 | if A[mid] == t: 132 | return mid 133 | elif A[mid] < t: 134 | lo = mid+1 135 | else: 136 | hi = mid 137 | 138 | return lo 139 | \end{python} 140 | \subsection{bisect} 141 | \subsubsection{Built-in Library} 142 | Assuming $A$ is already sorted. 143 | \begin{enumerate} 144 | \item \pyinline{bisect.bisect_left(A, x)}: If $x$ is already present in $A$, the insertion point will be before (to the left of) any existing entries. The return value is suitable for use to \pyinline{list.insert()}. 145 | \item \pyinline{bisect.bisect_left(A, x)}: Similar to \pyinline{bisect_left()}, but returns an insertion point which comes after (to the right of) any existing entries of x in a. The return value is suitable for use to \pyinline{list.insert()}. 146 | \item \pyinline{lst.insert(i, x)}: Insert $x$ at position $i$, the existing $A_i$ is push towards the end 147 | \item \pyinline{bisect_left} only looks at \pyinline{__lt__} 148 | \item \pyinline{bisect_left} can have \pyinline{key}: \pyinline{bisect_left(A, x, lambda a: B[a])} 149 | \end{enumerate} 150 | 151 | \runinhead{Find bounds.} Given a sorted list $A$ and a target $q$, find the largest element less than or equal to $q$ and the smallest element greater than or equal to $q$. If no such element exists, return -1 for that bound. 152 | \begin{python} 153 | def find_bounds(A, q): 154 | idx = bisect.bisect_left(A, q) 155 | if idx < len(A) and A[idx] == q: 156 | lo = A[idx] 157 | else: 158 | lo = A[idx-1] if idx > 0 else -1 159 | 160 | idx = bisect.bisect_right(A, q) 161 | if idx > 0 and A[idx-1] == q: 162 | hi = A[idx-1] 163 | else: 164 | hi = A[idx] if idx < len(A) else -1 165 | 166 | return lo, hi 167 | \end{python} 168 | 169 | \section{Applications} 170 | \subsection{Rotation} 171 | \runinhead{Find Minimum in Rotated Sorted Array.} Case by case analysis. Three cases to consider: 172 | \begin{enumerate} 173 | \item Monotonous 174 | \item Trough 175 | \item Peak 176 | \end{enumerate} 177 | 178 | If the elements can be duplicated, need to detect and skip. 179 | \begin{python} 180 | def find_min(self, A): 181 | lo = 0 182 | hi = len(A) 183 | mini = sys.maxsize 184 | while lo < hi: 185 | mid = (lo+hi)/2 186 | mini = min(mini, A[mid]) 187 | if A[lo] == A[mid]: # JUMP 188 | lo += 1 189 | elif A[lo] < A[mid] <= A[hi-1]: 190 | return min(mini, A[lo]) 191 | elif A[lo] > A[mid] <= A[hi-1]: # trough 192 | hi = mid 193 | else: # peak 194 | lo = mid+1 195 | 196 | return mini 197 | \end{python} 198 | \runinhead{Random Point in Area.} You are given an array of non-overlapping axis-aligned rectangles rects where $rects_i = [a_i, b_i, x_i, y_i]$ indicates the bottom-left corner and the top-right corner point. Design an algorithm to pick a random integer point inside the space covered by one of the given rectangles, including edges. 199 | \begin{enumerate} 200 | \item Probablistic select a point in the area space 201 | \item Need to identify which rectangle for the target area $\Ra$ prefix sum of the area 202 | \item Find the first prefix area sum larger than target area $\Ra$ \pyinline{bisect} 203 | \item Boundary problem: \pyinline{pref = [3, 7, 12]}, when $k=0$, we assign to the 1st rectangle, when $k=3$, we assign to the 2nd rectangle $\Ra$ \pyinline{bisect_right} 204 | \item Assign a point of the target rectangle for the target area 205 | \end{enumerate} 206 | \begin{python} 207 | class Solution: 208 | def __init__(self, rects): 209 | self.rects = rects 210 | 211 | self.pref = [] # prefix sums of area 212 | subtotal = 0 213 | for x1, y1, x2, y2 in rects: 214 | subtotal += (x2 - x1 + 1) * (y2 - y1 + 1) 215 | self.pref.append(subtotal) 216 | 217 | self.total = subtotal 218 | 219 | def pick(self): 220 | k = random.randrange(self.total) 221 | # not bisect_left 222 | i = bisect.bisect_right(self.pref, k) 223 | 224 | base = self.pref[i-1] if i - 1 >= 0 else 0 225 | offset = k - base 226 | 227 | x1, y1, x2, y2 = self.rects[i] 228 | w = (x2 - x1 + 1) 229 | x = x1 + (offset % w) 230 | y = y1 + (offset // w) 231 | return [x, y] 232 | \end{python} 233 | 234 | \section{Combinations} 235 | \subsection{Extreme-value problems}\label{extremeValueProblem} 236 | \runinhead{Longest increasing subsequence (LIS).} Array $A$. 237 | 238 | Clues: 239 | \begin{enumerate} 240 | \item \pyinline{L}: The $min$ index \textit{last/tail} value of LIS of a particular \textit{\textbf{len}}. 241 | \item \pyinline{PI}: result table, store the $\pi$'s idx (predecessor); (optional, to build the LIS, no need if only needs to return the length of LIS) 242 | \item \pyinline{Binary search}: For each currently scanning index \pyinline{i}, if it smaller (i.e. $\neg$ increasing), to maintain the \pyinline{L}, binary search to find the position to update the min value. The \pyinline{bi_search} need to find the element $\geq$ to \pyinline{A[i]}. 243 | \end{enumerate} 244 | \begin{python} 245 | def LIS(self, A): 246 | n = len(A) 247 | L = [-1 for _ in range(n+1)] 248 | k = 1 249 | L[k] = A[0] # store value 250 | for v in A[1:]: 251 | j = bisect.bisect_left(L, v, 1, k+1) 252 | L[j] = v 253 | k += 1 if j == k+1 else 0 254 | 255 | return k 256 | \end{python} 257 | The bisect index range can be avoided by building \pyinline{L} dynamically. Let $L_i$ be the smallest index that a LIS of length $i+1$ ending at that index. 258 | \begin{python} 259 | def LIS(self, A): 260 | L = [] 261 | for i in range(len(A)): 262 | j = bisect.bisect_left( 263 | L, A[i], key=lambda e: A[e] 264 | ) 265 | if j < len(L): 266 | L[j] = i 267 | else: 268 | L.append(i) 269 | 270 | return len(L) 271 | \end{python} 272 | 273 | If need to return the LIS itself, we need to maintain a predecessor array $\pi$. 274 | \begin{python} 275 | def LIS(self, A): 276 | n = len(A) 277 | L = [] 278 | pi = [-1] * n 279 | for i in range(n): 280 | j = bisect.bisect_left( 281 | L, A[i], key=lambda e: A[e] 282 | ) 283 | if j < len(L): 284 | L[j] = i 285 | else: 286 | L.append(i) 287 | 288 | pi[i] = L[j-1] if j-1 >= 0 else -1 289 | 290 | # build the LIS 291 | ret = [] 292 | cur = L[-1] 293 | while cur != -1: 294 | ret.append(A[cur]) 295 | cur = pi[cur] 296 | 297 | ret = ret[::-1] 298 | return ret 299 | \end{python} 300 | 301 | Note that monotonic queue is not applicable here. Monotonic queue is used for sliding-window problems. The LIS problem is fundamentally different because it deals with a subsequence that is not necessarily contiguous, rather than a sliding window that is contiguous. 302 | \section{High dimensional search} 303 | \subsection{2D Search} 304 | \runinhead{2D search matrix I.} Search a target in $m\times n$ mat. 305 | 306 | The mat as the following properties: 307 | \begin{enumerate} 308 | \item Integers in each \textit{row} are sorted from left to right. 309 | \item The first integer of each row is greater than the last integer of the previous row. 310 | \end{enumerate} 311 | $$ 312 | \begin{bmatrix} 313 | 1 & 3 & 5 & 7 \\ 314 | 10 & 11 & 16 & 20 \\ 315 | 23 & 30 & 34 & 50 \\ 316 | \end{bmatrix} 317 | $$ 318 | 319 | Row column search: starting at top right corner: $O(m+n)$. 320 | 321 | Binary search: search rows and then search columns: $O(\log m + \log n)$. 322 | 323 | 324 | \runinhead{2D search matrix II.} Search a target in $m\times n$ mat. 325 | 326 | The mat as the following properties: 327 | \begin{enumerate} 328 | \item Integers in each \textit{row} are sorted from left to right. 329 | \item Integers in each \textit{column} are sorted in ascending from top to bottom. 330 | \end{enumerate} 331 | $$ 332 | \begin{bmatrix} 333 | 1& 4& 7& 11& 15 \\ 334 | 2& 5& 8& 12& 19 \\ 335 | 3& 6& 9& 16& 22 \\ 336 | 10& 13& 14& 17& 24 \\ 337 | 18& 21& 23& 26& 30 \\ 338 | \end{bmatrix} 339 | $$ 340 | 341 | Row column search: starting at top right corner: $O(m+n)$. 342 | 343 | Binary search: search rows and then search columns, but upper bound row and lower bound row: 344 | $$ 345 | O(m \log n) 346 | $$ 347 | More formally 348 | $$O\big(\min(m\log n, n\log m)\big)$$ 349 | \subsection{Axis Projection} 350 | Project the mat dimension from 2D to 1D, using \textit{orthogonal axis}. 351 | 352 | \runinhead{Smallest bounding box.} Given the location $(x, y)$ of one of the 1's, return the area of the smallest bounding box that encloses 1's. 353 | $$ 354 | \begin{bmatrix} 355 | 0& 0& 1& 0 \\ 356 | 0& 1& 1& 0 \\ 357 | 0& 1& 0& 0 \\ 358 | \end{bmatrix} 359 | $$ 360 | 361 | \rih{Clues:} 362 | \begin{enumerate} 363 | \item Project the 1's onto x-axis, binary search for the left bound and right bound of the bounding box. 364 | \item We don't pre-project the axis beforehand, since it will take $O(mn)$ to collect the projected 1d array. Instead, we only project it during binary search when checking the mid item. Checking takes $O(m)$, searching takes $O(\log n)$. 365 | \item Do the same for y-axis. 366 | \end{enumerate} 367 | 368 | Time complexity: $O(m\log n + n \log m)$, where $O(m), O(n)$ is for projection complexity. 369 | 370 | -------------------------------------------------------------------------------- /chapterCombinatorics.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Combinatorics} 3 | \section{Basics} 4 | \subsection{Considerations} 5 | \begin{enumerate} 6 | \item Does \textbf{order} matter? Does the \textbf{timing} of choice matter? 7 | \item Are the object draws \textbf{repeatable}? 8 | \item Are the objects partially \textbf{duplicated}? 9 | \end{enumerate} 10 | If order does not matter or repeated objects, you can pre-set the order. 11 | \subsection{Basic formula} 12 | \begin{eqnarray*} 13 | && {n \choose k} = {n \choose n-k} \\ 14 | && {n \choose k} = \frac{n!}{k!(n-k)!} \\ 15 | && {n\choose k} = {n-1\choose k} + {n-1 \choose k-1} 16 | \end{eqnarray*} 17 | The 1st equation is complimentary. Considering a binary choice $x_i \in {0, 1}$ indicating whether to choose an item \pyinline{e}. Choosing $k$ objects (assigning $1$) directly means not choosing $n-k$ objects (assigning $0$). 18 | \begin{eqnarray*} 19 | X &&= \{x_0, x_1, ... x_{n-1}\} \\ 20 | x_i &&\in {0, 1} \\ 21 | \sum_i{x_i} &&= k \\ 22 | \sum_i{1 - x_i} &&= n - k \\ 23 | |X| &&= n 24 | \end{eqnarray*} 25 | 26 | 27 | The 2nd equation is deduping. Considering a set of objects $A = \{a, b, c, d, e\}$. $n = 5$. The number of total permutations is $5!$. If we want to pick $k=3$ objects: $list = [\alpha_0, \alpha_1, \alpha_2]$, but the order does not matter or element are repeatble, it becomes $set = \{\alpha_0, \alpha_1, \alpha_2\}$. It means $3!$ permutation $list$ is degraded to a single $set$. At the same time, the complimentary $list_{comp} = [\alpha_3, \alpha_4]$ is degraded to a single $set_{comp}$. For example, we are choosing $\{a, b, c\}$ and when the program is processing the permutation $abcde$ or $adbec$, we need to count duplicates for deduping. 28 | \begin{eqnarray*} 29 | \mathbf{abc}de &&= \mathbf{bac}de \\ 30 | \mathbf{abc}de && = \mathbf{abc}ed \\ 31 | \mathbf{a}d\mathbf{b}e\mathbf{c} &&= \mathbf{b}d\mathbf{a}e\mathbf{c} \\ 32 | \mathbf{a}d\mathbf{b}e\mathbf{c} && = \mathbf{a}e\mathbf{b}d\mathbf{c} \\ 33 | ret &&= \frac{5!}{3! \cdot 2!} 34 | \end{eqnarray*} 35 | 36 | The 3rd equation is DP. Let $F_{n, k}$ be the number of combinations of choosing $k$ elements from $n$ elements. We can either pick the $n$-th item or not (note: always choose $k$, not decide whether to choose the $k$-th). 37 | $$ 38 | F_{n,k} = F_{n-1, k} + F_{n-1, k-1} 39 | $$ 40 | 41 | The 1st term is not to choose $n$-th as the $k$-th, and the 2nd term is to choose $n$-th as the $k$-th 42 | 43 | How to construct $nCr$ in code: 44 | \begin{python} 45 | nCr = [ 46 | [0 for _ in range(K)] # not default to 1 47 | for _ in range(N+1) 48 | ] 49 | # nCr[0][x] must be 0 where x > 0 50 | 51 | nCr[0][0] = 1 52 | for n in range(1, N+1): 53 | nCr[n][0] = 1 54 | for r in range(1, K): 55 | nCr[n][r] = nCr[n-1][r-1] + nCr[n-1][r] 56 | \end{python} 57 | 58 | Use built-in library: 59 | \begin{python} 60 | math.comb(n, r) 61 | math.perm(n, r) 62 | math.factorial(n) 63 | \end{python} 64 | 65 | \subsection{N objects, K ceils. Stars \& Bars} 66 | When $N=10, K=3$: 67 | $$ 68 | x_1 + x_2 + x_3 = 10 69 | $$ 70 | is equivalent to 71 | $$ 72 | *****|**|*** 73 | $$ 74 | 75 | , notice that $*$ are non-order, and it is possible to have 76 | $$ 77 | *****||***** 78 | $$ 79 | \\ 80 | then the formula is: 81 | $$ 82 | {n+r \choose r} 83 | $$ 84 | 85 | ,where $r=k-1$. 86 | \\ 87 | \rih{Intuitively}, the meaning is to choose $r$ objects from $n+r$ objects to become the $|$. 88 | 89 | \runinhead{Unique paths.} Given a $m \times n$ matrix, starting from $(0, 0)$, ending at $(m-1, n-1)$, can only goes down or right. What is the number of unique paths? 90 | 91 | Let $F_{i, j}$ be the number of unique paths at \pyinline{[i][j]}. 92 | $$ 93 | F_{i, j} = F_{i-1, j} + F_{i, j-1} 94 | $$ 95 | 96 | \subsection{N objects, K types} \label{N_objects_K_types} 97 | What is the number of permutation of $N$ objects with $K$ different types? To handle duplicates, we need to dedupe: 98 | \begin{align*} 99 | ret &= \frac{A_N^N}{\prod_{k=1}^K{A_{sz(k)}^{sz(k)}}} \\ 100 | &= \frac{N!}{\prod_{k} sz[k]!} 101 | \end{align*} 102 | 103 | \subsection{Inclusion–Exclusion Principle} 104 | \begin{figure}[hbtp] 105 | \centering 106 | \subfloat{\includegraphics[width=0.8\linewidth]{500px-Inclusion-exclusion}} 107 | \caption{Inclusion–exclusion principle} 108 | \label{fig:500px-Inclusion-exclusion} 109 | \end{figure} 110 | \begin{eqnarray*} 111 | |A \cup B \cup C| = |A| + |B| + |C| \\ - |A \cap B| - |A \cap C| - |B \cap C| \\ + |A \cap B \cap C| 112 | \end{eqnarray*} 113 | Generally, 114 | $$ 115 | \Biggl|\bigcup_{i=1}^n A_i\Biggr| = \sum_{k = 1}^{n} (-1)^{k+1} \left( \sum_{1 \leq i_{1} < \cdots < i_{k} \leq n} \left| A_{i_{1}} \cap \cdots \cap A_{i_{k}} \right| \right) 116 | $$ 117 | \section{Combinations with Limited Repetitions} 118 | Determine the number of combinations of 10 letters (order does not matter) that can be formed from 12 letters of 3A, 4B, 5C, i.e. multisets $S=\{3.A, 4.B, 5.C\}$. 119 | 120 | \subsection{Basic Solution} 121 | $|S| = 12$. If there are unlimited the number of any of the letter, it is ${10+2 \choose 10}$ by stars and bars of $x1+x2+x3=10$; then we get the universal set, 122 | $$ 123 | |U|={10+2 \choose 10}={10+2 \choose 2} 124 | $$ 125 | 126 | Let $P_A$ be the set that a 10-combination has \rih{more than} 3A. $P_B$...4B. $P_C$...5C. 127 | 128 | The result is: 129 | \begin{align*} 130 | |3A \cap 4B \cap 5C| = & |U|\\ 131 | & - \sum{(|P_i|\cdot \forall i)} \\ 132 | & + \sum{(|P_i \cap P_j|\cdot \forall i,j)}\\ 133 | & - \sum{(|P_i \cap P_j \cap P_k|\cdot \forall i,j,k)} 134 | \end{align*} 135 | 136 | To calculate $|P_i|$, take $|P_A|$ as an example. $P_A$ means at least 4A -- if we take any one of these 10-combinations in $P_A$ and remove 4A we are left with a 6-combination with unlimited on the numbers of letters, including $A$; thus, 137 | $$ 138 | |P_A|={6+2 \choose 2} 139 | $$ 140 | 141 | Similarly, we can get $P_B, P_C$. 142 | 143 | To calculate $|P_i \cap P_j|$, take $|P_A \cap P_B|$ as an example for 4A and 5B; thus, 144 | $$ 145 | |P_A \cap P_B| = {1+2 \choose 2} 146 | $$ 147 | 148 | Similarly, we can get other $|P_i \cap P_j|$. 149 | 150 | Similarly, we can get other $|P_i \cap P_j \cap P_k|$. 151 | \subsection{Algebra Interpretation} 152 | The number of 10-combinations that can be made from 3A, 4B, 5C is found from the coefficient of $x^{10}$ in the expansion of: 153 | $$ 154 | (1+x+x^2+x^3)(1+x+x^2+x^3+x^4)(1+x+x^2+x^3+x^4+x^5) 155 | $$ 156 | 157 | And we know: 158 | \begin{eqnarray*} 159 | 1+x+x^2+x^3 = (1-x^4)/(1-x) \\ 160 | 1+x+x^2+x^3+x^4 = (1-x^5)/(1-x) \\ 161 | 1+x+x^2+x^3+x^4+x^5 = (1-x^6)/(1-x) \\ 162 | \end{eqnarray*} 163 | 164 | 165 | We expand the formula, although the naive way of getting the coefficient of $x^{10}$ is tedious. 166 | 167 | \section{Permutation} 168 | 169 | \subsection{$k$-th permutation} 170 | Given $n$ and $k$, return the $k$-th permutation sequence. $k\in [1, n!]$. $O(n!)$ in time complexity is easy. 171 | \begin{python} 172 | def getPermutation(n, k): 173 | A = [i + 1 for i in range(n)] 174 | ret = [] 175 | genPermutation(A, 0, [], ret) 176 | ret.sort() # requried 177 | return ret[k - 1] 178 | 179 | def genPermutation(A, idx, cur, ret): 180 | if idx == len(A): 181 | ret.append("".join(map(str, cur))) 182 | 183 | for j in range(idx, len(A)): 184 | A[idx], A[j] = A[j], A[idx] 185 | cur.append(A[idx]) 186 | genPermutation(A, idx + 1, cur, ret) 187 | A[j], A[idx] = A[idx], A[j] 188 | cur.pop() 189 | \end{python} 190 | 191 | Can we do it in $O(nk)$ or less? 192 | 193 | Reversed Cantor Expansion 194 | 195 | Core clues: 196 | \begin{enumerate} 197 | \item \pyinline{A = [1, 2, ..., n]} 198 | 199 | Suppose for $n$ element, the $k$-th permutation is: 200 | 201 | \pyinline{ret = [a0, a1, a2, ..., an-1]}. $A_i$ is different from $a_i$. 202 | \item \rih{Basic case.} Since \pyinline{[a1, a3, ..., an-1]} has $(n-1)!$ permutations, 203 | if $k < (n-1)!, a_0 = A_0$ (first element in array), else $a_0 = A_{k/(n-1)!}$ 204 | 205 | \item Recursively, (or iteratively), calculate the values at each position. Similar to Radix. 206 | \begin{enumerate} 207 | \item $a_0 = A_{k_0/(n-1)!}$, where $k_0 = k$ 208 | \item $a_1 = A_{k_1/(n-2)!}$, where $k_1 = k_0\%(n-1)!$ in the remaining array $A$ 209 | \item $a_2 = A_{k_2/(n-3)!}$, where $k_2 = k_1\%(n-2)!$ in the remaining array $A$ 210 | \end{enumerate} 211 | \end{enumerate} 212 | \begin{python} 213 | def getPermutation(self, n, k): 214 | k -= 1 # since k was 1-indexed 215 | 216 | A = [i + 1 for i in range(n)] 217 | # k %= math.factorial(n) # if k > n! 218 | ret = [] 219 | for i in range(n-1, -1, -1): 220 | idx = k // math.factorial(i) 221 | cur = A.pop(idx) 222 | ret.append(cur) 223 | k = k % math.factorial(i) 224 | 225 | return "".join(map(str, ret)) 226 | \end{python} 227 | \runinhead{Next Permutation.} A permutation of an array of integers is an arrangement of its members into a sequence or linear order. For example, for arr = [1,2,3], the following are all the permutations of arr: [1,2,3], [1,3,2], [2, 1, 3], [2, 3, 1], [3,1,2], [3,2,1]. 228 | 229 | Return the next permutation of a given array. 230 | 231 | \rih{Core Clues:} 232 | \begin{enumerate} 233 | \item Obervations: 234 | \begin{python} 235 | [1, 2, 3, 4] [1, 2, 4, 3] 236 | [1, 3, 4, 2] : [1, 4, 2, 3] 237 | [1, 4, 2, 3, 3, 2] : [1, 4, 3, 2, 2, 3] 238 | [1, 4, 2, 3, 2, 3] : [1, 4, 2, 3, 3, 2] 239 | \end{python} 240 | \item find the suffix shape of $\backslash$, and the pivot point. 241 | \begin{python} 242 | [1, 2, 3, 4] : [1, 2, 4, 3] 243 | ^ 244 | [1, 3, 4, 2] : [1, 4, 2, 3] 245 | ^ 246 | [1, 3, 5, 4, 2] : [1, 4, 2, 3, 5] 247 | ^ 248 | \end{python} 249 | \item scanning from right to left, find the first larger item than pivot 250 | \begin{python} 251 | [1, 2, 3, 4] : [1, 2, 4, 3] 252 | ^ | 253 | [1, 3, 4, 2] : [1, 4, 2, 3] 254 | ^ | 255 | [1, 3, 5, 4, 2] : [1, 4, 2, 3, 5] 256 | ^ | 257 | \end{python} 258 | \item Swap the rightmost first larger item and make the suffix shape of / 259 | \begin{python} 260 | [1, 2, 3, 4] -> [1, 2, 4, 3] : [1, 4, 2, 3] 261 | ^ 262 | [1, 3, 4, 2] -> [1, 4, 3, 2] : [1, 4, 2, 3] 263 | ^ | 264 | [1, 3, 5, 4, 2] -> [1, 4, 5, 3, 2] : [1, 4, 2, 3, 5] 265 | ^ | 266 | \end{python} 267 | \end{enumerate} 268 | \begin{python} 269 | def nextPermutation(self, A): 270 | N = len(A) 271 | # 1) find pivot, find the suffix shape of \ 272 | i = N-2 273 | while i >= 0 and not A[i] < A[i + 1]: 274 | i -= 1 275 | 276 | if i < 0: 277 | # already the highest permutation 278 | A.reverse() 279 | return 280 | 281 | # 2) find rightmost element greater than pivot 282 | j = N-1 283 | while not A[j] > A[i]: 284 | j -= 1 285 | 286 | # 3) swap 287 | A[i], A[j] = A[j], A[i] 288 | 289 | # 4) make the suffix shape of /, reverse 290 | l, r = i+1, N-1 291 | while l < r: 292 | A[l], A[r] = A[r], A[l] 293 | l += 1 294 | r -= 1 295 | \end{python} 296 | \subsection{Numbers counting} 297 | \runinhead{Count numbers with unique digit.} Given a non-negative integer n, count all numbers with unique digits, $x$, where $0 \leq x < 10^n$. 298 | 299 | Digit by digit: 300 | \begin{enumerate} 301 | \item The 1st digit has 10 possibilities. The 2nd digit has 9 possibilities. Therefore it seems to be $A_{10}^n$. 302 | \item Exception: The first digit cannot be 0. Therefore it is $9\times 9\times 8\times ...\times (10-i)$ 303 | \end{enumerate} 304 | 305 | 306 | 307 | \section{Catalan Number}\label{section:catalanNumber} 308 | \subsection{Math} 309 | \runinhead{Definition.} 310 | $$ 311 | C_n = {2n\choose n} - {2n\choose n+1} = {1\over n+1}{2n\choose n} \quad\text{ for }n\ge 0 312 | $$ 313 | \runinhead{Proof.} Proof of Calatan Number $C_n ={2n\choose n} - {2n\choose n+1}$. Objective: count the number of paths in $n\times n$ grid without exceeding the main diagonal. 314 | \begin{itemize} 315 | \begin{figure}[] 316 | \centerline{\includegraphics[height = 1.6in]{catalan_proof}} 317 | \caption{Monotonic Paths} 318 | \label{fig:catalanProof} 319 | \end{figure} 320 | \item Total monotonic paths including both exceeding and not exceeding - from $2n$ choose $n$ right and $n$ up: 321 | $$ 322 | {2n\choose n} 323 | $$ 324 | \item Find out how many combinations that exceeds the main diagonal. Flip all the exceeding lines at the line just above the diagonal line, we will see a rectangle of size $(n-1) * (n+1)$ has formed. Thus the total number of monotoic paths in the new rectange is - from $2n$ choose $n-1$ right and $n+1$ up: 325 | $$ 326 | {n-1+n+1\choose n-1} 327 | $$ 328 | \item Thus, the number of path without \textit{exceeding} (i.e. passing the diagonal line) is: 329 | \begin{align*} 330 | C_n &= {2n\choose n} - {2n\choose n-1}\\ 331 | &={2n\choose n} - {2n\choose n+1} 332 | \end{align*} 333 | \end{itemize} 334 | 335 | \subsection{Applications} 336 | The paths in Figure \ref{fig:catalanProof} can be abstracted to anything that at any time \#right $\geq$ \#up. 337 | \runinhead{\#Parentheses.}Number of different ways of adding parentheses. At any time, \#\pyinline{(} $\geq$ \#\pyinline{)}. 338 | \runinhead{\#Full binary trees.}Number of different full bindary tree. A full binary tree is a tree in which every node has either 0 or 2 children. Consider it as a set of same binary operators with their operands. Reduce this problem to \#Parentheses. 339 | \begin{figure}[hbtp] 340 | \centering 341 | \subfloat{\includegraphics[width=\linewidth]{Catalan_number_binary_tree_example}} 342 | \caption{\#Full bindary trees. Circles are operators; crescents are operands.} 343 | \label{fig:NumberOfBSTs} 344 | \end{figure} 345 | 346 | \section{Stirling Number} 347 | A Stirling number of the second kind (or Stirling partition number) is the number of ways to partition a set of n objects into k non-empty subsets and is denoted by $S(n,k)$ or $\lbrace{n\atop k}\rbrace$. 348 | 349 | $$ 350 | \left\{ {n \atop k}\right\} = \frac{1}{k!}\sum_{j=0}^{k} (-1)^{k-j} \binom{k}{j} j^n. 351 | $$ 352 | -------------------------------------------------------------------------------- /chapterBacktracking.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Backtracking} 3 | \section{Introduction} 4 | \runinhead{Difference between backtracking and dfs.} \textit{Backtracking} is a more general purpose algorithm. \textit{Dfs} is a specific form of backtracking related to searching tree structures. 5 | 6 | \runinhead{Prune.} Backtrack need to think about pruning using the condition \pyinline{predicate}. 7 | 8 | \runinhead{Jump.} Jump to skip ones the same as its parent to avoid duplication. 9 | 10 | \runinhead{Complexity.} $O(b^d)$, where $b$ is the branching factor and $d$ is the depth. 11 | 12 | 13 | \section{Memoization} 14 | dfs can be memoized. 15 | \begin{python} 16 | import functools 17 | 18 | # default is maxsize=128 19 | @functools.lru_cache(maxsize=None) 20 | def dfs(self, *args): 21 | pass 22 | 23 | 24 | @functools.lru_cache(maxsize=None) 25 | def F(self, i, j, A): 26 | return min( 27 | ( 28 | self.F(i, k, A) + self.F(k, j, A) 29 | + max(A[i:k]) * max(A[k:j]) 30 | for k in range(i+1, j) 31 | ), # generator must be parenthesized 32 | default=0, # default for empty seq 33 | ) 34 | \end{python} 35 | \section{Permutations} 36 | \runinhead{Permutation.} Generate all permutaitons of a list $A$. 37 | \runinhead{Core Clues:} 38 | \begin{itemize} 39 | \item Advancing the index \pyinline{cur} 40 | \end{itemize} 41 | 42 | \begin{python} 43 | # itertools.permutations 44 | 45 | def permutations(self, A, cur, ret): 46 | # in-place 47 | if cur == len(A): 48 | ret.append(list(A)) # clone 49 | return 50 | 51 | for i in range(cur, len(A)): 52 | # swap 53 | A[cur], A[i] = A[i], A[cur] 54 | self.permutations(A, cur+1, ret) 55 | # restore 56 | A[i], A[cur] = A[cur], A[i] 57 | \end{python} 58 | 59 | \runinhead{Permutation and subsequence.} Return the number of all possible non-empty subsequences of letters we can permutate. For example \pyinline{input = "AAB"}. The possible sequences are $A, B, AA, AB, BA, AAB, ABA, BAA$. 60 | 61 | \runinhead{Core Clues:} 62 | \begin{itemize} 63 | \item All elements are interconnected as neighbors 64 | \item There is no advancing of index \pyinline{i}. This is more like DFS than backtracking. 65 | \end{itemize} 66 | \begin{python} 67 | def dfs(self, inputs, visited, cur, ret): 68 | # add to result set as we build the subsequences 69 | ret.add("".join(cur)) 70 | 71 | for i, v in enumerate(inputs): 72 | # iterate all neighbors 73 | if not visited[i]: 74 | visited[i] = True 75 | cur.append(v) 76 | self.dfs(inputs, visited, cur, ret) 77 | cur.pop() 78 | visited[i] = False 79 | \end{python} 80 | 81 | \section{Sequence} 82 | \runinhead{k sum.} Given $n$ unique integers, number $k$ and target. Find all possible $k$ integers where their sum is target. 83 | 84 | Complexity: $O(2^n)$. 85 | 86 | Pay attention to the pruning condition. 87 | 88 | \begin{python} 89 | def dfs(self, A, i, k, cur, remain, ret): 90 | """self.dfs(A, 0, k, [], target, ret)""" 91 | if len(cur) == k and remain == 0: 92 | ret.append(list(cur)) # clone 93 | return 94 | 95 | if i >= len(A) or len(cur) > k 96 | or len(A) - i + len(cur) < k: 97 | return 98 | 99 | # not select 100 | self.dfs(A, i+1, k, cur, remain, ret) 101 | 102 | # select 103 | cur.append(A[i]) 104 | self.dfs(A, i+1, k, cur, remain-A[i], ret) 105 | cur.pop() 106 | \end{python} 107 | 108 | 109 | \section{String} 110 | In general, 111 | \begin{itemize} 112 | \item Break down the sequence into \pyinline{left} and \pyinline{right} two parts. 113 | \item Choose \pyinline{left} as terminal state, while DFS search the \pyinline{right}. 114 | \item Combine \pyinline{left} and the search results from \pyinline{right}. 115 | \item Sometimes it is easier to search \pyinline{left} and choose \pyinline{right} as terminal state. 116 | \end{itemize} 117 | \subsection{Palindrome} 118 | \subsubsection{Palindrome partition.} Given \pyinline{s = "aab"}, return: \\ 119 | \pyinline{[["aa","b"], ["a","a","b"]]} 120 | \\ 121 | \runinhead{Core clues:} 122 | \begin{enumerate} 123 | \item Expand the search tree \textbf{horizontally}. 124 | \end{enumerate} 125 | \rih{Search process:} 126 | \begin{python} 127 | input: "aabbc" 128 | 129 | "a", "abbc" 130 | "a", "bbc" 131 | "b", "bc" 132 | "b", "c" (o) 133 | "bc" (x) 134 | "bb", "c" (o) 135 | "bbc" (x) 136 | "ab", "bc" (x) 137 | "abb", "c" (x) 138 | "abbc" (x) 139 | "aa", "bbc" 140 | "b", "bc" 141 | "b", "c" (o) 142 | "bc" (x) 143 | "bb", "c" (o) 144 | "bbc" (x) 145 | "aab", "bc" (x) 146 | "aabb", "c" (x) 147 | \end{python} 148 | Code: 149 | 150 | \begin{python} 151 | def partition(self, s): 152 | ret = [] 153 | self.backtrack(s, [], ret) 154 | return ret 155 | 156 | def backtrack(self, s, cur_lvl, ret): 157 | """ 158 | Let i be the scanning ptr. 159 | If s[:i] passes predicate, then backtrack s[i:] 160 | """ 161 | if not s: 162 | ret.append(list(cur_lvl)) # clone 163 | 164 | for i in range(1, len(s)+1): 165 | if self.predicate(s[:i]): 166 | cur_lvl.append(s[:i]) 167 | self.backtrack(s[i:], cur_lvl, ret) 168 | cur_lvl.pop() 169 | 170 | def predicate(self, s): 171 | return s == s[::-1] 172 | \end{python} 173 | 174 | \subsection{Word Abbreviation} 175 | \runinhead{Core clues:} 176 | \begin{enumerate} 177 | \item Pivot a letter 178 | \item Left side as a number, right side dfs 179 | \end{enumerate} 180 | 181 | \begin{python} 182 | def dfs(self, word): 183 | if not word: 184 | yield "" 185 | 186 | for l in range(len(word)+1): 187 | left_num = str(l) if l else "" 188 | for right in self.dfs(word[l+1:]): 189 | yield left_num + word[l:l+1] + right 190 | # note word[l:l+1] and right default '' 191 | \end{python} 192 | 193 | \subsection{Split Array - Minimize Maximum Subarray Sum} 194 | Split the array into $m$ parts and minimize the max subarray sum. 195 | \runinhead{Core clues:} 196 | \begin{enumerate} 197 | \item Take one subarray from left, and search the right side for the minimum max subarray. 198 | \item To make process in the DFS, always make the left part a subarray, and DFS the right part. 199 | \item Pivot an index to break the array into the left and right parts. 200 | \end{enumerate} 201 | 202 | Search right. 203 | \begin{python} 204 | def dfs(self, cur, m): 205 | """ 206 | * p break the nums[cur:] into left and right part 207 | * sums is the prefix sum (sums[i] == sum(nums[:i])) 208 | """ 209 | if m == 1: 210 | return self.sums[len(nums)] - self.sums[cur] 211 | 212 | mini = float("inf") 213 | for j in range(cur, lens(nums)): 214 | left = self.sums[j + 1] - self.sums[0] 215 | right = self.dfs(j + 1, m - 1) 216 | # minimize the max 217 | mini = min(mini, max(left, right)) 218 | 219 | return mini 220 | \end{python} 221 | 222 | Alternatively, search left. 223 | \begin{python} 224 | def dfs(self, hi, m): 225 | """ 226 | j break the nums[:hi] into left and right part 227 | sums is the prefix sum (sums[i] == sum(nums[:i])) 228 | """ 229 | if m == 1: 230 | return self.sums[hi] - self.sums[0] 231 | 232 | mini = float("inf") 233 | for j in range(hi): 234 | right = self.sums[hi] - self.sums[j] 235 | left = self.dfs(j, m - 1) 236 | # minimize the max 237 | mini = min(mini, max(left, right)) 238 | 239 | return mini 240 | \end{python} 241 | 242 | 243 | \section{Cartesian Product} 244 | Each state can generate multiple combinations. Search through all the combinations. 245 | \subsection{Pyramid Transition Matrix.} 246 | \begin{python} 247 | """ 248 | (H, I ..) 249 | / \ 250 | (D,E) (F, G) 251 | / \ / \ 252 | A B C 253 | """ 254 | def dfs( 255 | self, 256 | T: Dict[Tuple[str, str], Set[str]], 257 | level: str, 258 | ) -> bool: 259 | """ 260 | T - Transition matrix 261 | stores all the possible end states from state1 262 | and state2 263 | [s1, s2] -> set of end states 264 | """ 265 | if len(level) == 1: 266 | return True 267 | 268 | for nxt_level in itertools.product( 269 | *[T[a, b] for a, b in zip(level, level[1:])] 270 | ): 271 | if self.dfs(T, nxt_level): 272 | return True 273 | 274 | return False 275 | 276 | \end{python} 277 | 278 | 279 | \section{Math} 280 | \subsection{Decomposition} 281 | \subsubsection{Factorize a number}\label{factorization} 282 | \runinhead{Core clues:} 283 | \begin{enumerate} 284 | \item Expand the search tree \textbf{horizontally}. 285 | \item Take the last number on the stack, and factorize it recursively. 286 | \end{enumerate} 287 | \rih{Search tree:} 288 | \begin{python} 289 | Input: 16 290 | get factors of cur[-1] 291 | [16] 292 | [2, 8] 293 | [2, 2, 4] 294 | [2, 2, 2, 2] 295 | 296 | [4, 4] 297 | \end{python} 298 | Take the last number from the list and factorize it. 299 | 300 | Code: 301 | 302 | \begin{python} 303 | ret = [] # collector 304 | 305 | def dfs(cur, remain, ret): 306 | if remain == 1: 307 | res.append(list(cur)) 308 | return 309 | 310 | start = 2 if not cur else cur[-1] 311 | # if start = 2, it generates duplicate combinations 312 | for factor in range(start, remain + 1): 313 | if remain % factor == 0: 314 | dfs(cur + [factor], remain // factor) 315 | 316 | dfs([], target, ret) 317 | \end{python} 318 | 319 | Using a single stack to conserve space, we need to maintain the stack \pyinline{cur}. 320 | \begin{python} 321 | self.dfs([16], []) 322 | 323 | def dfs(self, cur, ret): 324 | if len(cur) > 1: 325 | ret.append(list(cur)) # clone 326 | 327 | n = cur.pop() 328 | start = cur[-1] if cur else 2 329 | for i in range(start, int(sqrt(n))+1): 330 | if self.predicate(n, i): 331 | cur.append(i) 332 | cur.append(n // i) 333 | self.dfs(cur, ret) 334 | cur.pop() # pop the i here. pop n // i in dfs 335 | 336 | def predicate(self, n, i): 337 | return n % i == 0 338 | 339 | \end{python} 340 | \runinhead{Time complexity.} The search tree's size is $O(2^n)$ where $n$ is the number 341 | of prime factors. Choose $i$ prime factors to combine then, and keep the rest uncombined. 342 | 343 | 344 | $$\sum_i {n \choose i} = 2^n$$ 345 | 346 | \section{Arithmetic Expression} 347 | \subsection{Unidirection} 348 | \rih{Insert operators.} Given a string that contains only digits 0-9 and a target value, 349 | return all possibilities to add binary operators (not unary) +, -, or * between the 350 | digits so they evaluate to the target value. 351 | 352 | Example: 353 | \begin{align*} 354 | ``123", 6 \rightarrow [``1+2+3", ``1*2*3"] \\ 355 | ``232", 8 \rightarrow [``2*3+2", ``2+3*2"] \\ 356 | \end{align*} 357 | Clues: 358 | \begin{enumerate} 359 | \item Backtracking with \textit{horizontal} expanding 360 | \item Special handling for multiplication - caching the expression \textit{predecessor} 361 | for multiplication association. 362 | \item Detect \textit{invalid} number with leading 0's 363 | \end{enumerate} 364 | 365 | \begin{python} 366 | def addOperators(self, num, target): 367 | ret = [] 368 | self.dfs(num, target, 0, "", 0, 0, ret) 369 | return ret 370 | 371 | def dfs( 372 | self, 373 | num, 374 | target, 375 | pos, # scanning index 376 | cur_str, # The current str builder 377 | cur_val, # To reach the target 378 | mul, # first operand for multiplication 379 | ret, 380 | ): 381 | if pos >= len(num): 382 | if cur_val == target: 383 | ret.append(cur_str) 384 | else: 385 | for i in range(pos, len(num)): 386 | if i != pos and num[pos] == '0': 387 | continue 388 | 389 | nxt_val = int(num[pos:i+1]) 390 | if not cur_str: # 1st number 391 | self.dfs(num, target, i+1, 392 | f"nxt_val", nxt_val, 393 | nxt_val, ret) 394 | else: # +, -, * 395 | self.dfs(num, target, i+1, 396 | f"{cur_str}+{nxt_val}", cur_val+nxt_val, 397 | nxt_val, ret) 398 | self.dfs(num, target, i+1, 399 | f"{cur_str}-{nxt_val}", cur_val-nxt_val, 400 | -nxt_val, ret) 401 | self.dfs(num, target, i+1, 402 | f"{cur_str}*{nxt_val}", cur_val-mul+mul*nxt_val, 403 | mul*nxt_val, ret) 404 | \end{python} 405 | \subsection{Bidirection} 406 | \rih{Insert parenthesis.} Given a string of numbers and operators, return all possible 407 | results from computing all the different possible ways to group numbers and operators. 408 | The valid operators are +, - and *. 409 | 410 | Examples: 411 | \begin{align*} 412 | (2*(3-(4*5))) &= -34 \\ 413 | ((2*3)-(4*5)) &= -14 \\ 414 | ((2*(3-4))*5) &= -10 \\ 415 | (2*((3-4)*5)) &= -10 \\ 416 | (((2*3)-4)*5) &= 10 417 | \end{align*} 418 | Clues: Iterate the operators, divide and conquer - left parts and right parts and then 419 | combine result. \\ 420 | Code: 421 | \begin{python} 422 | def dfs_eval(self, nums, ops): 423 | ret = [] 424 | if not ops: 425 | assert len(nums) == 1 426 | return nums 427 | 428 | for i, op in enumerate(ops): 429 | left_vals = self.dfs_eval(nums[:i+1], ops[:i]) 430 | right_vals = self.dfs_eval(nums[i+1:], ops[i+1:]) 431 | for l in left_vals: 432 | for r in right_vals: 433 | ret.append(self._eval(l, r, op)) 434 | 435 | return ret 436 | \end{python} 437 | \section{Parenthesis} 438 | \runinhead{Remove Invalid Parentheses.} Remove the minimum number of invalid parentheses in order to make the input string valid. Return all possible results. 439 | 440 | Core clues: 441 | \begin{enumerate} 442 | \item \textbf{Backtracking}: All possible results $\ra$ backtrack. 443 | \item \textbf{Minrm}: Find the minimal number of removal. 444 | \item \textbf{Jump}: To avoid duplicate, remove all brackets same as previous one $\pi$ at once. 445 | \end{enumerate} 446 | 447 | To find the minimal number of removal: 448 | \begin{python} 449 | def minrm(self, s): 450 | """ 451 | returns minimal number of removals 452 | """ 453 | rmcnt = 0 454 | left = 0 455 | for c in s: 456 | if c == "(": 457 | left += 1 458 | elif c == ")": 459 | if left > 0: 460 | left -= 1 461 | else: 462 | rmcnt += 1 463 | 464 | rmcnt += left 465 | return rmcnt 466 | \end{python} 467 | To return all possible results, do backtracking: 468 | \begin{python} 469 | def dfs(self, s, cur, left, pi, i, rmcnt, ret): 470 | """ 471 | Remove parenthesis 472 | backtracking, post-check 473 | :s: original string 474 | :cur: current string builder 475 | :left: number of remaining left parentheses in s[0..i] 476 | :pi: last removed char 477 | :i: current index 478 | :rmcnt: number of remaining removals needed 479 | :ret: results 480 | """ 481 | if left < 0 or rmcnt < 0 or i > len(s): 482 | return 483 | if i == len(s): 484 | if rmcnt == 0 and left == 0: 485 | ret.append(cur) 486 | return 487 | 488 | if s[i] not in ("(", ")"): # skip non-parenthesis 489 | self.dfs(s, cur+s[i], left, None, i+1, rmcnt, ret) 490 | else: 491 | if pi == s[i]: 492 | while i < len(s) and pi and pi == s[i]: 493 | i += 1 494 | rmcnt -= 1 495 | self.dfs(s, cur, left, pi, i, rmcnt, ret) 496 | else: 497 | self.dfs(s, cur, left, s[i], i+1, rmcnt-1, ret) 498 | L = left+1 if s[i] == "(" else left-1 # consume "(" 499 | self.dfs(s, cur+s[i], L, None, i+1, rmcnt, ret) # not rm 500 | \end{python} 501 | \section{Tree} 502 | \subsection{BST} 503 | \subsubsection{Generate Valid BST} 504 | Generate all valid BST with nodes from 1 to $n$. 505 | \runinhead{Core clues:} 506 | \begin{enumerate} 507 | \item Iterate pivot 508 | \item Generate left and right 509 | \end{enumerate} 510 | Code: 511 | \begin{python} 512 | def generate(self, start, end) -> List[TreeNode]: 513 | roots = [] 514 | if start > end: 515 | roots.append(None) 516 | return roots 517 | 518 | for pivot in range(start, end+1): 519 | left_roots = self.generate_cache(start, pivot-1) 520 | right_roots = self.generate_cache(pivot+1, end) 521 | 522 | for left_root in left_roots: 523 | for right_root in right_roots: 524 | root = TreeNode(pivot) # new instance 525 | root.left = left_root 526 | root.right = right_root 527 | 528 | roots.append(root) 529 | 530 | return roots 531 | \end{python} 532 | \subsection{Graph} 533 | \runinhead{Word Search.} Given an $m \times n$ grid of characters board and a string word, return true if word exists in the grid. 534 | \rih{Core Clues:} 535 | \begin{enumerate} 536 | \item dfs 537 | \end{enumerate} 538 | \begin{python} 539 | dirs = [(-1, 0), (1, 0), (0, -1), (0, 1)] 540 | def exist(self, A, word) -> bool: 541 | M, N = len(A), len(A[0]) 542 | L = len(word) 543 | visited = [ 544 | [False for _ in range(N)] 545 | for _ in range(M) 546 | ] 547 | 548 | def dfs(r, c, i) -> bool: 549 | if i == L: 550 | return True 551 | 552 | if not (0 < r <= M and 0 < c <= N): 553 | return False 554 | 555 | if A[r][c] != word[i]: 556 | return False 557 | 558 | visited[r][c] = True 559 | for dr, dc in dirs: 560 | if dfs(r + dr, c + dc, i + 1): 561 | return True 562 | 563 | visited[r][c] = False 564 | return False 565 | 566 | for r in range(M): 567 | for c in range(N): 568 | if dfs(r, c, 0): 569 | return True 570 | 571 | return False 572 | \end{python} 573 | 574 | 575 | -------------------------------------------------------------------------------- /chapterSort.tex: -------------------------------------------------------------------------------- 1 | % !TEX root = algo-quicksheet.tex 2 | \chapter{Sort} 3 | 4 | 5 | \section{Introduction} 6 | List of general algorithms: 7 | \begin{enumerate} 8 | \item Selection sort: invariant 9 | \begin{enumerate} 10 | \item Elements to the left of $i$ (including $i$) are fixed and in ascending order (fixed and sorted). 11 | \item No element to the right of $i$ is smaller than any entry to the left of $i$ ($A[i] \leq\min(A[i+1:n])$. 12 | \end{enumerate} 13 | \item Insertion sort: invariant 14 | \begin{enumerate} 15 | \item Elements to the left of $i$ (including $i$) are in ascending order (sorted). 16 | \item Elements to the right of $i$ have not yet been seen. 17 | \end{enumerate} 18 | \item Shell sort: h-sort using insertion sort. 19 | \item Quick sort: invariant 20 | \begin{enumerate} 21 | \item $|A_p|..\leq..|..unseen..|..\geq..|$ maintain the 3 subarrays. 22 | \end{enumerate} 23 | \item Heap sort: compared to quick sort it is guaranteed $O(n \lg n)$, compared to merge sort it is $O(1)$ extra space. 24 | \end{enumerate} 25 | 26 | \section{Algorithms} 27 | \subsection{Binary / Quick Sort} 28 | \rih{Concise}. Concise but space inefficient: 29 | \begin{python} 30 | def quicksort(self, A): 31 | if len(A) <= 1: 32 | return A 33 | pivot = A[len(A) // 2] 34 | left = [e for e in A if e < pivot] 35 | middle = [e for e in A if e == pivot] 36 | right = [e for e in A if e > pivot] 37 | return quicksort(left) + middle + quicksort(right) 38 | \end{python} 39 | \rih{Pivoting}. Pivoting/Partitioning to be space efficient: 40 | \begin{python} 41 | def quicksort(self, A, lo, hi): 42 | if lo >= hi: 43 | return 44 | p = pivot(A, lo, hi) 45 | quicksort(A, lo, p-1) 46 | quicksort(A, p+1, hi) 47 | \end{python} 48 | 49 | \subsubsection{Normal pivoting}\label{section:pivot} 50 | The key part of quick sort is pivoting. Using the last element as pivot: 51 | \begin{python} 52 | def pivot(self, A, lo, hi): 53 | """ 54 | pivoting algorithm: 55 | | left array | right array | p | 56 | | left array | p | right array | 57 | """ 58 | p = hi 59 | l = lo 60 | for i in range(lo, hi - 1): 61 | if A[i] < A[p]: 62 | A[i], A[l] = A[l], A[i] 63 | l += 1 64 | 65 | A[l], A[hi] = A[hi], A[l] 66 | return l 67 | \end{python} 68 | 69 | Alternatively, Using the first element as pivot: 70 | \begin{python} 71 | def pivot(self, A, lo, hi): 72 | """ 73 | pivoting algorithm: 74 | | p | left array | right array | 75 | | left array | p | right array | 76 | """ 77 | p = lo 78 | l = lo # left array ending index 79 | for i in range(lo + 1, hi): 80 | if A[i] < A[p]: 81 | l += 1 82 | A[i], A[l] = A[l], A[i] 83 | 84 | A[l], A[p] = A[p], A[l] 85 | return l 86 | \end{python} 87 | 88 | Notice that this implementation goes $O(N^2)$ for arrays with all duplicates. 89 | 90 | \textbf{Problem with duplicate keys}: it is important to stop scan at duplicate 91 | keys (counter-intuitive); otherwise quick sort will goes $O(N^2)$ for the 92 | array with all duplicate items, because the algorithm will put all items 93 | equal to the $A[p]$ on \textbf{a single side}. 94 | 95 | Example: quadratic time to sort random arrays of 0s and 1s. 96 | 97 | \subsubsection{Stop-at-equal pivoting} 98 | Alternative pivoting implementation with optimization for duplicated keys: 99 | \begin{python} 100 | def pivot_optimized(self, A, lo, hi): 101 | """ 102 | Fix the pivot as the 1st element 103 | Scan from left to right and right to left simultaneously 104 | Avoid the case that the algo goes O(N^2) with duplicated keys 105 | """ 106 | p = lo 107 | i = lo 108 | j = hi 109 | while True: 110 | while True: 111 | i += 1 112 | if i >= hi or A[i] >= A[lo]: 113 | break 114 | while True: 115 | j -= 1 116 | if j < lo or A[j] <= A[lo]: 117 | break 118 | 119 | if i >= j: 120 | break 121 | 122 | A[i], A[j] = A[j], A[i] 123 | 124 | A[lo], A[j] = A[j], A[lo] 125 | return j 126 | 127 | \end{python} 128 | \subsubsection{3-way pivoting} 129 | This problem is also known as \textit{Dutch national flag problem}. 130 | 131 | 3-way pivoting: pivot the array into 3 subarrays: 132 | 133 | $$|..\leq..|..=..|..unseen..|..\geq..|$$ 134 | \begin{python} 135 | def pivot_3way(self, A, lo, hi): 136 | # pointing to end of left array LT 137 | left = lo-1 138 | # pointing to the end of array right GT (reversed) 139 | right = hi 140 | 141 | pivot = A[lo] 142 | i = lo # scanning pointer 143 | while i < right: # not n nor hi 144 | if A[i] < pivot: 145 | left += 1 146 | A[left], A[i] = A[i], A[left] 147 | i += 1 148 | elif A[i] == pivot: 149 | i += 1 150 | else: 151 | right -= 1 152 | A[right], A[i] = A[i], A[right] 153 | # i stays 154 | 155 | return left, right 156 | \end{python} 157 | \subsection{Merge Sort} 158 | \begin{figure}[!htp] 159 | \centering 160 | \subfloat{\includegraphics[width=\linewidth]{msort}} 161 | \caption{Merge Sort} 162 | \label{fig:msort} 163 | \end{figure} 164 | \runinhead{Normal merge} Normal merge sort with extra space 165 | \begin{python} 166 | def merge_sort(self, A): 167 | if len(A) <= 1: 168 | return 169 | 170 | mid = len(A) // 2 171 | L, R = A[:mid], A[mid:] 172 | self.merge_sort(L) 173 | self.merge_sort(R) 174 | 175 | l, r, i = 0, 0, 0 176 | while l < len(L) and r < len(R): 177 | if L[l] < R[r]: 178 | A[i] = L[l] 179 | l += 1 180 | else: 181 | A[i] = R[r] 182 | r += 1 183 | i += 1 184 | 185 | if l < len(L): 186 | A[i:] = L[l:] 187 | if r < len(R): 188 | A[i:] = R[r:] 189 | \end{python} 190 | 191 | \runinhead{Merge backward.} Merge two arrays in the place of one of the arrays. 192 | \begin{python} 193 | def merge(self, A, m, B, n): 194 | """ 195 | Arrays in asc order. 196 | Assume A has enough space. 197 | CONSTANT SPACE: starting backward. 198 | """ 199 | i = m-1 200 | j = n-1 201 | closed = m+n 202 | 203 | while i >= 0 and j >= 0: 204 | closed -= 1 205 | if A[i] > B[j]: 206 | A[closed] = A[i] 207 | i -= 1 208 | else: 209 | A[closed] = B[j] 210 | j -= 1 211 | 212 | # either-or 213 | # dangling 214 | if j >= 0: A[:closed] = B[:j+1] 215 | # if i >= 0: A[:closed] = A[:i+1] 216 | \end{python} 217 | \runinhead{In-place \& Iterative merge.} 218 | In-place merge sort of array without recursion. The basic idea is to avoid the recursive call while using iterative solution. 219 | 220 | The algorithm first merge chunk of length of 2, 4, 8 ... until $2^k$ where $2^k$ is large than the length of the array. 221 | \begin{python} 222 | def merge_sort(self, A): 223 | n = len(A) 224 | l = 1 225 | while l <= n: 226 | for i in range(0, n, l*2): 227 | lo, hi = i, min(n, i+2*l) 228 | mid = i + l 229 | p, q = lo, mid 230 | while p < mid and q < hi: 231 | if A[p] < A[q]: 232 | p += 1 233 | else: 234 | tmp = A[q] 235 | A[p+1:q+1] = A[p:q] 236 | A[p] = tmp 237 | p, mid, q = p+1, mid+1, q+1 238 | 239 | l *= 2 240 | 241 | return A 242 | \end{python} 243 | The time complexity may be degenerated to $O(n^2)$. 244 | \subsection{Do Something While Merging} 245 | During the merging, the left half and the right half are both sorted; therefore, we can carry out operations like: 246 | \begin{enumerate} 247 | \item inversion count 248 | \item range sum count 249 | \end{enumerate} 250 | 251 | \runinhead{Count of Range Sum.} Given an integer array nums, return the number of range sums that lie in $[lower, upper]$ inclusively. Make an array $A$ of sums, where \pyinline{A[i]} is \pyinline{sum(nums[:i])}; and then feed to merge sort. Since both the left half and the right half are sorted, we can diff $A$ in $O(n)$ time to find range sum. 252 | 253 | \begin{python} 254 | def msort(A, lo, hi): 255 | if lo + 1 >= hi: 256 | return 0 257 | 258 | mid = (lo + hi)//2 259 | cnt = msort(A, lo, mid) + msort(A, mid, hi) 260 | 261 | temp = [] 262 | i = j = r = mid 263 | for l in range(lo, mid): 264 | # range count 265 | while i < hi and A[i] - A[l] < LOWER: i += 1 266 | while j < hi and A[j] - A[l] <= UPPER: j += 1 267 | cnt += j - i 268 | 269 | # normal merge 270 | while r < hi and A[r] < A[l]: 271 | temp.append(A[r]) 272 | r += 1 273 | 274 | temp.append(A[l]) 275 | 276 | while r < hi: # dangling right 277 | temp.append(A[r]) 278 | r += 1 279 | 280 | A[lo:hi] = temp 281 | return cnt 282 | \end{python} 283 | 284 | Here, the implementation of merge sort use: 1 for-loop for the left half and 2 while-loop for the right half. 285 | 286 | \subsection{Bridge Two Sorted Arrays} 287 | We want to bridge (not merge) two sorted arrays by remove a minimal number of elements from two arrays. 288 | $$ 289 | A[:i] + B[j:] 290 | $$ 291 | For example: 292 | \begin{python} 293 | A = [1, 2, 3, 10] 294 | B = [1, 8, 9] 295 | 296 | ret = [1, 2, 3, 8, 9] 297 | \end{python} 298 | \rih{Core Clues:} 299 | \begin{enumerate} 300 | \item Search: We need to search the bridging indices on two arrays 301 | \item Brute force: iterate both arrays, $O(N^2)$ 302 | \item Binary search: iterate $A$ while biscet $B$, using \pyinline{bisect.bisect_right}, $O(N \lg{N})$ 303 | \item Two pointers: since both $A$ and $B$ are sorted, moving the index $i$ in $A$ forward, the search of $j$ in $B$ can only be forward. $j$ cannot go backward. 304 | \end{enumerate} 305 | 306 | \begin{python} 307 | def bridge(A, B): 308 | # A[:i] 309 | # B[j:] 310 | ret = 0 311 | j = 0 312 | for i in range(len(A)+1): 313 | while i-1 >= 0 and j < len(B) and A[i-1] > B[j]: 314 | j += 1 315 | 316 | ret = max(ret, i + (len(B)-j)) 317 | 318 | return ret 319 | \end{python} 320 | 321 | \section{Properties} 322 | \subsection{Stability} 323 | Definition: a stable sort preserves the \textbf{relative order of items with equal keys} (scenario: sorted by time then sorted by location). 324 | 325 | Algorithms: 326 | \begin{enumerate} 327 | \item Stable 328 | \begin{enumerate} 329 | \item Merge sort 330 | \item Insertion sort 331 | \end{enumerate} 332 | \item Unstable 333 | \begin{enumerate} 334 | \item Selection sort 335 | \item Shell sort 336 | \item Quick sort 337 | \item Heap sort 338 | \end{enumerate} 339 | \end{enumerate} 340 | \textbf{Long-distance swap} operation is the key to find the unstable case during sorting. 341 | \begin{figure}[hbtp] 342 | \centering 343 | \subfloat{\includegraphics[width=\linewidth]{stable_sort}} 344 | \caption{Stale sort vs. unstable sort} 345 | \label{fig:trie} 346 | \end{figure} 347 | 348 | \subsection{Sorting Applications} 349 | \begin{enumerate} 350 | \item Sort 351 | \item Partial quick sort (selection), k-th largest elements 352 | \item Binary search 353 | \item Find duplicates 354 | \item Graham scan 355 | \item Data compression 356 | \end{enumerate} 357 | 358 | \subsection{Considerations} 359 | \begin{enumerate} 360 | \item Stable? 361 | \item Distinct keys? 362 | \item Need guaranteed performance? 363 | \item Linked list or arrays? 364 | \item Caching system? (reference to neighboring cells in the array? 365 | \item Usually randomly ordered array? 366 | (or partially sorted?)\item Parallel? 367 | \item Deterministic? 368 | \item Multiple key types? 369 | \end{enumerate} 370 | 371 | $O(N\lg N)$ is the lower bound of comparison-based sorting; but for other 372 | contexts, we may not need $O(N \lg N)$: 373 | \begin{enumerate} 374 | \item Partially-ordered arrays: insertion sort to achieve $O(N)$. \textbf{Number of inversions}: 1 inversion $=$ 1 pair of keys that are out 375 | of order. 376 | \item Duplicate keys 377 | \item Digital properties of keys: radix sort to achieve $O(N)$. 378 | \end{enumerate} 379 | 380 | \subsection{Sorting Summary} 381 | See Figure \ref{fig:sortSummary}. 382 | \begin{figure*}[h] 383 | \centering 384 | \subfloat{\includegraphics[width=0.8\linewidth]{sort_summary}} 385 | \caption{Sort summary} 386 | \label{fig:sortSummary} 387 | \end{figure*} 388 | 389 | \section{Partial Quicksort} 390 | \subsection{Find $k$ smallest}\label{find-k-th} 391 | \runinhead{Heap-based solution.} $O(n \log k)$ 392 | 393 | Version 1, construct heap with $n$ numbers, and take $k$: $O(n+k\log n)$, where $O(n)$ is for constructing heap. 394 | 395 | Version 2. construct heap with $k$ numbers, and iterate $n$: $O(n\log k)$. 396 | 397 | The 2nd version is much faster and more memory efficient. 398 | 399 | \begin{itemize} 400 | \item To find k-th \textit{smallest}, maintain a \textit{max}-heap of top k smallest number, with the top of the heap being the current k-th smallest. 401 | \item To find k-th \textit{largest}, maintain a \textit{min}-heap of tok k largest number, with the top of the heap being the current k-th largest. 402 | \end{itemize} 403 | 404 | 405 | 406 | In python built-in there are: 407 | \begin{python} 408 | heapq.nlargest(n, iterable, key=None) 409 | heapq.nsmallest(n, iterable, key=None) 410 | \end{python} 411 | 412 | 413 | \runinhead{Partial Quicksort} Then the $A[:k]$ is sorted $k$ smallest. The algorithm recursively sort the $A[lo:hi]$ 414 | 415 | The average time complexity is 416 | \begin{eqnarray*} 417 | F(n) = \left\{ \begin{array}{rl} 418 | F(\frac{n}{2})+O(n) &\mbox{// if $\frac{n}{2} \geq k$} \\ 419 | 2F(\frac{n}{2})+O(n) &\mbox{// otherwise} 420 | \end{array} \right. 421 | \end{eqnarray*} 422 | Therefore, the complexity is $O(n+k \log k)$. 423 | \begin{python} 424 | def partial_qsort(self, A, lo, hi, k): 425 | if lo >= hi: 426 | return 427 | 428 | p = self.pivot(A, lo, hi) 429 | self.partial_qsort(A, lo, p, k) 430 | if k <= p+1: 431 | return 432 | self.partial_qsort(A, p+1, hi, k) 433 | \end{python} 434 | The partial quick sort will find the $k$ smallest number in sorted order. If the top $k$ elements are not required to be sorted, then use find $k$-th algorithm 435 | 436 | \subsection{Find $k$-th: Quick Select} 437 | Use partial quick sort to find $k$-th smallest element in the unsorted array. The algorithm recursively sort the $A[lo:hi]$ 438 | 439 | The average time complexity is 440 | \begin{align*} 441 | F(n) &= F(n/2) + O(n) \\ 442 | &= O(n) 443 | \end{align*} 444 | Refresh the \pyinline{def pivot}: 445 | \begin{python} 446 | def pivot(self, A, lo, hi): 447 | p = hi 448 | l = lo 449 | for i in range(lo, hi - 1): 450 | if A[i] < A[p]: 451 | A[i], A[l] = A[l], A[i] 452 | l += 1 453 | 454 | A[l], A[p] = A[p], A[l] 455 | return l # return the pivot index 456 | \end{python} 457 | Find $k$-th with 2-way partitioning. Notice that only index changing while $k$ is the same. 458 | \begin{python} 459 | def find_kth(self, A, lo, hi, k): 460 | if lo >= hi: 461 | return 462 | 463 | p = self.pivot(A, lo, hi) 464 | if k == p: 465 | return p 466 | elif k < p: 467 | return self.find_kth(A, lo, p, k) 468 | else: 469 | return self.find_kth(A, p+1, hi, k) 470 | \end{python} 471 | Find $k$-th with 3-way partitioning. Pay attention to the indexing. $lt$, $gt$ means the last index of less-than portion and larger-than partion. 472 | \begin{python} 473 | def find_kth(self, A, lo, hi, k): 474 | if lo >= hi: 475 | return 476 | 477 | lt, gt = self.pivot(A, lo, hi) 478 | if lt < k < gt: 479 | return k 480 | elif k <= lt: 481 | return self.find_kth(A, lo, lt+1, k) 482 | else: 483 | return self.find_kth(A, gt, hi, k) 484 | \end{python} 485 | 486 | Pivoting see section - \ref{section:pivot}. 487 | 488 | \runinhead{Find $k$-th in union of two sorted array.} Given sorted two arrays $A, B$, find the $k$-th element (0 based index). 489 | 490 | Core clues: 491 | \begin{enumerate} 492 | \item To reduce the complexity of $O(\log (m+n))$, need to half the arrays. 493 | \item Decide which half of the array to disregard. 494 | \item Decide whether to disregard the median (i.e. boundary point). 495 | \end{enumerate} 496 | \begin{python} 497 | def find_kth(self, A, B, k): 498 | if not A: return B[k] 499 | if not B: return A[k] 500 | if k == 0: return min(A[0], B[0]) 501 | 502 | m, n = len(A), len(B) 503 | if A[m/2] >= B[n/2]: 504 | if k > m/2 + n/2: 505 | return self.find_kth(A, B[n/2+1:], k-n/2-1) # exclude median 506 | else: 507 | return self.find_kth(A[:m/2], B, k) # exclude median 508 | else: 509 | return self.find_kth(B, A, k) # swap 510 | \end{python} 511 | \subsection{Applications} 512 | \runinhead{Wiggle Sort.} Given an unsorted array $A$, reorder it such that $A_0 < A_1 > A_2 < A_3$. Do it in $O(n)$ time and $O(1)$ space. 513 | 514 | Core clues: 515 | \begin{enumerate} 516 | \item Quick selection for finding median (Average $O(n)$) 517 | \item Three-way partitioning to split the data 518 | \item Re-mapping the index to do in-place partitioning 519 | \end{enumerate} 520 | 521 | \runinhead{Pre-processing} Sorting can be an important pre-processing step as to: 522 | \begin{enumerate} 523 | \item Satisfying the output order (e.g. if multiple results are possible, output the one that's smallest in terms of the natural order). 524 | \end{enumerate} 525 | 526 | \begin{python} 527 | class Solution: 528 | def wiggleSort(self, A): 529 | n = len(A) 530 | median_idx = self.find_kth(A, 0, n, n/2) 531 | v = A[median_idx] 532 | 533 | idx = lambda i: (2*i+1)%(n|1) 534 | lt = -1 535 | hi = n 536 | i = 0 537 | while i < hi: 538 | if A[idx(i)] > v: 539 | lt += 1 540 | A[idx(lt)], A[idx(i)] = A[idx(i)], A[idx(lt)] 541 | i += 1 542 | elif A[idx(i)] == v: 543 | i += 1 544 | else: 545 | hi -= 1 546 | A[idx(hi)], A[idx(i)] = A[idx(i)], A[idx(hi)] 547 | 548 | def pivot(self, A, lo, hi, pidx=None): 549 | lt = lo-1 550 | gt = hi 551 | if not pidx: pidx = lo 552 | 553 | v = A[pidx] 554 | i = lo 555 | while i < gt: 556 | if A[i] < v: 557 | lt += 1 558 | A[lt], A[i] = A[i], A[lt] 559 | i += 1 560 | elif A[i] == v: 561 | i += 1 562 | else: 563 | gt -= 1 564 | A[gt], A[i] = A[i], A[gt] 565 | 566 | return lt, gt 567 | 568 | def find_kth(self, A, lo, hi, k): 569 | if lo >= hi: return 570 | 571 | lt, gt = self.pivot(A, lo, hi) 572 | 573 | if lt < k < gt: 574 | return k 575 | if k <= lt: 576 | return self.find_kth(A, lo, lt+1, k) 577 | else: 578 | return self.find_kth(A, gt, hi, k) 579 | \end{python} 580 | 581 | \section{Inversion} 582 | If $a_i > a_j$ but $i A2[i2]: # push the A2 to A 614 | A[i] = A2[i2] 615 | i2 += 1 616 | # number of reverse-ordered pairs 617 | ret += len(A1) - i1 618 | else: 619 | A[i] = A1[i1] 620 | i1 += 1 621 | 622 | return ret 623 | 624 | def merge_sort(a): 625 | n = len(a) 626 | if n == 1: 627 | return 0 628 | 629 | a1 = a[:n/2] 630 | a2 = a[n/2:] 631 | 632 | ret1 = merge_sort(a1) 633 | ret2 = merge_sort(a2) 634 | # merge not merge_sort 635 | ret = ret1+ret2+merge(a1, a2, a) 636 | return ret 637 | \end{python} --------------------------------------------------------------------------------